Migrate test_utils from acts to acts_contrib

Bug: 171076051
Test: local
Change-Id: Idddaf0a715a5d3e48f13614328191fd270ee5582
Merged-In: Idddaf0a715a5d3e48f13614328191fd270ee5582
diff --git a/acts_tests/acts_contrib/test_utils b/acts_tests/acts_contrib/test_utils
deleted file mode 120000
index 8a6ce16..0000000
--- a/acts_tests/acts_contrib/test_utils
+++ /dev/null
@@ -1 +0,0 @@
-../../acts/framework/acts/test_utils
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/OWNERS b/acts_tests/acts_contrib/test_utils/OWNERS
new file mode 100644
index 0000000..bf3ed6c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/OWNERS
@@ -0,0 +1 @@
+include /acts_tests/tests/OWNERS
diff --git a/acts_tests/acts_contrib/test_utils/__init__.py b/acts_tests/acts_contrib/test_utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/abstract_devices/__init__.py b/acts_tests/acts_contrib/test_utils/abstract_devices/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/abstract_devices/bluetooth_device.py b/acts_tests/acts_contrib/test_utils/abstract_devices/bluetooth_device.py
new file mode 100644
index 0000000..1e02d89
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/bluetooth_device.py
@@ -0,0 +1,1471 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import inspect
+import logging
+
+from queue import Empty
+
+from acts.controllers.android_device import AndroidDevice
+from acts.controllers.fuchsia_device import FuchsiaDevice
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import gatt_event
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_gatt_utils import GattTestUtilsError
+from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_connection
+from acts_contrib.test_utils.fuchsia.bt_test_utils import le_scan_for_device_by_name
+
+import acts_contrib.test_utils.bt.bt_test_utils as bt_test_utils
+
+
+def create_bluetooth_device(hardware_device):
+    """Creates a generic Bluetooth device based on type of device that is sent
+    to the functions.
+
+    Args:
+        hardware_device: A Bluetooth hardware device that is supported by ACTS.
+    """
+    if isinstance(hardware_device, FuchsiaDevice):
+        return FuchsiaBluetoothDevice(hardware_device)
+    elif isinstance(hardware_device, AndroidDevice):
+        return AndroidBluetoothDevice(hardware_device)
+    else:
+        raise ValueError('Unable to create BluetoothDevice for type %s' %
+                         type(hardware_device))
+
+
+class BluetoothDevice(object):
+    """Class representing a generic Bluetooth device.
+
+    Each object of this class represents a generic Bluetooth device.
+    Android device and Fuchsia devices are the currently supported devices.
+
+    Attributes:
+        device: A generic Bluetooth device.
+    """
+    def __init__(self, device):
+        self.device = device
+        self.log = logging
+
+    def a2dp_initiate_open_stream(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def start_profile_a2dp_sink(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def stop_profile_a2dp_sink(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def start_pairing_helper(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def set_discoverable(self, is_discoverable):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def bluetooth_toggle_state(self, state):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_discover_characteristic_by_uuid(self, peer_identifier,
+                                                    uuid):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def initialize_bluetooth_controller(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def get_pairing_pin(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def input_pairing_pin(self, pin):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def get_bluetooth_local_address(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_connect(self, peer_identifier, transport, autoconnect):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_write_characteristic_without_response_by_handle(
+            self, peer_identifier, handle, value):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_write_characteristic_by_handle(self, peer_identifier,
+                                                   handle, offset, value):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_read_characteristic_by_handle(self, peer_identifier,
+                                                  handle):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_read_characteristic_by_uuid(self, peer_identifier, uuid):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_read_long_characteristic_by_handle(self, peer_identifier,
+                                                       handle, offset,
+                                                       max_bytes):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_enable_notifiy_characteristic_by_handle(
+            self, peer_identifier, handle):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_disable_notifiy_characteristic_by_handle(
+            self, peer_identifier, handle):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_read_descriptor_by_handle(self, peer_identifier, handle):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_write_descriptor_by_handle(self, peer_identifier, handle,
+                                               offset, value):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_long_read_descriptor_by_handle(self, peer_identifier,
+                                                   handle, offset, max_bytes):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_disconnect(self, peer_identifier):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_refresh(self, peer_identifier):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def le_scan_with_name_filter(self, name, timeout):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def log_info(self, log):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def reset_bluetooth(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def sdp_add_search(self, attribute_list, profile_id):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def sdp_add_service(self, sdp_record):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def sdp_clean_up(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def sdp_init(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def sdp_remove_service(self, service_id):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def start_le_advertisement(self, adv_data, scan_response, adv_interval,
+                               connectable):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def stop_le_advertisement(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def set_bluetooth_local_name(self, name):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def setup_gatt_server(self, database):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def close_gatt_server(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def unbond_device(self, peer_identifier):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def unbond_all_known_devices(self):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def init_pair(self, peer_identifier, security_level, non_bondable,
+                  transport):
+        """Base generic Bluetooth interface. Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+
+class AndroidBluetoothDevice(BluetoothDevice):
+    """Class wrapper for an Android Bluetooth device.
+
+    Each object of this class represents a generic Bluetooth device.
+    Android device and Fuchsia devices are the currently supported devices/
+
+    Attributes:
+        android_device: An Android Bluetooth device.
+    """
+    def __init__(self, android_device):
+        super().__init__(android_device)
+        self.gatt_timeout = 10
+        self.peer_mapping = {}
+        self.discovered_services_index = None
+
+    def _client_wait(self, gatt_event, gatt_callback):
+        return self._timed_pop(gatt_event, gatt_callback)
+
+    def _timed_pop(self, gatt_event, gatt_callback):
+        expected_event = gatt_event["evt"].format(gatt_callback)
+        try:
+            return self.device.ed.pop_event(expected_event, self.gatt_timeout)
+        except Empty as emp:
+            raise AssertionError(gatt_event["err"].format(expected_event))
+
+    def _setup_discovered_services_index(self, bluetooth_gatt):
+        """ Sets the discovered services index for the gatt connection
+        related to the Bluetooth GATT callback object.
+
+        Args:
+            bluetooth_gatt: The BluetoothGatt callback id
+        """
+        if not self.discovered_services_index:
+            self.device.droid.gattClientDiscoverServices(bluetooth_gatt)
+            expected_event = gatt_cb_strings['gatt_serv_disc'].format(
+                self.gatt_callback)
+            event = self.dut.ed.pop_event(expected_event, self.gatt_timeout)
+            self.discovered_services_index = event['data']['ServicesIndex']
+
+    def a2dp_initiate_open_stream(self):
+        raise NotImplementedError("{} not yet implemented.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def start_profile_a2dp_sink(self):
+        raise NotImplementedError("{} not yet implemented.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def stop_profile_a2dp_sink(self):
+        raise NotImplementedError("{} not yet implemented.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def bluetooth_toggle_state(self, state):
+        self.device.droid.bluetoothToggleState(state)
+
+    def set_discoverable(self, is_discoverable):
+        """ Sets the device's discoverability.
+
+        Args:
+            is_discoverable: True if discoverable, false if not discoverable
+        """
+        if is_discoverable:
+            self.device.droid.bluetoothMakeDiscoverable()
+        else:
+            self.device.droid.bluetoothMakeUndiscoverable()
+
+    def initialize_bluetooth_controller(self):
+        """ Just pass for Android as there is no concept of initializing
+        a Bluetooth controller.
+        """
+        pass
+
+    def start_pairing_helper(self):
+        """ Starts the Android pairing helper.
+        """
+        self.device.droid.bluetoothStartPairingHelper(True)
+
+    def gatt_client_write_characteristic_without_response_by_handle(
+            self, peer_identifier, handle, value):
+        """ Perform a GATT Client write Characteristic without response to
+        remote peer GATT server database.
+
+        Args:
+            peer_identifier: The mac address associated with the GATT connection
+            handle: The characteristic handle (or instance id).
+            value: The list of bytes to write.
+        Returns:
+            True if success, False if failure.
+        """
+        peer_info = self.peer_mapping.get(peer_identifier)
+        if not peer_info:
+            self.log.error(
+                "Peer idenifier {} not currently connected or unknown.".format(
+                    peer_identifier))
+            return False
+        self._setup_discovered_services_index()
+        self.device.droid.gattClientWriteCharacteristicByInstanceId(
+            peer_info.get('bluetooth_gatt'), self.discovered_services_index,
+            handle, value)
+        try:
+            event = self._client_wait(gatt_event['char_write'],
+                                      peer_info.get('gatt_callback'))
+        except AssertionError as err:
+            self.log.error("Failed to write Characteristic: {}".format(err))
+        return True
+
+    def gatt_client_write_characteristic_by_handle(self, peer_identifier,
+                                                   handle, offset, value):
+        """ Perform a GATT Client write Characteristic without response to
+        remote peer GATT server database.
+
+        Args:
+            peer_identifier: The mac address associated with the GATT connection
+            handle: The characteristic handle (or instance id).
+            offset: Not used yet.
+            value: The list of bytes to write.
+        Returns:
+            True if success, False if failure.
+        """
+        peer_info = self.peer_mapping.get(peer_identifier)
+        if not peer_info:
+            self.log.error(
+                "Peer idenifier {} not currently connected or unknown.".format(
+                    peer_identifier))
+            return False
+        self._setup_discovered_services_index()
+        self.device.droid.gattClientWriteCharacteristicByInstanceId(
+            peer_info.get('bluetooth_gatt'), self.discovered_services_index,
+            handle, value)
+        try:
+            event = self._client_wait(gatt_event['char_write'],
+                                      peer_info.get('gatt_callback'))
+        except AssertionError as err:
+            self.log.error("Failed to write Characteristic: {}".format(err))
+        return True
+
+    def gatt_client_read_characteristic_by_handle(self, peer_identifier,
+                                                  handle):
+        """ Perform a GATT Client read Characteristic to remote peer GATT
+        server database.
+
+        Args:
+            peer_identifier: The mac address associated with the GATT connection
+            handle: The characteristic handle (or instance id).
+        Returns:
+            Value of Characteristic if success, None if failure.
+        """
+        peer_info = self.peer_mapping.get(peer_identifier)
+        if not peer_info:
+            self.log.error(
+                "Peer idenifier {} not currently connected or unknown.".format(
+                    peer_identifier))
+            return False
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientReadCharacteristicByInstanceId(
+            peer_info.get('bluetooth_gatt'), self.discovered_services_index,
+            handle)
+        try:
+            event = self._client_wait(gatt_event['char_read'],
+                                      peer_info.get('gatt_callback'))
+        except AssertionError as err:
+            self.log.error("Failed to read Characteristic: {}".format(err))
+
+        return event['data']['CharacteristicValue']
+
+    def gatt_client_read_long_characteristic_by_handle(self, peer_identifier,
+                                                       handle, offset,
+                                                       max_bytes):
+        """ Perform a GATT Client read Characteristic to remote peer GATT
+        server database.
+
+        Args:
+            peer_identifier: The mac address associated with the GATT connection
+            offset: Not used yet.
+            handle: The characteristic handle (or instance id).
+            max_bytes: Not used yet.
+        Returns:
+            Value of Characteristic if success, None if failure.
+        """
+        peer_info = self.peer_mapping.get(peer_identifier)
+        if not peer_info:
+            self.log.error(
+                "Peer idenifier {} not currently connected or unknown.".format(
+                    peer_identifier))
+            return False
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientReadCharacteristicByInstanceId(
+            peer_info.get('bluetooth_gatt'), self.discovered_services_index,
+            handle)
+        try:
+            event = self._client_wait(gatt_event['char_read'],
+                                      peer_info.get('gatt_callback'))
+        except AssertionError as err:
+            self.log.error("Failed to read Characteristic: {}".format(err))
+
+        return event['data']['CharacteristicValue']
+
+    def gatt_client_enable_notifiy_characteristic_by_handle(
+            self, peer_identifier, handle):
+        """ Perform a GATT Client enable Characteristic notification to remote
+        peer GATT server database.
+
+        Args:
+            peer_identifier: The mac address associated with the GATT connection
+            handle: The characteristic handle.
+        Returns:
+            True is success, False if failure.
+        """
+        raise NotImplementedError("{} not yet implemented.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_disable_notifiy_characteristic_by_handle(
+            self, peer_identifier, handle):
+        """ Perform a GATT Client disable Characteristic notification to remote
+        peer GATT server database.
+
+        Args:
+            peer_identifier: The mac address associated with the GATT connection
+            handle: The characteristic handle.
+        Returns:
+            True is success, False if failure.
+        """
+        raise NotImplementedError("{} not yet implemented.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def gatt_client_read_descriptor_by_handle(self, peer_identifier, handle):
+        """ Perform a GATT Client read Descriptor to remote peer GATT
+        server database.
+
+        Args:
+            peer_identifier: The mac address associated with the GATT connection
+            handle: The Descriptor handle (or instance id).
+        Returns:
+            Value of Descriptor if success, None if failure.
+        """
+        peer_info = self.peer_mapping.get(peer_identifier)
+        if not peer_info:
+            self.log.error(
+                "Peer idenifier {} not currently connected or unknown.".format(
+                    peer_identifier))
+            return False
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientReadDescriptorByInstanceId(
+            peer_info.get('bluetooth_gatt'), self.discovered_services_index,
+            handle)
+        try:
+            event = self._client_wait(gatt_event['desc_read'],
+                                      peer_info.get('gatt_callback'))
+        except AssertionError as err:
+            self.log.error("Failed to read Descriptor: {}".format(err))
+        # TODO: Implement sending Descriptor value in SL4A such that the data
+        # can be represented by: event['data']['DescriptorValue']
+        return ""
+
+    def gatt_client_write_descriptor_by_handle(self, peer_identifier, handle,
+                                               offset, value):
+        """ Perform a GATT Client write Descriptor to the remote peer GATT
+        server database.
+
+        Args:
+            peer_identifier: The mac address associated with the GATT connection
+            handle: The Descriptor handle (or instance id).
+            offset: Not used yet
+            value: The list of bytes to write.
+        Returns:
+            True if success, False if failure.
+        """
+        peer_info = self.peer_mapping.get(peer_identifier)
+        if not peer_info:
+            self.log.error(
+                "Peer idenifier {} not currently connected or unknown.".format(
+                    peer_identifier))
+            return False
+        self._setup_discovered_services_index()
+        self.device.droid.gattClientWriteDescriptorByInstanceId(
+            peer_info.get('bluetooth_gatt'), self.discovered_services_index,
+            handle, value)
+        try:
+            event = self._client_wait(gatt_event['desc_write'],
+                                      peer_info.get('gatt_callback'))
+        except AssertionError as err:
+            self.log.error("Failed to write Characteristic: {}".format(err))
+        return True
+
+    def gatt_connect(self, peer_identifier, transport, autoconnect=False):
+        """ Perform a GATT connection to a perihperal.
+
+        Args:
+            peer_identifier: The mac address to connect to.
+            transport: Which transport to use.
+            autoconnect: Set autocnnect to True or False.
+        Returns:
+            True if success, False if failure.
+        """
+        try:
+            bluetooth_gatt, gatt_callback = setup_gatt_connection(
+                self.device, peer_identifier, autoconnect, transport)
+            self.peer_mapping[peer_identifier] = {
+                "bluetooth_gatt": bluetooth_gatt,
+                "gatt_callback": gatt_callback
+            }
+        except GattTestUtilsError as err:
+            self.log.error(err)
+            return False
+        return True
+
+    def gatt_disconnect(self, peer_identifier):
+        """ Perform a GATT disconnect from a perihperal.
+
+        Args:
+            peer_identifier: The peer to disconnect from.
+        Returns:
+            True if success, False if failure.
+        """
+        peer_info = self.peer_mapping.get(peer_identifier)
+        if not peer_info:
+            self.log.error(
+                "No previous connections made to {}".format(peer_identifier))
+            return False
+
+        try:
+            disconnect_gatt_connection(self.device,
+                                       peer_info.get("bluetooth_gatt"),
+                                       peer_info.get("gatt_callback"))
+            self.device.droid.gattClientClose(peer_info.get("bluetooth_gatt"))
+        except GattTestUtilsError as err:
+            self.log.error(err)
+            return False
+        self.device.droid.gattClientClose(peer_info.get("bluetooth_gatt"))
+
+    def gatt_client_refresh(self, peer_identifier):
+        """ Perform a GATT Client Refresh of a perihperal.
+
+        Clears the internal cache and forces a refresh of the services from the
+        remote device.
+
+        Args:
+            peer_identifier: The peer to refresh.
+        """
+        peer_info = self.peer_mapping.get(peer_identifier)
+        if not peer_info:
+            self.log.error(
+                "No previous connections made to {}".format(peer_identifier))
+            return False
+        self.device.droid.gattClientRefresh(peer_info["bluetooth_gatt"])
+
+    def le_scan_with_name_filter(self, name, timeout):
+        """ Scan over LE for a specific device name.
+
+         Args:
+            name: The name filter to set.
+            timeout: The timeout to wait to find the advertisement.
+        Returns:
+            Discovered mac address or None
+        """
+        self.device.droid.bleSetScanSettingsScanMode(
+            ble_scan_settings_modes['low_latency'])
+        filter_list = self.device.droid.bleGenFilterList()
+        scan_settings = self.device.droid.bleBuildScanSetting()
+        scan_callback = self.device.droid.bleGenScanCallback()
+        self.device.droid.bleSetScanFilterDeviceName(name)
+        self.device.droid.bleBuildScanFilter(filter_list)
+        self.device.droid.bleSetScanFilterDeviceName(self.name)
+        self.device.droid.bleStartBleScan(filter_list, scan_settings,
+                                          scan_callback)
+        try:
+            event = self.device.ed.pop_event(scan_result.format(scan_callback),
+                                             timeout)
+            return event['data']['Result']['deviceInfo']['address']
+        except Empty as err:
+            self.log.info("Scanner did not find advertisement {}".format(err))
+            return None
+
+    def log_info(self, log):
+        """ Log directly onto the device.
+
+        Args:
+            log: The informative log.
+        """
+        self.device.droid.log.logI(log)
+
+    def set_bluetooth_local_name(self, name):
+        """ Sets the Bluetooth controller's local name
+        Args:
+            name: The name to set.
+        """
+        self.device.droid.bluetoothSetLocalName(name)
+
+    def get_local_bluetooth_address(self):
+        """ Returns the Bluetooth local address.
+        """
+        return self.device.droid.bluetoothGetLocalAddress()
+
+    def reset_bluetooth(self):
+        """ Resets Bluetooth on the Android Device.
+        """
+        bt_test_utils.reset_bluetooth([self.device])
+
+    def sdp_add_search(self, attribute_list, profile_id):
+        """Adds an SDP search record.
+        Args:
+            attribute_list: The list of attributes to set
+            profile_id: The profile ID to set.
+        """
+        # Android devices currently have no hooks to modify the SDP record.
+        pass
+
+    def sdp_add_service(self, sdp_record):
+        """Adds an SDP service record.
+        Args:
+            sdp_record: The dictionary representing the search record to add.
+        Returns:
+            service_id: The service id to track the service record published.
+                None if failed.
+        """
+        # Android devices currently have no hooks to modify the SDP record.
+        pass
+
+    def sdp_clean_up(self):
+        """Cleans up all objects related to SDP.
+        """
+        self.device.sdp_lib.cleanUp()
+
+    def sdp_init(self):
+        """Initializes SDP on the device.
+        """
+        # Android devices currently have no hooks to modify the SDP record.
+        pass
+
+    def sdp_remove_service(self, service_id):
+        """Removes a service based on an input id.
+        Args:
+            service_id: The service ID to remove.
+        """
+        # Android devices currently have no hooks to modify the SDP record.
+        pass
+
+    def unbond_all_known_devices(self):
+        """ Unbond all known remote devices.
+        """
+        self.device.droid.bluetoothFactoryReset()
+
+    def unbond_device(self, peer_identifier):
+        """ Unbond peer identifier.
+
+        Args:
+            peer_identifier: The mac address for the peer to unbond.
+
+        """
+        self.device.droid.bluetoothUnbond(peer_identifier)
+
+    def init_pair(self, peer_identifier, security_level, non_bondable,
+                  transport):
+        """ Send an outgoing pairing request the input peer_identifier.
+
+        Android currently does not support setting various security levels or
+        bondable modes. Making them available for other bluetooth_device
+        variants. Depending on the Address type, Android will figure out the
+        transport to pair automatically.
+
+        Args:
+            peer_identifier: A string representing the device id.
+            security_level: Not yet implemented. See Fuchsia device impl.
+            non_bondable: Not yet implemented. See Fuchsia device impl.
+            transport: Not yet implemented. See Fuchsia device impl.
+
+        """
+        self.dut.droid.bluetoothBond(self.peer_identifier)
+
+
+class FuchsiaBluetoothDevice(BluetoothDevice):
+    """Class wrapper for an Fuchsia Bluetooth device.
+
+    Each object of this class represents a generic luetooth device.
+    Android device and Fuchsia devices are the currently supported devices/
+
+    Attributes:
+        fuchsia_device: A Fuchsia Bluetooth device.
+    """
+    def __init__(self, fuchsia_device):
+        super().__init__(fuchsia_device)
+
+    def a2dp_initiate_open_stream(self):
+        raise NotImplementedError("{} not yet implemented.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def start_profile_a2dp_sink(self):
+        """ Starts the A2DP sink profile.
+        """
+        self.device.control_daemon("bt-a2dp-sink.cmx", "start")
+
+    def stop_profile_a2dp_sink(self):
+        """ Stops the A2DP sink profile.
+        """
+        self.device.control_daemon("bt-a2dp-sink.cmx", "stop")
+
+    def start_pairing_helper(self):
+        self.device.bts_lib.acceptPairing()
+
+    def bluetooth_toggle_state(self, state):
+        """Stub for Fuchsia implementation."""
+        pass
+
+    def set_discoverable(self, is_discoverable):
+        """ Sets the device's discoverability.
+
+        Args:
+            is_discoverable: True if discoverable, false if not discoverable
+        """
+        self.device.bts_lib.setDiscoverable(is_discoverable)
+
+    def get_pairing_pin(self):
+        """ Get the pairing pin from the active pairing delegate.
+        """
+        return self.device.bts_lib.getPairingPin()['result']
+
+    def input_pairing_pin(self, pin):
+        """ Input pairing pin to active pairing delegate.
+
+        Args:
+            pin: The pin to input.
+        """
+        self.device.bts_lib.inputPairingPin(pin)
+
+    def initialize_bluetooth_controller(self):
+        """ Initialize Bluetooth controller for first time use.
+        """
+        self.device.bts_lib.initBluetoothSys()
+
+    def get_local_bluetooth_address(self):
+        """ Returns the Bluetooth local address.
+        """
+        return self.device.bts_lib.getActiveAdapterAddress().get("result")
+
+    def set_bluetooth_local_name(self, name):
+        """ Sets the Bluetooth controller's local name
+        Args:
+            name: The name to set.
+        """
+        self.device.bts_lib.setName(name)
+
+    def gatt_client_write_characteristic_without_response_by_handle(
+            self, peer_identifier, handle, value):
+        """ Perform a GATT Client write Characteristic without response to
+        remote peer GATT server database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            handle: The characteristic handle.
+            value: The list of bytes to write.
+        Returns:
+            True if success, False if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, handle)):
+            self.log.warn(
+                "Unable to find handle {} in GATT server db.".format(handle))
+        result = self.device.gattc_lib.writeCharByIdWithoutResponse(
+            handle, value)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to write characteristic handle {} with err: {}".format(
+                    handle, result.get("error")))
+            return False
+        return True
+
+    def gatt_client_write_characteristic_by_handle(self, peer_identifier,
+                                                   handle, offset, value):
+        """ Perform a GATT Client write Characteristic to remote peer GATT
+        server database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            handle: The characteristic handle.
+            offset: The offset to start writing to.
+            value: The list of bytes to write.
+        Returns:
+            True if success, False if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, handle)):
+            self.log.warn(
+                "Unable to find handle {} in GATT server db.".format(handle))
+        result = self.device.gattc_lib.writeCharById(handle, offset, value)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to write characteristic handle {} with err: {}".format(
+                    handle, result.get("error")))
+            return False
+        return True
+
+    def gatt_client_write_long_characteristic_by_handle(
+            self, peer_identifier, handle, offset, value, reliable_mode=False):
+        """ Perform a GATT Client write long Characteristic to remote peer GATT
+        server database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            handle: The characteristic handle.
+            offset: The offset to start writing to.
+            value: The list of bytes to write.
+            reliable_mode: A bool value representing a reliable write or not.
+        Returns:
+            True if success, False if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, handle)):
+            self.log.error(
+                "Unable to find handle {} in GATT server db.".format(handle))
+            return False
+        result = self.device.gattc_lib.writeLongCharById(
+            handle, offset, value, reliable_mode)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to write long characteristic handle {} with err: {}".
+                format(peer_identifier, result.get("error")))
+            return False
+        return True
+
+    def gatt_client_write_long_descriptor_by_handle(self, peer_identifier,
+                                                    handle, offset, value):
+        """ Perform a GATT Client write long Descriptor to remote peer GATT
+        server database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            handle: The descriptor handle.
+            offset: The offset to start writing to.
+            value: The list of bytes to write.
+        Returns:
+            True if success, False if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, handle)):
+            self.log.error(
+                "Unable to find handle {} in GATT server db.".format(handle))
+            return False
+        result = self.device.gattc_lib.writeLongDescById(handle, offset, value)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to write long descriptor handle {} with err: {}".
+                format(peer_identifier, result.get("error")))
+            return False
+        return True
+
+    def gatt_client_read_characteristic_by_handle(self, peer_identifier,
+                                                  handle):
+        """ Perform a GATT Client read Characteristic to remote peer GATT
+        server database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            handle: The characteristic handle.
+        Returns:
+            Value of Characteristic if success, None if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, handle)):
+            self.log.warn(
+                "Unable to find handle {} in GATT server db.".format(handle))
+        result = self.device.gattc_lib.readCharacteristicById(handle)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to read characteristic handle {} with err: {}".format(
+                    handle, result.get("error")))
+            return None
+        return result.get("result")
+
+    def gatt_client_read_characteristic_by_uuid(self, peer_identifier, uuid):
+        """ Perform a GATT Client read Characteristic by uuid to remote peer GATT
+        server database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            uuid: The characteristic uuid.
+        Returns:
+            Value of Characteristic if success, None if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, uuid, uuid=True)):
+            self.log.warn(
+                "Unable to find uuid {} in GATT server db.".format(uuid))
+        result = self.device.gattc_lib.readCharacteristicByType(uuid)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to read characteristic uuid {} with err: {}".format(
+                    uuid, result.get("error")))
+            return None
+        return result.get("result")
+
+    def gatt_client_read_long_characteristic_by_handle(self, peer_identifier,
+                                                       handle, offset,
+                                                       max_bytes):
+        """ Perform a GATT Client read Characteristic to remote peer GATT
+        server database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            handle: The characteristic handle.
+            offset: The offset to start reading.
+            max_bytes: The max bytes to return for each read.
+        Returns:
+            Value of Characteristic if success, None if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, handle)):
+            self.log.warn(
+                "Unable to find handle {} in GATT server db.".format(handle))
+        result = self.device.gattc_lib.readLongCharacteristicById(
+            handle, offset, max_bytes)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to read characteristic handle {} with err: {}".format(
+                    handle, result.get("error")))
+            return None
+        return result.get("result")
+
+    def gatt_client_enable_notifiy_characteristic_by_handle(
+            self, peer_identifier, handle):
+        """ Perform a GATT Client enable Characteristic notification to remote
+        peer GATT server database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            handle: The characteristic handle.
+        Returns:
+            True is success, False if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, handle)):
+            self.log.warn(
+                "Unable to find handle {} in GATT server db.".format(handle))
+        result = self.device.gattc_lib.enableNotifyCharacteristic(handle)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to enable characteristic notifications for handle {} "
+                "with err: {}".format(handle, result.get("error")))
+            return None
+        return result.get("result")
+
+    def gatt_client_disable_notifiy_characteristic_by_handle(
+            self, peer_identifier, handle):
+        """ Perform a GATT Client disable Characteristic notification to remote
+        peer GATT server database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            handle: The characteristic handle.
+        Returns:
+            True is success, False if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, handle)):
+            self.log.warn(
+                "Unable to find handle {} in GATT server db.".format(handle))
+        result = self.device.gattc_lib.disableNotifyCharacteristic(handle)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to disable characteristic notifications for handle {} "
+                "with err: {}".format(peer_identifier, result.get("error")))
+            return None
+        return result.get("result")
+
+    def gatt_client_read_descriptor_by_handle(self, peer_identifier, handle):
+        """ Perform a GATT Client read Descriptor to remote peer GATT server
+        database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            handle: The Descriptor handle.
+        Returns:
+            Value of Descriptor if success, None if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, handle)):
+            self.log.warn(
+                "Unable to find handle {} in GATT server db.".format(handle))
+        result = self.device.gattc_lib.readDescriptorById(handle)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to read descriptor for handle {} with err: {}".format(
+                    peer_identifier, result.get("error")))
+            return None
+        return result.get("result")
+
+    def gatt_client_write_descriptor_by_handle(self, peer_identifier, handle,
+                                               offset, value):
+        """ Perform a GATT Client write Descriptor to remote peer GATT server
+        database.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            handle: The Descriptor handle.
+            offset: The offset to start writing at.
+            value: The list of bytes to write.
+        Returns:
+            True if success, False if failure.
+        """
+        if (not self._find_service_id_and_connect_to_service_for_handle(
+                peer_identifier, handle)):
+            self.log.warn(
+                "Unable to find handle {} in GATT server db.".format(handle))
+        result = self.device.gattc_lib.writeDescriptorById(
+            handle, offset, value)
+        if result.get("error") is not None:
+            self.log.error(
+                "Failed to write descriptor for handle {} with err: {}".format(
+                    peer_identifier, result.get("error")))
+            return None
+        return True
+
+    def gatt_connect(self, peer_identifier, transport, autoconnect):
+        """ Perform a GATT connection to a perihperal.
+
+        Args:
+            peer_identifier: The peer to connect to.
+            transport: Not implemented.
+            autoconnect: Not implemented.
+        Returns:
+            True if success, False if failure.
+        """
+        connection_result = self.device.gattc_lib.bleConnectToPeripheral(
+            peer_identifier)
+        if connection_result.get("error") is not None:
+            self.log.error("Failed to connect to peer id {}: {}".format(
+                peer_identifier, connection_result.get("error")))
+            return False
+        return True
+
+    def gatt_client_refresh(self, peer_identifier):
+        """ Perform a GATT Client Refresh of a perihperal.
+
+        Clears the internal cache and forces a refresh of the services from the
+        remote device. In Fuchsia there is no FIDL api to automatically do this
+        yet. Therefore just read all Characteristics which satisfies the same
+        requirements.
+
+        Args:
+            peer_identifier: The peer to refresh.
+        """
+        self._read_all_characteristics(peer_identifier)
+
+    def gatt_client_discover_characteristic_by_uuid(self, peer_identifier,
+                                                    uuid):
+        """ Perform a GATT Client Refresh of a perihperal.
+
+        Clears the internal cache and forces a refresh of the services from the
+        remote device. In Fuchsia there is no FIDL api to automatically do this
+        yet. Therefore just read all Characteristics which satisfies the same
+        requirements.
+
+        Args:
+            peer_identifier: The peer to refresh.
+        """
+        self._read_all_characteristics(peer_identifier, uuid)
+
+    def gatt_disconnect(self, peer_identifier):
+        """ Perform a GATT disconnect from a perihperal.
+
+        Args:
+            peer_identifier: The peer to disconnect from.
+        Returns:
+            True if success, False if failure.
+        """
+        disconnect_result = self.device.gattc_lib.bleDisconnectPeripheral(
+            peer_identifier)
+        if disconnect_result.get("error") is None:
+            self.log.error("Failed to disconnect from peer id {}: {}".format(
+                peer_identifier, disconnect_result.get("error")))
+            return False
+        return True
+
+    def reset_bluetooth(self):
+        """Stub for Fuchsia implementation."""
+        pass
+
+    def sdp_add_search(self, attribute_list, profile_id):
+        """Adds an SDP search record.
+        Args:
+            attribute_list: The list of attributes to set
+            profile_id: The profile ID to set.
+        """
+        return self.device.sdp_lib.addSearch(attribute_list, profile_id)
+
+    def sdp_add_service(self, sdp_record):
+        """Adds an SDP service record.
+        Args:
+            sdp_record: The dictionary representing the search record to add.
+        """
+        return self.device.sdp_lib.addService(sdp_record)
+
+    def sdp_clean_up(self):
+        """Cleans up all objects related to SDP.
+        """
+        return self.device.sdp_lib.cleanUp()
+
+    def sdp_init(self):
+        """Initializes SDP on the device.
+        """
+        return self.device.sdp_lib.init()
+
+    def sdp_remove_service(self, service_id):
+        """Removes a service based on an input id.
+        Args:
+            service_id: The service ID to remove.
+        """
+        return self.device.sdp_lib.init()
+
+    def start_le_advertisement(self, adv_data, scan_response, adv_interval,
+                               connectable):
+        """ Starts an LE advertisement
+
+        Args:
+            adv_data: Advertisement data.
+            adv_interval: Advertisement interval.
+        """
+        self.device.ble_lib.bleStartBleAdvertising(adv_data, scan_response,
+                                                   adv_interval, connectable)
+
+    def stop_le_advertisement(self):
+        """ Stop active LE advertisement.
+        """
+        self.device.ble_lib.bleStopBleAdvertising()
+
+    def setup_gatt_server(self, database):
+        """ Sets up an input GATT server.
+
+        Args:
+            database: A dictionary representing the GATT database to setup.
+        """
+        self.device.gatts_lib.publishServer(database)
+
+    def close_gatt_server(self):
+        """ Closes an existing GATT server.
+        """
+        self.device.gatts_lib.closeServer()
+
+    def le_scan_with_name_filter(self, name, timeout):
+        """ Scan over LE for a specific device name.
+
+        Args:
+            name: The name filter to set.
+            timeout: The timeout to wait to find the advertisement.
+        Returns:
+            Discovered device id or None
+        """
+        partial_match = True
+        return le_scan_for_device_by_name(self.device, self.device.log, name,
+                                          timeout, partial_match)
+
+    def log_info(self, log):
+        """ Log directly onto the device.
+
+        Args:
+            log: The informative log.
+        """
+        self.device.logging_lib.logI(log)
+        pass
+
+    def unbond_all_known_devices(self):
+        """ Unbond all known remote devices.
+        """
+        try:
+            device_list = self.device.bts_lib.getKnownRemoteDevices()['result']
+            for device_info in device_list:
+                device = device_list[device_info]
+                if device['bonded']:
+                    self.device.bts_lib.forgetDevice(device['id'])
+        except Exception as err:
+            self.log.err("Unable to unbond all devices: {}".format(err))
+
+    def unbond_device(self, peer_identifier):
+        """ Unbond peer identifier.
+
+        Args:
+            peer_identifier: The peer identifier for the peer to unbond.
+
+        """
+        self.device.bts_lib.forgetDevice(peer_identifier)
+
+    def _find_service_id_and_connect_to_service_for_handle(
+            self, peer_identifier, handle, uuid=False):
+        fail_err = "Failed to find handle {} in Peer database."
+        if uuid:
+            handle = handle.lower()
+        try:
+            services = self.device.gattc_lib.listServices(peer_identifier)
+            for service in services['result']:
+                service_id = service['id']
+                self.device.gattc_lib.connectToService(peer_identifier,
+                                                       service_id)
+                chars = self.device.gattc_lib.discoverCharacteristics()
+
+                for char in chars['result']:
+                    char_id = char['id']
+                    if uuid:
+                        char_id = char['uuid_type']
+                    if handle == char_id:
+                        return True
+                    descriptors = char['descriptors']
+                    for desc in descriptors:
+                        desc_id = desc["id"]
+                        if uuid:
+                            desc_id = desc['uuid_type']
+                        if handle == desc_id:
+                            return True
+        except Exception as err:
+            self.log.error(fail_err.format(err))
+            return False
+
+    def _read_all_characteristics(self, peer_identifier, uuid=None):
+        fail_err = "Failed to read all characteristics with: {}"
+        try:
+            services = self.device.gattc_lib.listServices(peer_identifier)
+            for service in services['result']:
+                service_id = service['id']
+                service_uuid = service['uuid_type']
+                self.device.gattc_lib.connectToService(peer_identifier,
+                                                       service_id)
+                chars = self.device.gattc_lib.discoverCharacteristics()
+                self.log.info(
+                    "Reading chars in service uuid: {}".format(service_uuid))
+
+                for char in chars['result']:
+                    char_id = char['id']
+                    char_uuid = char['uuid_type']
+                    if uuid and uuid.lower() not in char_uuid.lower():
+                        continue
+                    try:
+                        read_val =  \
+                            self.device.gattc_lib.readCharacteristicById(
+                                char_id)
+                        self.log.info(
+                            "\tCharacteristic uuid / Value: {} / {}".format(
+                                char_uuid, read_val['result']))
+                        str_value = ""
+                        for val in read_val['result']:
+                            str_value += chr(val)
+                        self.log.info("\t\tstr val: {}".format(str_value))
+                    except Exception as err:
+                        self.log.error(err)
+                        pass
+        except Exception as err:
+            self.log.error(fail_err.forma(err))
+
+    def _perform_read_all_descriptors(self, peer_identifier):
+        fail_err = "Failed to read all characteristics with: {}"
+        try:
+            services = self.device.gattc_lib.listServices(peer_identifier)
+            for service in services['result']:
+                service_id = service['id']
+                service_uuid = service['uuid_type']
+                self.device.gattc_lib.connectToService(peer_identifier,
+                                                       service_id)
+                chars = self.device.gattc_lib.discoverCharacteristics()
+                self.log.info(
+                    "Reading descs in service uuid: {}".format(service_uuid))
+
+                for char in chars['result']:
+                    char_id = char['id']
+                    char_uuid = char['uuid_type']
+                    descriptors = char['descriptors']
+                    self.log.info(
+                        "\tReading descs in char uuid: {}".format(char_uuid))
+                    for desc in descriptors:
+                        desc_id = desc["id"]
+                        desc_uuid = desc["uuid_type"]
+                    try:
+                        read_val = self.device.gattc_lib.readDescriptorById(
+                            desc_id)
+                        self.log.info(
+                            "\t\tDescriptor uuid / Value: {} / {}".format(
+                                desc_uuid, read_val['result']))
+                    except Exception as err:
+                        pass
+        except Exception as err:
+            self.log.error(fail_err.format(err))
+
+    def init_pair(self, peer_identifier, security_level, non_bondable,
+                  transport):
+        """ Send an outgoing pairing request the input peer_identifier.
+
+        Android currently does not support setting various security levels or
+        bondable modes. Making them available for other bluetooth_device
+        variants. Depending on the Address type, Android will figure out the
+        transport to pair automatically.
+
+        Args:
+            peer_identifier: A string representing the device id.
+            security_level: The security level required for this pairing request
+                represented as a u64. (Only for LE pairing)
+                Available Values
+                1 - ENCRYPTED: Encrypted without MITM protection
+                    (unauthenticated)
+                2 - AUTHENTICATED: Encrypted with MITM protection
+                    (authenticated)
+                None: No pairing security level.
+            non_bondable: A bool representing whether the pairing mode is
+                bondable or not. None is also accepted. False if bondable, True
+                if non-bondable
+            transport: A u64 representing the transport type.
+                Available Values
+                1 - BREDR: Classic BR/EDR transport
+                2 - LE: Bluetooth Low Energy Transport
+        Returns:
+            True if successful, False if failed.
+        """
+        try:
+            self.device.bts_lib.pair(peer_identifier, security_level,
+                                     non_bondable, transport)
+            return True
+        except Exception as err:
+            fail_err = "Failed to pair to peer_identifier {} with: {}".format(
+                peer_identifier)
+            self.log.error(fail_err.format(err))
diff --git a/acts_tests/acts_contrib/test_utils/abstract_devices/bluetooth_handsfree_abstract_device.py b/acts_tests/acts_contrib/test_utils/abstract_devices/bluetooth_handsfree_abstract_device.py
new file mode 100644
index 0000000..00e0c4a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/bluetooth_handsfree_abstract_device.py
@@ -0,0 +1,340 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+import inspect
+import time
+from acts import asserts
+from acts.controllers.buds_lib.dev_utils import apollo_sink_events
+from acts_contrib.test_utils.bt.bt_constants import bt_default_timeout
+
+
+
+def validate_controller(controller, abstract_device_class):
+    """Ensure controller has all methods in abstract_device_class.
+    Also checks method signatures to ensure parameters are satisfied.
+
+    Args:
+        controller: instance of a device controller.
+        abstract_device_class: class definition of an abstract_device interface.
+    Raises:
+         NotImplementedError: if controller is missing one or more methods.
+    """
+    ctlr_methods = inspect.getmembers(controller, predicate=callable)
+    reqd_methods = inspect.getmembers(
+        abstract_device_class, predicate=inspect.ismethod)
+    expected_func_names = {method[0] for method in reqd_methods}
+    controller_func_names = {method[0] for method in ctlr_methods}
+
+    if not controller_func_names.issuperset(expected_func_names):
+        raise NotImplementedError(
+            'Controller {} is missing the following functions: {}'.format(
+                controller.__class__.__name__,
+                repr(expected_func_names - controller_func_names)))
+
+    for func_name in expected_func_names:
+        controller_func = getattr(controller, func_name)
+        required_func = getattr(abstract_device_class, func_name)
+        required_signature = inspect.signature(required_func)
+        if inspect.signature(controller_func) != required_signature:
+            raise NotImplementedError(
+                'Method {} must have the signature {}{}.'.format(
+                    controller_func.__qualname__, controller_func.__name__,
+                    required_signature))
+
+
+class BluetoothHandsfreeAbstractDevice:
+    """Base class for all Bluetooth handsfree abstract devices.
+
+    Desired controller classes should have a corresponding Bluetooth handsfree
+    abstract device class defined in this module.
+    """
+
+    @property
+    def mac_address(self):
+        raise NotImplementedError
+
+    def accept_call(self):
+        raise NotImplementedError()
+
+    def end_call(self):
+        raise NotImplementedError()
+
+    def enter_pairing_mode(self):
+        raise NotImplementedError()
+
+    def next_track(self):
+        raise NotImplementedError()
+
+    def pause(self):
+        raise NotImplementedError()
+
+    def play(self):
+        raise NotImplementedError()
+
+    def power_off(self):
+        raise NotImplementedError()
+
+    def power_on(self):
+        raise NotImplementedError()
+
+    def previous_track(self):
+        raise NotImplementedError()
+
+    def reject_call(self):
+        raise NotImplementedError()
+
+    def volume_down(self):
+        raise NotImplementedError()
+
+    def volume_up(self):
+        raise NotImplementedError()
+
+
+class PixelBudsBluetoothHandsfreeAbstractDevice(
+        BluetoothHandsfreeAbstractDevice):
+
+    CMD_EVENT = 'EvtHex'
+
+    def __init__(self, pixel_buds_controller):
+        self.pixel_buds_controller = pixel_buds_controller
+
+    def format_cmd(self, cmd_name):
+        return self.CMD_EVENT + ' ' + apollo_sink_events.SINK_EVENTS[cmd_name]
+
+    @property
+    def mac_address(self):
+        return self.pixel_buds_controller.bluetooth_address
+
+    def accept_call(self):
+        return self.pixel_buds_controller.cmd(
+            self.format_cmd('EventUsrAnswer'))
+
+    def end_call(self):
+        return self.pixel_buds_controller.cmd(
+            self.format_cmd('EventUsrCancelEnd'))
+
+    def enter_pairing_mode(self):
+        return self.pixel_buds_controller.set_pairing_mode()
+
+    def next_track(self):
+        return self.pixel_buds_controller.cmd(
+            self.format_cmd('EventUsrAvrcpSkipForward'))
+
+    def pause(self):
+        return self.pixel_buds_controller.cmd(
+            self.format_cmd('EventUsrAvrcpPause'))
+
+    def play(self):
+        return self.pixel_buds_controller.cmd(
+            self.format_cmd('EventUsrAvrcpPlay'))
+
+    def power_off(self):
+        return self.pixel_buds_controller.power('Off')
+
+    def power_on(self):
+        return self.pixel_buds_controller.power('On')
+
+    def previous_track(self):
+        return self.pixel_buds_controller.cmd(
+            self.format_cmd('EventUsrAvrcpSkipBackward'))
+
+    def reject_call(self):
+        return self.pixel_buds_controller.cmd(
+            self.format_cmd('EventUsrReject'))
+
+    def volume_down(self):
+        return self.pixel_buds_controller.volume('Down')
+
+    def volume_up(self):
+        return self.pixel_buds_controller.volume('Up')
+
+
+class EarstudioReceiverBluetoothHandsfreeAbstractDevice(
+        BluetoothHandsfreeAbstractDevice):
+    def __init__(self, earstudio_controller):
+        self.earstudio_controller = earstudio_controller
+
+    @property
+    def mac_address(self):
+        return self.earstudio_controller.mac_address
+
+    def accept_call(self):
+        return self.earstudio_controller.press_accept_call()
+
+    def end_call(self):
+        return self.earstudio_controller.press_end_call()
+
+    def enter_pairing_mode(self):
+        return self.earstudio_controller.enter_pairing_mode()
+
+    def next_track(self):
+        return self.earstudio_controller.press_next()
+
+    def pause(self):
+        return self.earstudio_controller.press_play_pause()
+
+    def play(self):
+        return self.earstudio_controller.press_play_pause()
+
+    def power_off(self):
+        return self.earstudio_controller.power_off()
+
+    def power_on(self):
+        return self.earstudio_controller.power_on()
+
+    def previous_track(self):
+        return self.earstudio_controller.press_previous()
+
+    def reject_call(self):
+        return self.earstudio_controller.press_reject_call()
+
+    def volume_down(self):
+        return self.earstudio_controller.press_volume_down()
+
+    def volume_up(self):
+        return self.earstudio_controller.press_volume_up()
+
+
+class JaybirdX3EarbudsBluetoothHandsfreeAbstractDevice(
+        BluetoothHandsfreeAbstractDevice):
+    def __init__(self, jaybird_controller):
+        self.jaybird_controller = jaybird_controller
+
+    @property
+    def mac_address(self):
+        return self.jaybird_controller.mac_address
+
+    def accept_call(self):
+        return self.jaybird_controller.press_accept_call()
+
+    def end_call(self):
+        return self.jaybird_controller.press_reject_call()
+
+    def enter_pairing_mode(self):
+        return self.jaybird_controller.enter_pairing_mode()
+
+    def next_track(self):
+        return self.jaybird_controller.press_next()
+
+    def pause(self):
+        return self.jaybird_controller.press_play_pause()
+
+    def play(self):
+        return self.jaybird_controller.press_play_pause()
+
+    def power_off(self):
+        return self.jaybird_controller.power_off()
+
+    def power_on(self):
+        return self.jaybird_controller.power_on()
+
+    def previous_track(self):
+        return self.jaybird_controller.press_previous()
+
+    def reject_call(self):
+        return self.jaybird_controller.press_reject_call()
+
+    def volume_down(self):
+        return self.jaybird_controller.press_volume_down()
+
+    def volume_up(self):
+        return self.jaybird_controller.press_volume_up()
+
+
+class AndroidHeadsetBluetoothHandsfreeAbstractDevice(
+        BluetoothHandsfreeAbstractDevice):
+    def __init__(self, ad_controller):
+        self.ad_controller = ad_controller
+
+    @property
+    def mac_address(self):
+        """Getting device mac with more stability ensurance.
+
+        Sometime, getting mac address is flaky that it returns None. Adding a
+        loop to add more ensurance of getting correct mac address.
+        """
+        device_mac = None
+        start_time = time.time()
+        end_time = start_time + bt_default_timeout
+        while not device_mac and time.time() < end_time:
+            device_mac = self.ad_controller.droid.bluetoothGetLocalAddress()
+        asserts.assert_true(device_mac, 'Can not get the MAC address')
+        return device_mac
+
+    def accept_call(self):
+        return self.ad_controller.droid.telecomAcceptRingingCall(None)
+
+    def end_call(self):
+        return self.ad_controller.droid.telecomEndCall()
+
+    def enter_pairing_mode(self):
+        self.ad_controller.droid.bluetoothStartPairingHelper(True)
+        return self.ad_controller.droid.bluetoothMakeDiscoverable()
+
+    def next_track(self):
+        return (self.ad_controller.droid.bluetoothMediaPassthrough("skipNext"))
+
+    def pause(self):
+        return self.ad_controller.droid.bluetoothMediaPassthrough("pause")
+
+    def play(self):
+        return self.ad_controller.droid.bluetoothMediaPassthrough("play")
+
+    def power_off(self):
+        return self.ad_controller.droid.bluetoothToggleState(False)
+
+    def power_on(self):
+        return self.ad_controller.droid.bluetoothToggleState(True)
+
+    def previous_track(self):
+        return (self.ad_controller.droid.bluetoothMediaPassthrough("skipPrev"))
+
+    def reject_call(self):
+        return self.ad_controller.droid.telecomCallDisconnect(
+            self.ad_controller.droid.telecomCallGetCallIds()[0])
+
+    def reset(self):
+        return self.ad_controller.droid.bluetoothFactoryReset()
+
+    def volume_down(self):
+        target_step = self.ad_controller.droid.getMediaVolume() - 1
+        target_step = max(target_step, 0)
+        return self.ad_controller.droid.setMediaVolume(target_step)
+
+    def volume_up(self):
+        target_step = self.ad_controller.droid.getMediaVolume() + 1
+        max_step = self.ad_controller.droid.getMaxMediaVolume()
+        target_step = min(target_step, max_step)
+        return self.ad_controller.droid.setMediaVolume(target_step)
+
+
+class BluetoothHandsfreeAbstractDeviceFactory:
+    """Generates a BluetoothHandsfreeAbstractDevice for any device controller.
+    """
+
+    _controller_abstract_devices = {
+        'EarstudioReceiver': EarstudioReceiverBluetoothHandsfreeAbstractDevice,
+        'JaybirdX3Earbuds': JaybirdX3EarbudsBluetoothHandsfreeAbstractDevice,
+        'ParentDevice': PixelBudsBluetoothHandsfreeAbstractDevice,
+        'AndroidDevice': AndroidHeadsetBluetoothHandsfreeAbstractDevice
+    }
+
+    def generate(self, controller):
+        class_name = controller.__class__.__name__
+        if class_name in self._controller_abstract_devices:
+            return self._controller_abstract_devices[class_name](controller)
+        else:
+            validate_controller(controller, BluetoothHandsfreeAbstractDevice)
+            return controller
diff --git a/acts_tests/acts_contrib/test_utils/abstract_devices/utils_lib/wlan_policy_utils.py b/acts_tests/acts_contrib/test_utils/abstract_devices/utils_lib/wlan_policy_utils.py
new file mode 100644
index 0000000..ef89d95
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/utils_lib/wlan_policy_utils.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+SAVED_NETWORKS = "saved_networks"
+CLIENT_STATE = "client_connections_state"
+CONNECTIONS_ENABLED = "ConnectionsEnabled"
+CONNECTIONS_DISABLED = "ConnectionsDisabled"
+
+
+def setup_policy_tests(fuchsia_devices):
+    """ Preserves networks already saved on devices before removing them to
+        setup up for a clean test environment. Records the state of client
+        connections before tests. Initializes the client controller
+        and enables connections.
+    Args:
+        fuchsia_devices: the devices under test
+    Returns:
+        A dict of the data to restore after tests indexed by device. The data
+        for each device is a dict of the saved data, ie saved networks and
+        state of client connections.
+    """
+    preserved_data_by_device = {}
+    for fd in fuchsia_devices:
+        data = {}
+        # Collect and delete networks saved on the device.
+        fd.wlan_policy_lib.wlanCreateClientController()
+        result_get = fd.wlan_policy_lib.wlanGetSavedNetworks()
+        if result_get.get("result") != None:
+            data[SAVED_NETWORKS] = result_get['result']
+        fd.wlan_policy_lib.wlanRemoveAllNetworks()
+
+        # Get the currect client connection state (connections enabled or disabled)
+        # and enable connections by default.
+        fd.wlan_policy_lib.wlanSetNewListener()
+        result_update = fd.wlan_policy_lib.wlanGetUpdate()
+        if result_update.get("result") != None and result_update.get(
+                "result").get("state") != None:
+            data[CLIENT_STATE] = result_update.get("result").get("state")
+        else:
+            fd.log.warn("Failed to get update; test will not start or "
+                        "stop client connections at the end of the test.")
+        fd.wlan_policy_lib.wlanStartClientConnections()
+
+        preserved_data_by_device[fd] = data
+    return preserved_data_by_device
+
+
+def restore_state(fuchsia_devices, preserved_data):
+    """ Restore the state of the test device to what it was before tests began.
+        Remove any remaining saved networks, and restore the saved networks and
+        client connections state recorded by setup_policy_tests
+    Args:
+        fuchsia_devices: The fuchsia devices under test
+        preserved data: Dict of data indexed by fuchsia device, as returned
+                        by setup_policy_tests
+    """
+    for fd in fuchsia_devices:
+        data = preserved_data[fd]
+        fd.wlan_policy_lib.wlanRemoveAllNetworks()
+        for network in data[SAVED_NETWORKS]:
+            save_network(fd, network["ssid"], network["security_type"],
+                         network["credential_value"])
+        for starting_state in data[CLIENT_STATE]:
+            if starting_state == CONNECTIONS_ENABLED:
+                fd.wlan_policy_lib.wlanStartClientConnections()
+            elif starting_state == CONNECTIONS_DISABLED:
+                fd.wlan_policy_lib.wlanStopClientConnections()
+
+
+def save_network(fd, ssid, security_type, password=""):
+    """ Saves a network as specified on the given device and verify that the operation succeeded.
+        Returns true if there was no error, and false otherwise
+    Args:
+        fd: The Fuchsia device to save the network on
+        ssid: The SSID or name of the network to save.
+        security_type: The security type to save the network as, ie "none",
+                    "wep", "wpa", "wpa2", or "wpa3"
+        password: The password to save for the network. Empty string represents
+                no password, and PSK should be provided as 64 character hex string.
+    """
+    result_save = fd.wlan_policy_lib.wlanSaveNetwork(ssid, security_type,
+                                                     password)
+    if result_save.get("error") != None:
+        fd.log.info("Failed to save network %s with error: %s" %
+                    (ssid, result_save["error"]))
+        return False
+    else:
+        return True
+
+
+def start_connections(fd):
+    """ Starts client connections on the specified device and verifies that it
+        succeeds, and raises a test failure if not.
+    Returns:
+        True if there are no errors, False if there are errors.
+    """
+    resultStart = fd.wlan_policy_lib.wlanStartClientConnections()
+    if resultStart.get("error") != None:
+        fd.log.error(
+            "Error occurred when starting client connections in test setup: %s"
+            % resultStart.get("error"))
+        return False
+    else:
+        return True
+
+
+def stop_connections(fd):
+    """ Stops client connections on the device and verify that there are no
+        errors are returned, and raises a test failure if there are.
+    Returns:
+        True if there are noe errors, False otherwise.
+    """
+    result_stop = fd.wlan_policy_lib.wlanStopClientConnections()
+    if result_stop.get("error") != None:
+        fd.log.error("Error occurred stopping client connections: %s" %
+                     result_stop.get("error"))
+        return False
+    else:
+        return True
+
+
+def reboot_device(fd):
+    """ Reboot the device and reinitialize the device after.
+    Args:
+        fd: The device to reboot.
+    """
+    fd.reboot()
+    fd.wlan_policy_lib.wlanCreateClientController()
+    fd.wlan_policy_lib.wlanStartClientConnections()
+    fd.wlan_policy_lib.wlanSetNewListener()
diff --git a/acts_tests/acts_contrib/test_utils/abstract_devices/utils_lib/wlan_utils.py b/acts_tests/acts_contrib/test_utils/abstract_devices/utils_lib/wlan_utils.py
new file mode 100644
index 0000000..bebcb1e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/utils_lib/wlan_utils.py
@@ -0,0 +1,219 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+
+from acts import asserts
+from acts.controllers.ap_lib import hostapd_ap_preset
+
+
+def validate_setup_ap_and_associate(*args, **kwargs):
+    """Validates if setup_ap_and_associate was a success or not
+
+       Args: Args match setup_ap_and_associate
+    """
+    asserts.assert_true(setup_ap_and_associate(*args, **kwargs),
+                        'Failed to associate.')
+    asserts.explicit_pass('Successfully associated.')
+
+
+def setup_ap_and_associate(access_point,
+                           client,
+                           profile_name,
+                           channel,
+                           ssid,
+                           mode=None,
+                           preamble=None,
+                           beacon_interval=None,
+                           dtim_period=None,
+                           frag_threshold=None,
+                           rts_threshold=None,
+                           force_wmm=None,
+                           hidden=False,
+                           security=None,
+                           additional_ap_parameters=None,
+                           password=None,
+                           check_connectivity=False,
+                           n_capabilities=None,
+                           ac_capabilities=None,
+                           vht_bandwidth=None,
+                           setup_bridge=False,
+                           target_security=None,
+                           association_mechanism=None):
+    """Sets up the AP and associates a client.
+
+    Args:
+        access_point: An ACTS access_point controller
+        client: A WlanDevice.
+        profile_name: The profile name of one of the hostapd ap presets.
+        channel: What channel to set the AP to.
+        preamble: Whether to set short or long preamble (True or False)
+        beacon_interval: The beacon interval (int)
+        dtim_period: Length of dtim period (int)
+        frag_threshold: Fragmentation threshold (int)
+        rts_threshold: RTS threshold (int)
+        force_wmm: Enable WMM or not (True or False)
+        hidden: Advertise the SSID or not (True or False)
+        security: What security to enable.
+        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.
+        target_security: The security to try to associate to if using policy
+                         to associate.
+        association_mechanism: The way we will connect, through the core or
+                               policy layer of WLAN on the device.
+    """
+    setup_ap(access_point, profile_name, channel, ssid, mode, preamble,
+             beacon_interval, dtim_period, frag_threshold, rts_threshold,
+             force_wmm, hidden, security, additional_ap_parameters, password,
+             check_connectivity, n_capabilities, ac_capabilities,
+             vht_bandwidth, setup_bridge)
+
+    if not security:
+        target_security = "none"
+
+    if security and security.wpa3:
+        return associate(client,
+                         ssid,
+                         password,
+                         target_security=target_security,
+                         key_mgmt='SAE',
+                         check_connectivity=check_connectivity,
+                         hidden=hidden,
+                         association_mechanism=association_mechanism)
+    else:
+        return associate(client,
+                         ssid,
+                         password=password,
+                         target_security=target_security,
+                         check_connectivity=check_connectivity,
+                         hidden=hidden,
+                         association_mechanism=association_mechanism)
+
+
+def setup_ap(access_point,
+             profile_name,
+             channel,
+             ssid,
+             mode=None,
+             preamble=None,
+             beacon_interval=None,
+             dtim_period=None,
+             frag_threshold=None,
+             rts_threshold=None,
+             force_wmm=None,
+             hidden=False,
+             security=None,
+             additional_ap_parameters=None,
+             password=None,
+             check_connectivity=False,
+             n_capabilities=None,
+             ac_capabilities=None,
+             vht_bandwidth=None,
+             setup_bridge=False):
+    """Sets up the AP.
+
+    Args:
+        access_point: An ACTS access_point controller
+        profile_name: The profile name of one of the hostapd ap presets.
+        channel: What channel to set the AP to.
+        preamble: Whether to set short or long preamble (True or False)
+        beacon_interval: The beacon interval (int)
+        dtim_period: Length of dtim period (int)
+        frag_threshold: Fragmentation threshold (int)
+        rts_threshold: RTS threshold (int)
+        force_wmm: Enable WMM or not (True or False)
+        hidden: Advertise the SSID or not (True or False)
+        security: What security to enable.
+        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.
+    """
+    ap = hostapd_ap_preset.create_ap_preset(profile_name=profile_name,
+                                            iface_wlan_2g=access_point.wlan_2g,
+                                            iface_wlan_5g=access_point.wlan_5g,
+                                            channel=channel,
+                                            ssid=ssid,
+                                            mode=mode,
+                                            short_preamble=preamble,
+                                            beacon_interval=beacon_interval,
+                                            dtim_period=dtim_period,
+                                            frag_threshold=frag_threshold,
+                                            rts_threshold=rts_threshold,
+                                            force_wmm=force_wmm,
+                                            hidden=hidden,
+                                            bss_settings=[],
+                                            security=security,
+                                            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)
+
+
+def associate(client,
+              ssid,
+              password=None,
+              key_mgmt=None,
+              check_connectivity=True,
+              hidden=False,
+              security=None,
+              association_mechanism=None,
+              target_security=None):
+    """Associates a client to a WLAN network.
+
+    Args:
+        client: A WlanDevice
+        ssid: SSID of the ap we are looking for.
+        password: The password for the WLAN, if applicable.
+        key_mgmt: The hostapd wpa_key_mgmt value.
+        check_connectivity: Whether to check internet connectivity.
+        hidden: If the WLAN is hidden or not.
+    """
+    return client.associate(ssid,
+                            password,
+                            key_mgmt=key_mgmt,
+                            check_connectivity=check_connectivity,
+                            hidden=hidden,
+                            association_mechanism=association_mechanism,
+                            target_security=target_security)
+
+
+def status(client):
+    """Requests the state of WLAN network.
+
+    Args:
+        None
+    """
+    status = ''
+    status_response = client.status()
+
+    if status_response.get('error') is None:
+        # No error, so get the result
+        status = status_response['result']
+
+    logging.debug('status: %s' % status)
+    return status
+
+
+def disconnect(client):
+    """Disconnect client from its WLAN network.
+
+    Args:
+        client: A WlanDevice
+    """
+    client.disconnect()
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
new file mode 100644
index 0000000..f1f1b72
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device.py
@@ -0,0 +1,482 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import inspect
+import logging
+
+import acts_contrib.test_utils.wifi.wifi_test_utils as awutils
+import acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils as fwutils
+from acts.utils import get_interface_ip_addresses
+from acts.utils import adb_shell_ping
+
+from acts import asserts
+from acts.controllers.fuchsia_device import FuchsiaDevice
+from acts.controllers.android_device import AndroidDevice
+
+
+def create_wlan_device(hardware_device):
+    """Creates a generic WLAN device based on type of device that is sent to
+    the functions.
+
+    Args:
+        hardware_device: A WLAN hardware device that is supported by ACTS.
+    """
+    if isinstance(hardware_device, FuchsiaDevice):
+        return FuchsiaWlanDevice(hardware_device)
+    elif isinstance(hardware_device, AndroidDevice):
+        return AndroidWlanDevice(hardware_device)
+    else:
+        raise ValueError('Unable to create WlanDevice for type %s' %
+                         type(hardware_device))
+
+
+FUCHSIA_VALID_SECURITY_TYPES = {"none", "wep", "wpa", "wpa2", "wpa3"}
+
+
+class WlanDevice(object):
+    """Class representing a generic WLAN device.
+
+    Each object of this class represents a generic WLAN device.
+    Android device and Fuchsia devices are the currently supported devices/
+
+    Attributes:
+        device: A generic WLAN device.
+    """
+    def __init__(self, device):
+        self.device = device
+        self.log = logging
+        self.identifier = None
+
+    def wifi_toggle_state(self, state):
+        """Base generic WLAN interface.  Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def reset_wifi(self):
+        """Base generic WLAN interface.  Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def take_bug_report(self, test_name, begin_time):
+        """Base generic WLAN interface.  Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def get_log(self, test_name, begin_time):
+        """Base generic WLAN interface.  Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def turn_location_off_and_scan_toggle_off(self):
+        """Base generic WLAN interface.  Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def associate(self,
+                  target_ssid,
+                  target_pwd=None,
+                  check_connectivity=True,
+                  hidden=False,
+                  association_mechanism=None,
+                  target_security=None):
+        """Base generic WLAN interface.  Only called if not overriden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def disconnect(self, association_mechanism=None):
+        """Base generic WLAN interface.  Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def get_wlan_interface_id_list(self):
+        """Base generic WLAN interface.  Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def destroy_wlan_interface(self, iface_id):
+        """Base generic WLAN interface.  Only called if not overridden by
+        another supported device.
+        """
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def send_command(self, command):
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def get_interface_ip_addresses(self, interface):
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def is_connected(self, ssid=None):
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def can_ping(self,
+                 dest_ip,
+                 count=3,
+                 interval=1000,
+                 timeout=1000,
+                 size=25,
+                 additional_ping_params=None):
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def ping(self,
+             dest_ip,
+             count=3,
+             interval=1000,
+             timeout=1000,
+             size=25,
+             additional_ping_params=None):
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def hard_power_cycle(self, pdus=None):
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def save_network(self, ssid):
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+    def clear_saved_networks(self):
+        raise NotImplementedError("{} must be defined.".format(
+            inspect.currentframe().f_code.co_name))
+
+
+class AndroidWlanDevice(WlanDevice):
+    """Class wrapper for an Android WLAN device.
+
+    Each object of this class represents a generic WLAN device.
+    Android device and Fuchsia devices are the currently supported devices/
+
+    Attributes:
+        android_device: An Android WLAN device.
+    """
+    def __init__(self, android_device):
+        super().__init__(android_device)
+        self.identifier = android_device.serial
+
+    def wifi_toggle_state(self, state):
+        awutils.wifi_toggle_state(self.device, state)
+
+    def reset_wifi(self):
+        awutils.reset_wifi(self.device)
+
+    def take_bug_report(self, test_name, begin_time):
+        self.device.take_bug_report(test_name, begin_time)
+
+    def get_log(self, test_name, begin_time):
+        self.device.cat_adb_log(test_name, begin_time)
+
+    def turn_location_off_and_scan_toggle_off(self):
+        awutils.turn_location_off_and_scan_toggle_off(self.device)
+
+    def associate(self,
+                  target_ssid,
+                  target_pwd=None,
+                  key_mgmt=None,
+                  check_connectivity=True,
+                  hidden=False,
+                  association_mechanism=None,
+                  target_security=None):
+        """Function to associate an Android WLAN device.
+
+        Args:
+            target_ssid: SSID to associate to.
+            target_pwd: Password for the SSID, if necessary.
+            key_mgmt: The hostapd wpa_key_mgmt value, distinguishes wpa3 from
+                wpa2 for android tests.
+            check_connectivity: Whether to check for internet connectivity.
+            hidden: Whether the network is hidden.
+        Returns:
+            True if successfully connected to WLAN, False if not.
+        """
+        network = {'SSID': target_ssid, 'hiddenSSID': hidden}
+        if target_pwd:
+            network['password'] = target_pwd
+        if key_mgmt:
+            network['security'] = key_mgmt
+        try:
+            awutils.connect_to_wifi_network(
+                self.device,
+                network,
+                check_connectivity=check_connectivity,
+                hidden=hidden)
+            return True
+        except Exception as e:
+            self.device.log.info('Failed to associated (%s)' % e)
+            return False
+
+    def disconnect(self, association_mechanism=None):
+        awutils.turn_location_off_and_scan_toggle_off(self.device)
+
+    def get_wlan_interface_id_list(self):
+        pass
+
+    def destroy_wlan_interface(self, iface_id):
+        pass
+
+    def send_command(self, command):
+        return self.device.adb.shell(str(command))
+
+    def get_interface_ip_addresses(self, interface):
+        return get_interface_ip_addresses(self.device, interface)
+
+    def is_connected(self, ssid=None):
+        wifi_info = self.device.droid.wifiGetConnectionInfo()
+        if ssid:
+            return 'BSSID' in wifi_info and wifi_info['SSID'] == ssid
+        return 'BSSID' in wifi_info
+
+    def can_ping(self,
+                 dest_ip,
+                 count=3,
+                 interval=1000,
+                 timeout=1000,
+                 size=25,
+                 additional_ping_params=None):
+        return adb_shell_ping(self.device,
+                              dest_ip=dest_ip,
+                              count=count,
+                              timeout=timeout)
+
+    def ping(self, dest_ip, count=3, interval=1000, timeout=1000, size=25):
+        pass
+
+    def hard_power_cycle(self, pdus):
+        pass
+
+    def save_network(self, ssid):
+        pass
+
+    def clear_saved_networks(self):
+        pass
+
+
+class FuchsiaWlanDevice(WlanDevice):
+    """Class wrapper for an Fuchsia WLAN device.
+
+    Each object of this class represents a generic WLAN device.
+    Android device and Fuchsia devices are the currently supported devices/
+
+    Attributes:
+        fuchsia_device: A Fuchsia WLAN device.
+    """
+    def __init__(self, fuchsia_device):
+        super().__init__(fuchsia_device)
+        self.identifier = fuchsia_device.ip
+
+    def wifi_toggle_state(self, state):
+        """Stub for Fuchsia implementation."""
+        pass
+
+    def reset_wifi(self):
+        """Stub for Fuchsia implementation."""
+        pass
+
+    def take_bug_report(self, test_name, begin_time):
+        """Stub for Fuchsia implementation."""
+        pass
+
+    def get_log(self, test_name, begin_time):
+        """Stub for Fuchsia implementation."""
+        pass
+
+    def turn_location_off_and_scan_toggle_off(self):
+        """Stub for Fuchsia implementation."""
+        pass
+
+    def associate(self,
+                  target_ssid,
+                  target_pwd=None,
+                  key_mgmt=None,
+                  check_connectivity=True,
+                  hidden=False,
+                  association_mechanism=None,
+                  target_security=None):
+        """Function to associate a Fuchsia WLAN device.
+
+        Args:
+            target_ssid: SSID to associate to.
+            target_pwd: Password for the SSID, if necessary.
+            key_mgmt: the hostapd wpa_key_mgmt, if specified.
+            check_connectivity: Whether to check for internet connectivity.
+            hidden: Whether the network is hidden.
+        Returns:
+            True if successfully connected to WLAN, False if not.
+        """
+        if association_mechanism == 'policy':
+            return self.device.policy_save_and_connect(target_ssid,
+                                                       target_security,
+                                                       password=target_pwd)
+        elif not association_mechanism or association_mechanism == 'drivers':
+            connection_response = self.device.wlan_lib.wlanConnectToNetwork(
+                target_ssid, target_pwd=target_pwd)
+            return self.device.check_connect_response(connection_response)
+        else:
+            self.log.error(
+                "Association mechanism %s is not recognized. Acceptable values are 'drivers' and 'policy'"
+                % association_mechanism)
+            return False
+
+    def disconnect(self, association_mechanism=None):
+        """Function to disconnect from a Fuchsia WLAN device.
+           Asserts if disconnect was not successful.
+        """
+        if association_mechanism == 'policy':
+            asserts.assert_true(self.device.remove_all_and_disconnect(),
+                                'Failed to disconnect')
+        elif not association_mechanism or association_mechanism == 'drivers':
+            disconnect_response = self.device.wlan_lib.wlanDisconnect()
+            asserts.assert_true(
+                self.device.check_disconnect_response(disconnect_response),
+                'Failed to disconnect.')
+        else:
+            self.log.error(
+                "Association mechanism %s is not recognized. Acceptable values are 'drivers' and 'policy'"
+                % association_mechanism)
+            raise ValueError(
+                'Invalid association_mechanism "%s". Valid options are "policy" or "drivers".'
+                % association_mechanism)
+
+    def status(self):
+        return self.device.wlan_lib.wlanStatus()
+
+    def can_ping(self,
+                 dest_ip,
+                 count=3,
+                 interval=1000,
+                 timeout=1000,
+                 size=25,
+                 additional_ping_params=None):
+        return self.device.can_ping(
+            dest_ip,
+            count=count,
+            interval=interval,
+            timeout=timeout,
+            size=size,
+            additional_ping_params=additional_ping_params)
+
+    def ping(self,
+             dest_ip,
+             count=3,
+             interval=1000,
+             timeout=1000,
+             size=25,
+             additional_ping_params=None):
+        return self.device.ping(dest_ip,
+                                count=count,
+                                interval=interval,
+                                timeout=timeout,
+                                size=size,
+                                additional_ping_params=additional_ping_params)
+
+    def get_wlan_interface_id_list(self):
+        """Function to list available WLAN interfaces.
+
+        Returns:
+            A list of wlan interface IDs.
+        """
+        return self.device.wlan_lib.wlanGetIfaceIdList().get('result')
+
+    def destroy_wlan_interface(self, iface_id):
+        """Function to associate a Fuchsia WLAN device.
+
+        Args:
+            target_ssid: SSID to associate to.
+            target_pwd: Password for the SSID, if necessary.
+            check_connectivity: Whether to check for internet connectivity.
+            hidden: Whether the network is hidden.
+        Returns:
+            True if successfully destroyed wlan interface, False if not.
+        """
+        result = self.device.wlan_lib.wlanDestroyIface(iface_id)
+        if result.get('error') is None:
+            return True
+        else:
+            self.log.error("Failed to destroy interface with: {}".format(
+                result.get('error')))
+            return False
+
+    def send_command(self, command):
+        return self.device.send_command_ssh(str(command)).stdout
+
+    def get_interface_ip_addresses(self, interface):
+        return get_interface_ip_addresses(self.device, interface)
+
+    def is_connected(self, ssid=None):
+        """ Determines if wlan_device is connected to wlan network.
+
+        Args:
+            ssid (optional): string, to check if device is connect to a specific
+                network.
+
+        Returns:
+            True, if connected to a network or to the correct network when SSID
+                is provided.
+            False, if not connected or connect to incorrect network when SSID is
+                provided.
+        """
+        response = self.status()
+        if response.get('error'):
+            raise ConnectionError(
+                'Failed to get client network connection status')
+
+        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
+            return True
+        return False
+
+    def hard_power_cycle(self, pdus):
+        self.device.reboot(reboot_type='hard', testbed_pdus=pdus)
+
+    def save_network(self, target_ssid, security_type=None, target_pwd=None):
+        if security_type and security_type not in FUCHSIA_VALID_SECURITY_TYPES:
+            raise TypeError('Invalid security type: %s' % security_type)
+        response = self.device.wlan_policy_lib.wlanSaveNetwork(
+            target_ssid, security_type, target_pwd=target_pwd)
+        if response.get('error'):
+            raise EnvironmentError('Failed to save network %s. Err: %s' %
+                                   (target_ssid, response.get('error')))
+
+    def clear_saved_networks(self):
+        response = self.device.wlan_policy_lib.wlanRemoveAllNetworks()
+        if response.get('error'):
+            raise EnvironmentError('Failed to clear saved networks: %s' %
+                                   response.get('error'))
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
new file mode 100644
index 0000000..f8b33dc
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/wlan_device_lib/AbstractDeviceWlanDeviceBaseTest.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python3
+#
+#   Copyright (C) 2020 The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+
+class AbstractDeviceWlanDeviceBaseTest(WifiBaseTest):
+    def setup_class(self):
+        super().setup_class()
+
+    def on_fail(self, test_name, begin_time):
+        try:
+            self.dut.take_bug_report(test_name, begin_time)
+            self.dut.get_log(test_name, begin_time)
+        except Exception:
+            pass
+
+        try:
+            if self.dut.device.hard_reboot_on_fail:
+                self.dut.hard_power_cycle(self.pdu_devices)
+        except AttributeError:
+            pass
diff --git a/acts_tests/acts_contrib/test_utils/abstract_devices/wmm_transceiver.py b/acts_tests/acts_contrib/test_utils/abstract_devices/wmm_transceiver.py
new file mode 100644
index 0000000..f1aad98
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/abstract_devices/wmm_transceiver.py
@@ -0,0 +1,665 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import multiprocessing
+import time
+
+from datetime import datetime
+from uuid import uuid4
+
+from acts import signals
+from acts import tracelogger
+from acts import utils
+from acts.controllers import iperf_client
+from acts.controllers import iperf_server
+
+AC_VO = 'AC_VO'
+AC_VI = 'AC_VI'
+AC_BE = 'AC_BE'
+AC_BK = 'AC_BK'
+
+# TODO(fxb/61421): Add tests to check all DSCP classes are mapped to the correct
+# AC (there are many that aren't included here). Requires implementation of
+# sniffer.
+DEFAULT_AC_TO_TOS_TAG_MAP = {
+    AC_VO: '0xC0',
+    AC_VI: '0x80',
+    AC_BE: '0x0',
+    AC_BK: '0x20'
+}
+UDP = 'udp'
+TCP = 'tcp'
+DEFAULT_IPERF_PORT = 5201
+DEFAULT_STREAM_TIME = 10
+DEFAULT_IP_ADDR_TIMEOUT = 15
+PROCESS_JOIN_TIMEOUT = 60
+AVAILABLE = True
+UNAVAILABLE = False
+
+
+class WmmTransceiverError(signals.ControllerError):
+    pass
+
+
+def create(config, identifier=None, wlan_devices=None, access_points=None):
+    """Creates a WmmTransceiver from a config.
+
+    Args:
+        config: dict, config parameters for the transceiver. Contains:
+            - iperf_config: dict, the config to use for creating IPerfClients
+                and IPerfServers (excluding port).
+            - port_range_start: int, the lower bound of the port range to use
+                for creating IPerfServers. Defaults to 5201.
+            - wlan_device: string, the identifier of the wlan_device used for
+                this WmmTransceiver (optional)
+
+        identifier: string, identifier for the WmmTransceiver. Must be provided
+            either as arg or in the config.
+        wlan_devices: list of WlanDevice objects from which to get the
+            wlan_device, if any, used as this transceiver
+        access_points: list of AccessPoint objects from which to get the
+            access_point, if any, used as this transceiver
+    """
+    try:
+        # If identifier is not provided as func arg, it must be provided via
+        # config file.
+        if not identifier:
+            identifier = config['identifier']
+        iperf_config = config['iperf_config']
+
+    except KeyError as err:
+        raise WmmTransceiverError(
+            'Parameter not provided as func arg, nor found in config: %s' %
+            err)
+
+    if wlan_devices is None:
+        wlan_devices = []
+
+    if access_points is None:
+        access_points = []
+
+    port_range_start = config.get('port_range_start', DEFAULT_IPERF_PORT)
+
+    wd = None
+    ap = None
+    if 'wlan_device' in config:
+        wd = _find_wlan_device(config['wlan_device'], wlan_devices)
+    elif 'access_point' in config:
+        ap = _find_access_point(config['access_point'], access_points)
+
+    return WmmTransceiver(iperf_config,
+                          identifier,
+                          wlan_device=wd,
+                          access_point=ap,
+                          port_range_start=port_range_start)
+
+
+def _find_wlan_device(wlan_device_identifier, wlan_devices):
+    """Returns WlanDevice based on string identifier (e.g. ip, serial, etc.)
+
+    Args:
+        wlan_device_identifier: string, identifier for the desired WlanDevice
+        wlan_devices: list, WlanDevices to search through
+
+    Returns:
+        WlanDevice, with identifier matching wlan_device_identifier
+
+    Raises:
+        WmmTransceiverError, if no WlanDevice matches identifier
+    """
+    for wd in wlan_devices:
+        if wlan_device_identifier == wd.identifier:
+            return wd
+    raise WmmTransceiverError('No WlanDevice with identifier: %s' %
+                              wlan_device_identifier)
+
+
+def _find_access_point(access_point_ip, access_points):
+    """Returns AccessPoint based on string ip address
+
+    Args:
+        access_point_ip: string, control plane ip addr of the desired AP,
+        access_points: list, AccessPoints to search through
+
+    Returns:
+        AccessPoint, with hostname matching access_point_ip
+
+    Raises:
+        WmmTransceiverError, if no AccessPoint matches ip"""
+    for ap in access_points:
+        if ap.ssh_settings.hostname == access_point_ip:
+            return ap
+    raise WmmTransceiverError('No AccessPoint with ip: %s' % access_point_ip)
+
+
+class WmmTransceiver(object):
+    """Object for handling WMM tagged streams between devices"""
+    def __init__(self,
+                 iperf_config,
+                 identifier,
+                 wlan_device=None,
+                 access_point=None,
+                 port_range_start=5201):
+
+        self.identifier = identifier
+        self.log = tracelogger.TraceLogger(
+            WmmTransceiverLoggerAdapter(logging.getLogger(),
+                                        {'identifier': self.identifier}))
+        # WlanDevice or AccessPoint, that is used as the transceiver. Only one
+        # will be set. This helps consolodate association, setup, teardown, etc.
+        self.wlan_device = wlan_device
+        self.access_point = access_point
+
+        # Parameters used to create IPerfClient and IPerfServer objects on
+        # device
+        self._iperf_config = iperf_config
+        self._test_interface = self._iperf_config.get('test_interface')
+        self._port_range_start = port_range_start
+        self._next_server_port = port_range_start
+
+        # Maps IPerfClients, used for streams from this device, to True if
+        # available, False if reserved
+        self._iperf_clients = {}
+
+        # Maps IPerfServers, used to receive streams from other devices, to True
+        # if available, False if reserved
+        self._iperf_servers = {}
+
+        # Maps ports of servers, which are provided to other transceivers, to
+        # the actual IPerfServer objects
+        self._iperf_server_ports = {}
+
+        # Maps stream UUIDs to IPerfClients reserved for that streams use
+        self._reserved_clients = {}
+
+        # Maps stream UUIDs to (WmmTransceiver, IPerfServer) tuples, where the
+        # server is reserved on the transceiver for that streams use
+        self._reserved_servers = {}
+
+        # Maps with shared memory functionality to be used across the parallel
+        # streams. active_streams holds UUIDs of streams that are currently
+        # running on this device (mapped to True, since there is no
+        # multiprocessing set). stream_results maps UUIDs of streams completed
+        # on this device to IPerfResult results for that stream.
+        self._manager = multiprocessing.Manager()
+        self._active_streams = self._manager.dict()
+        self._stream_results = self._manager.dict()
+
+        # Holds parameters for streams that are prepared to run asynchronously
+        # (i.e. resources have been allocated). Maps UUIDs of the future streams
+        # to a dict, containing the stream parameters.
+        self._pending_async_streams = {}
+
+        # Set of UUIDs of asynchronous streams that have at least started, but
+        # have not had their resources reclaimed yet
+        self._ran_async_streams = set()
+
+        # Set of stream parallel process, which can be joined if completed
+        # successfully, or  terminated and joined in the event of an error
+        self._running_processes = set()
+
+    def run_synchronous_traffic_stream(self, stream_parameters, subnet):
+        """Runs a traffic stream with IPerf3 between two WmmTransceivers and
+        saves the results.
+
+        Args:
+            stream_parameters: dict, containing parameters to used for the
+                stream. See _parse_stream_parameters for details.
+            subnet: string, the subnet of the network to use for the stream
+
+        Returns:
+            uuid: UUID object, identifier of the stream
+        """
+        (receiver, access_category, bandwidth,
+         stream_time) = self._parse_stream_parameters(stream_parameters)
+        uuid = uuid4()
+
+        (client, server_ip,
+         server_port) = self._get_stream_resources(uuid, receiver, subnet)
+
+        self._validate_server_address(server_ip, uuid)
+
+        self.log.info('Running synchronous stream to %s WmmTransceiver' %
+                      receiver.identifier)
+        self._run_traffic(uuid,
+                          client,
+                          server_ip,
+                          server_port,
+                          self._active_streams,
+                          self._stream_results,
+                          access_category=access_category,
+                          bandwidth=bandwidth,
+                          stream_time=stream_time)
+
+        self._return_stream_resources(uuid)
+        return uuid
+
+    def prepare_asynchronous_stream(self, stream_parameters, subnet):
+        """Reserves resources and saves configs for upcoming asynchronous
+        traffic streams, so they can be started more simultaneously.
+
+        Args:
+            stream_parameters: dict, containing parameters to used for the
+                stream. See _parse_stream_parameters for details.
+            subnet: string, the subnet of the network to use for the stream
+
+        Returns:
+            uuid: UUID object, identifier of the stream
+        """
+        (receiver, access_category, bandwidth,
+         time) = self._parse_stream_parameters(stream_parameters)
+        uuid = uuid4()
+
+        (client, server_ip,
+         server_port) = self._get_stream_resources(uuid, receiver, subnet)
+
+        self._validate_server_address(server_ip, uuid)
+
+        pending_stream_config = {
+            'client': client,
+            'server_ip': server_ip,
+            'server_port': server_port,
+            'access_category': access_category,
+            'bandwidth': bandwidth,
+            'time': time
+        }
+
+        self._pending_async_streams[uuid] = pending_stream_config
+        self.log.info('Stream to %s WmmTransceiver prepared.' %
+                      receiver.identifier)
+        return uuid
+
+    def start_asynchronous_streams(self, start_time=None):
+        """Starts pending asynchronous streams between two WmmTransceivers as
+        parallel processes.
+
+        Args:
+            start_time: float, time, seconds since epoch, at which to start the
+                stream (for better synchronicity). If None, start immediately.
+        """
+        for uuid in self._pending_async_streams:
+            pending_stream_config = self._pending_async_streams[uuid]
+            client = pending_stream_config['client']
+            server_ip = pending_stream_config['server_ip']
+            server_port = pending_stream_config['server_port']
+            access_category = pending_stream_config['access_category']
+            bandwidth = pending_stream_config['bandwidth']
+            time = pending_stream_config['time']
+
+            process = multiprocessing.Process(target=self._run_traffic,
+                                              args=[
+                                                  uuid, client, server_ip,
+                                                  server_port,
+                                                  self._active_streams,
+                                                  self._stream_results
+                                              ],
+                                              kwargs={
+                                                  'access_category':
+                                                  access_category,
+                                                  'bandwidth': bandwidth,
+                                                  'stream_time': time,
+                                                  'start_time': start_time
+                                              })
+
+            # This needs to be set here to ensure its marked active before
+            # it even starts.
+            self._active_streams[uuid] = True
+            process.start()
+            self._ran_async_streams.add(uuid)
+            self._running_processes.add(process)
+
+        self._pending_async_streams.clear()
+
+    def cleanup_asynchronous_streams(self, timeout=PROCESS_JOIN_TIMEOUT):
+        """Releases reservations on resources (IPerfClients and IPerfServers)
+        that were held for asynchronous streams, both pending and finished.
+        Attempts to join any running processes, logging an error if timeout is
+        exceeded.
+
+        Args:
+            timeout: time, in seconds, to wait for each running process, if any,
+                to join
+        """
+        self.log.info('Cleaning up any asynchronous streams.')
+
+        # Releases resources for any streams that were prepared, but no run
+        for uuid in self._pending_async_streams:
+            self.log.error(
+                'Pending asynchronous stream %s never ran. Cleaning.' % uuid)
+            self._return_stream_resources(uuid)
+        self._pending_async_streams.clear()
+
+        # Attempts to join any running streams, terminating them after timeout
+        # if necessary.
+        while self._running_processes:
+            process = self._running_processes.pop()
+            process.join(timeout)
+            if process.is_alive():
+                self.log.error(
+                    'Stream process failed to join in %s seconds. Terminating.'
+                    % timeout)
+                process.terminate()
+                process.join()
+        self._active_streams.clear()
+
+        # Release resources for any finished streams
+        while self._ran_async_streams:
+            uuid = self._ran_async_streams.pop()
+            self._return_stream_resources(uuid)
+
+    def get_results(self, uuid):
+        """Retrieves a streams IPerfResults from stream_results
+
+        Args:
+            uuid: UUID object, identifier of the stream
+        """
+        return self._stream_results.get(uuid, None)
+
+    def destroy_resources(self):
+        for server in self._iperf_servers:
+            server.stop()
+        self._iperf_servers.clear()
+        self._iperf_server_ports.clear()
+        self._iperf_clients.clear()
+        self._next_server_port = self._port_range_start
+        self._stream_results.clear()
+
+    @property
+    def has_active_streams(self):
+        return bool(self._active_streams)
+
+    # Helper Functions
+
+    def _run_traffic(self,
+                     uuid,
+                     client,
+                     server_ip,
+                     server_port,
+                     active_streams,
+                     stream_results,
+                     access_category=None,
+                     bandwidth=None,
+                     stream_time=DEFAULT_STREAM_TIME,
+                     start_time=None):
+        """Runs an iperf3 stream.
+
+        1. Adds stream UUID to active_streams
+        2. Runs stream
+        3. Saves results to stream_results
+        4. Removes stream UUID from active_streams
+
+        Args:
+            uuid: UUID object, identifier for stream
+            client: IPerfClient object on device
+            server_ip: string, ip address of IPerfServer for stream
+            server_port: int, port of the IPerfServer for stream
+            active_streams: multiprocessing.Manager.dict, which holds stream
+                UUIDs of active streams on the device
+            stream_results: multiprocessing.Manager.dict, which maps stream
+                UUIDs of streams to IPerfResult objects
+            access_category: string, WMM access category to use with iperf
+                (AC_BK, AC_BE, AC_VI, AC_VO). Unset if None.
+            bandwidth: int, bandwidth in mbps to use with iperf. Implies UDP.
+                Unlimited if None.
+            stream_time: int, time in seconds, to run iperf stream
+            start_time: float, time, seconds since epoch, at which to start the
+                stream (for better synchronicity). If None, start immediately.
+        """
+        active_streams[uuid] = True
+        # SSH sessions must be started within the process that is going to
+        # use it.
+        if type(client) == iperf_client.IPerfClientOverSsh:
+            with utils.SuppressLogOutput():
+                client.start_ssh()
+
+        ac_flag = ''
+        bandwidth_flag = ''
+        time_flag = '-t %s' % stream_time
+
+        if access_category:
+            ac_flag = ' -S %s' % DEFAULT_AC_TO_TOS_TAG_MAP[access_category]
+
+        if bandwidth:
+            bandwidth_flag = ' -u -b %sM' % bandwidth
+
+        iperf_flags = '-p %s -i 1 %s%s%s -J' % (server_port, time_flag,
+                                                ac_flag, bandwidth_flag)
+        if not start_time:
+            start_time = time.time()
+        time_str = datetime.fromtimestamp(start_time).strftime('%H:%M:%S.%f')
+        self.log.info(
+            'At %s, starting %s second stream to %s:%s with (AC: %s, Bandwidth: %s)'
+            % (time_str, stream_time, server_ip, server_port, access_category,
+               bandwidth if bandwidth else 'Unlimited'))
+
+        # If present, wait for stream start time
+        if start_time:
+            current_time = time.time()
+            while current_time < start_time:
+                current_time = time.time()
+        path = client.start(server_ip, iperf_flags, '%s' % uuid)
+        stream_results[uuid] = iperf_server.IPerfResult(
+            path, reporting_speed_units='mbps')
+
+        if type(client) == iperf_client.IPerfClientOverSsh:
+            client.close_ssh()
+        active_streams.pop(uuid)
+
+    def _get_stream_resources(self, uuid, receiver, subnet):
+        """Reserves an IPerfClient and IPerfServer for a stream.
+
+        Args:
+            uuid: UUID object, identifier of the stream
+            receiver: WmmTransceiver object, which will be the streams receiver
+            subnet: string, subnet of test network, to retrieve the appropriate
+                server address
+
+        Returns:
+            (IPerfClient, string, int) representing the client, server address,
+            and server port to use for the stream
+        """
+        client = self._get_client(uuid)
+        server_ip, server_port = self._get_server(receiver, uuid, subnet)
+        return (client, server_ip, server_port)
+
+    def _return_stream_resources(self, uuid):
+        """Releases reservations on a streams IPerfClient and IPerfServer, so
+        they can be used by a future stream.
+
+        Args:
+            uuid: UUID object, identifier of the stream
+        """
+        if uuid in self._active_streams:
+            raise EnvironmentError('Resource still being used by stream %s' %
+                                   uuid)
+        (receiver, server_port) = self._reserved_servers.pop(uuid)
+        receiver._release_server(server_port)
+        client = self._reserved_clients.pop(uuid)
+        self._iperf_clients[client] = AVAILABLE
+
+    def _get_client(self, uuid):
+        """Retrieves and reserves IPerfClient for use in a stream. If none are
+        available, a new one is created.
+
+        Args:
+            uuid: UUID object, identifier for stream, used to link client to
+                stream for teardown
+
+        Returns:
+            IPerfClient on device
+        """
+        reserved_client = None
+        for client in self._iperf_clients:
+            if self._iperf_clients[client] == AVAILABLE:
+                reserved_client = client
+                break
+        else:
+            reserved_client = iperf_client.create([self._iperf_config])[0]
+            # Due to the nature of multiprocessing, ssh connections must
+            # be started inside the parallel processes, so it must be closed
+            # here.
+            if type(reserved_client) == iperf_client.IPerfClientOverSsh:
+                reserved_client.close_ssh()
+
+        self._iperf_clients[reserved_client] = UNAVAILABLE
+        self._reserved_clients[uuid] = reserved_client
+        return reserved_client
+
+    def _get_server(self, receiver, uuid, subnet):
+        """Retrieves the address and port of a reserved IPerfServer object from
+        the receiver object for use in a stream.
+
+        Args:
+            receiver: WmmTransceiver, to get an IPerfServer from
+            uuid: UUID, identifier for stream, used to link server to stream
+                for teardown
+            subnet: string, subnet of test network, to retrieve the appropriate
+                server address
+
+        Returns:
+            (string, int) representing the IPerfServer address and port
+        """
+        (server_ip, server_port) = receiver._reserve_server(subnet)
+        self._reserved_servers[uuid] = (receiver, server_port)
+        return (server_ip, server_port)
+
+    def _reserve_server(self, subnet):
+        """Reserves an available IPerfServer for use in a stream from another
+        WmmTransceiver. If none are available, a new one is created.
+
+        Args:
+            subnet: string, subnet of test network, to retrieve the appropriate
+                server address
+
+        Returns:
+            (string, int) representing the IPerfServer address and port
+        """
+        reserved_server = None
+        for server in self._iperf_servers:
+            if self._iperf_servers[server] == AVAILABLE:
+                reserved_server = server
+                break
+        else:
+            iperf_server_config = self._iperf_config
+            iperf_server_config.update({'port': self._next_server_port})
+            self._next_server_port += 1
+            reserved_server = iperf_server.create([iperf_server_config])[0]
+            self._iperf_server_ports[reserved_server.port] = reserved_server
+
+        self._iperf_servers[reserved_server] = UNAVAILABLE
+        reserved_server.start()
+        end_time = time.time() + DEFAULT_IP_ADDR_TIMEOUT
+        while time.time() < end_time:
+            if self.wlan_device:
+                addresses = utils.get_interface_ip_addresses(
+                    self.wlan_device.device, self._test_interface)
+            else:
+                addresses = reserved_server.get_interface_ip_addresses(
+                    self._test_interface)
+            for addr in addresses['ipv4_private']:
+                if utils.ip_in_subnet(addr, subnet):
+                    return (addr, reserved_server.port)
+        raise AttributeError(
+            'Reserved server has no ipv4 address in the %s subnet' % subnet)
+
+    def _release_server(self, server_port):
+        """Releases reservation on IPerfServer, which was held for a stream
+        from another WmmTransceiver.
+
+        Args:
+            server_port: int, the port of the IPerfServer being returned (since)
+                it is the identifying characteristic
+        """
+        server = self._iperf_server_ports[server_port]
+        server.stop()
+        self._iperf_servers[server] = AVAILABLE
+
+    def _validate_server_address(self, server_ip, uuid, timeout=60):
+        """ Verifies server address can be pinged before attempting to run
+        traffic, since iperf is unforgiving when the server is unreachable.
+
+        Args:
+            server_ip: string, ip address of the iperf server
+            uuid: string, uuid of the stream to use this server
+            timeout: int, time in seconds to wait for server to respond to pings
+
+        Raises:
+            WmmTransceiverError, if, after timeout, server ip is unreachable.
+        """
+        self.log.info('Verifying server address (%s) is reachable.' %
+                      server_ip)
+        end_time = time.time() + timeout
+        while time.time() < end_time:
+            if self.can_ping(server_ip):
+                break
+            else:
+                self.log.debug(
+                    'Could not ping server address (%s). Retrying in 1 second.'
+                    % (server_ip))
+                time.sleep(1)
+        else:
+            self._return_stream_resources(uuid)
+            raise WmmTransceiverError('IPerfServer address (%s) unreachable.' %
+                                      server_ip)
+
+    def can_ping(self, dest_ip):
+        """ Utilizes can_ping function in wlan_device or access_point device to
+        ping dest_ip
+
+        Args:
+            dest_ip: string, ip address to ping
+
+        Returns:
+            True, if dest address is reachable
+            False, otherwise
+        """
+        if self.wlan_device:
+            return self.wlan_device.can_ping(dest_ip)
+        else:
+            return self.access_point.can_ping(dest_ip)
+
+    def _parse_stream_parameters(self, stream_parameters):
+        """Parses stream_parameters from dictionary.
+
+        Args:
+            stream_parameters: dict of stream parameters
+                'receiver': WmmTransceiver, the receiver for the stream
+                'access_category': String, the access category to use for the
+                    stream. Unset if None.
+                'bandwidth': int, bandwidth in mbps for the stream. If set,
+                    implies UDP. If unset, implies TCP and unlimited bandwidth.
+                'time': int, time in seconds to run stream.
+
+        Returns:
+            (receiver, access_category, bandwidth, time) as
+            (WmmTransceiver, String, int, int)
+        """
+        receiver = stream_parameters['receiver']
+        access_category = stream_parameters.get('access_category', None)
+        bandwidth = stream_parameters.get('bandwidth', None)
+        time = stream_parameters.get('time', DEFAULT_STREAM_TIME)
+        return (receiver, access_category, bandwidth, time)
+
+
+class WmmTransceiverLoggerAdapter(logging.LoggerAdapter):
+    def process(self, msg, kwargs):
+        if self.extra['identifier']:
+            log_identifier = ' | %s' % self.extra['identifier']
+        else:
+            log_identifier = ''
+        msg = "[WmmTransceiver%s] %s" % (log_identifier, msg)
+        return (msg, kwargs)
diff --git a/acts_tests/acts_contrib/test_utils/audio_analysis_lib/__init__.py b/acts_tests/acts_contrib/test_utils/audio_analysis_lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/audio_analysis_lib/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/audio_analysis_lib/audio_analysis.py b/acts_tests/acts_contrib/test_utils/audio_analysis_lib/audio_analysis.py
new file mode 100644
index 0000000..a5b9fe1
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/audio_analysis_lib/audio_analysis.py
@@ -0,0 +1,667 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+"""This module provides utilities to do audio data analysis."""
+
+import logging
+import numpy
+import soundfile
+from scipy.signal import blackmanharris
+from scipy.signal import iirnotch
+from scipy.signal import lfilter
+
+# The default block size of pattern matching.
+ANOMALY_DETECTION_BLOCK_SIZE = 120
+
+# Only peaks with coefficient greater than 0.01 of the first peak should be
+# considered. Note that this correspond to -40dB in the spectrum.
+DEFAULT_MIN_PEAK_RATIO = 0.01
+
+# The minimum RMS value of meaningful audio data.
+MEANINGFUL_RMS_THRESHOLD = 0.001
+
+# The minimal signal norm value.
+_MINIMUM_SIGNAL_NORM = 0.001
+
+# The default pattern mathing threshold. By experiment, this threshold
+# can tolerate normal noise of 0.3 amplitude when sine wave signal
+# amplitude is 1.
+PATTERN_MATCHING_THRESHOLD = 0.85
+
+# The default number of samples within the analysis step size that the
+# difference between two anomaly time values can be to be grouped together.
+ANOMALY_GROUPING_TOLERANCE = 1.0
+
+# Window size for peak detection.
+PEAK_WINDOW_SIZE_HZ = 20
+
+
+class RMSTooSmallError(Exception):
+    """Error when signal RMS is too small."""
+    pass
+
+
+class EmptyDataError(Exception):
+    """Error when signal is empty."""
+    pass
+
+
+def normalize_signal(signal, saturate_value):
+    """Normalizes the signal with respect to the saturate value.
+
+    Args:
+        signal: A list for one-channel PCM data.
+        saturate_value: The maximum value that the PCM data might be.
+
+    Returns:
+        A numpy array containing normalized signal. The normalized signal has
+            value -1 and 1 when it saturates.
+
+    """
+    signal = numpy.array(signal)
+    return signal / float(saturate_value)
+
+
+def spectral_analysis(signal,
+                      rate,
+                      min_peak_ratio=DEFAULT_MIN_PEAK_RATIO,
+                      peak_window_size_hz=PEAK_WINDOW_SIZE_HZ):
+    """Gets the dominant frequencies by spectral analysis.
+
+    Args:
+        signal: A list of numbers for one-channel PCM data. This should be
+                   normalized to [-1, 1] so the function can check if signal RMS
+                   is too small to be meaningful.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        min_peak_ratio: The minimum peak_i/peak_0 ratio such that the
+                           peaks other than the greatest one should be
+                           considered.
+                           This is to ignore peaks that are too small compared
+                           to the first peak peak_0.
+        peak_window_size_hz: The window size in Hz to find the peaks.
+                                The minimum differences between found peaks will
+                                be half of this value.
+
+    Returns:
+        A list of tuples:
+              [(peak_frequency_0, peak_coefficient_0),
+               (peak_frequency_1, peak_coefficient_1),
+               (peak_frequency_2, peak_coefficient_2), ...]
+              where the tuples are sorted by coefficients. The last
+              peak_coefficient will be no less than peak_coefficient *
+              min_peak_ratio. If RMS is less than MEANINGFUL_RMS_THRESHOLD,
+              returns [(0, 0)].
+
+    """
+    # Checks the signal is meaningful.
+    if len(signal) == 0:
+        raise EmptyDataError('Signal data is empty')
+
+    signal_rms = numpy.linalg.norm(signal) / numpy.sqrt(len(signal))
+    logging.debug('signal RMS = %s', signal_rms)
+
+    # If RMS is too small, set dominant frequency and coefficient to 0.
+    if signal_rms < MEANINGFUL_RMS_THRESHOLD:
+        logging.warning(
+            'RMS %s is too small to be meaningful. Set frequency to 0.',
+            signal_rms)
+        return [(0, 0)]
+
+    logging.debug('Doing spectral analysis ...')
+
+    # First, pass signal through a window function to mitigate spectral leakage.
+    y_conv_w = signal * numpy.hanning(len(signal))
+
+    length = len(y_conv_w)
+
+    # x_f is the frequency in Hz, y_f is the transformed coefficient.
+    x_f = _rfft_freq(length, rate)
+    y_f = 2.0 / length * numpy.fft.rfft(y_conv_w)
+
+    # y_f is complex so consider its absolute value for magnitude.
+    abs_y_f = numpy.abs(y_f)
+    threshold = max(abs_y_f) * min_peak_ratio
+
+    # Suppresses all coefficients that are below threshold.
+    for i in range(len(abs_y_f)):
+        if abs_y_f[i] < threshold:
+            abs_y_f[i] = 0
+
+    # Gets the peak detection window size in indice.
+    # x_f[1] is the frequency difference per index.
+    peak_window_size = int(peak_window_size_hz / x_f[1])
+
+    # Detects peaks.
+    peaks = peak_detection(abs_y_f, peak_window_size)
+
+    # Transform back the peak location from index to frequency.
+    results = []
+    for index, value in peaks:
+        results.append((x_f[int(index)], value))
+    return results
+
+
+def _rfft_freq(length, rate):
+    """Gets the frequency at each index of real FFT.
+
+    Args:
+        length: The window length of FFT.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+
+    Returns:
+        A numpy array containing frequency corresponding to numpy.fft.rfft
+            result at each index.
+
+    """
+    # The difference in Hz between each index.
+    val = rate / float(length)
+    # Only care half of frequencies for FFT on real signal.
+    result_length = length // 2 + 1
+    return numpy.linspace(0, (result_length - 1) * val, result_length)
+
+
+def peak_detection(array, window_size):
+    """Detects peaks in an array.
+
+    A point (i, array[i]) is a peak if array[i] is the maximum among
+    array[i - half_window_size] to array[i + half_window_size].
+    If array[i - half_window_size] to array[i + half_window_size] are all equal,
+    then there is no peak in this window.
+    Note that we only consider peak with value greater than 0.
+
+    Args:
+        array: The input array to detect peaks in. Array is a list of
+        absolute values of the magnitude of transformed coefficient.
+
+        window_size: The window to detect peaks.
+
+    Returns:
+        A list of tuples:
+              [(peak_index_1, peak_value_1), (peak_index_2, peak_value_2), ...]
+              where the tuples are sorted by peak values.
+
+    """
+    half_window_size = window_size / 2
+    length = len(array)
+
+    def mid_is_peak(array, mid, left, right):
+        """Checks if value at mid is the largest among left to right in array.
+
+        Args:
+            array: A list of numbers.
+            mid: The mid index.
+            left: The left index.
+            rigth: The right index.
+
+        Returns:
+            A tuple (is_peak, next_candidate)
+                  is_peak is True if array[index] is the maximum among numbers
+                  in array between index [left, right] inclusively.
+                  next_candidate is the index of next candidate for peak if
+                  is_peak is False. It is the index of maximum value in
+                  [mid + 1, right]. If is_peak is True, next_candidate is
+                  right + 1.
+
+        """
+        value_mid = array[int(mid)]
+        is_peak = True
+        next_peak_candidate_index = None
+
+        # Check the left half window.
+        for index in range(int(left), int(mid)):
+            if array[index] >= value_mid:
+                is_peak = False
+                break
+
+        # Mid is at the end of array.
+        if mid == right:
+            return is_peak, right + 1
+
+        # Check the right half window and also record next candidate.
+        # Favor the larger index for next_peak_candidate_index.
+        for index in range(int(right), int(mid), -1):
+            if (next_peak_candidate_index is None or
+                    array[index] > array[next_peak_candidate_index]):
+                next_peak_candidate_index = index
+
+        if array[next_peak_candidate_index] >= value_mid:
+            is_peak = False
+
+        if is_peak:
+            next_peak_candidate_index = right + 1
+
+        return is_peak, next_peak_candidate_index
+
+    results = []
+    mid = 0
+    next_candidate_idx = None
+    while mid < length:
+        left = max(0, mid - half_window_size)
+        right = min(length - 1, mid + half_window_size)
+
+        # Only consider value greater than 0.
+        if array[int(mid)] == 0:
+            mid = mid + 1
+            continue
+
+        is_peak, next_candidate_idx = mid_is_peak(array, mid, left, right)
+
+        if is_peak:
+            results.append((mid, array[int(mid)]))
+
+        # Use the next candidate found in [mid + 1, right], or right + 1.
+        mid = next_candidate_idx
+
+    # Sort the peaks by values.
+    return sorted(results, key=lambda x: x[1], reverse=True)
+
+
+def anomaly_detection(signal,
+                      rate,
+                      freq,
+                      block_size=ANOMALY_DETECTION_BLOCK_SIZE,
+                      threshold=PATTERN_MATCHING_THRESHOLD):
+    """Detects anomaly in a sine wave signal.
+
+    This method detects anomaly in a sine wave signal by matching
+    patterns of each block.
+    For each moving window of block in the test signal, checks if there
+    is any block in golden signal that is similar to this block of test signal.
+    If there is such a block in golden signal, then this block of test
+    signal is matched and there is no anomaly in this block of test signal.
+    If there is any block in test signal that is not matched, then this block
+    covers an anomaly.
+    The block of test signal starts from index 0, and proceeds in steps of
+    half block size. The overlapping of test signal blocks makes sure there must
+    be at least one block covering the transition from sine wave to anomaly.
+
+    Args:
+        signal: A 1-D array-like object for 1-channel PCM data.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        freq: The expected frequency of signal.
+        block_size: The block size in samples to detect anomaly.
+        threshold: The threshold of correlation index to be judge as matched.
+
+    Returns:
+        A list containing time markers in seconds that have an anomaly within
+            block_size samples.
+
+    """
+    if len(signal) == 0:
+        raise EmptyDataError('Signal data is empty')
+
+    golden_y = _generate_golden_pattern(rate, freq, block_size)
+
+    results = []
+
+    for start in range(0, len(signal), int(block_size / 2)):
+        end = start + block_size
+        test_signal = signal[start:end]
+        matched = _moving_pattern_matching(golden_y, test_signal, threshold)
+        if not matched:
+            results.append(start)
+
+    results = [float(x) / rate for x in results]
+
+    return results
+
+
+def get_anomaly_durations(signal,
+                          rate,
+                          freq,
+                          block_size=ANOMALY_DETECTION_BLOCK_SIZE,
+                          threshold=PATTERN_MATCHING_THRESHOLD,
+                          tolerance=ANOMALY_GROUPING_TOLERANCE):
+    """Detect anomalies in a sine wav and return their start and end times.
+
+    Run anomaly_detection function and parse resulting array of time values into
+    discrete anomalies defined by a start and end time tuple. Time values are
+    judged to be part of the same anomaly if they lie within a given tolerance
+    of half the block_size number of samples of each other.
+
+    Args:
+        signal: A 1-D array-like object for 1-channel PCM data.
+        rate (int): Sampling rate in samples per second.
+            Example inputs: 44100, 48000
+        freq (int): The expected frequency of signal.
+        block_size (int): The block size in samples to detect anomaly.
+        threshold (float): The threshold of correlation index to be judge as
+            matched.
+        tolerance (float): The number of samples greater than block_size / 2
+            that the sample distance between two anomaly time values can be and
+            still be grouped as the same anomaly.
+    Returns:
+        bounds (list): a list of (start, end) tuples where start and end are the
+            boundaries in seconds of the detected anomaly.
+    """
+    bounds = []
+    anoms = anomaly_detection(signal, rate, freq, block_size, threshold)
+    if len(anoms) == 0:
+        return bounds
+    end = anoms[0]
+    start = anoms[0]
+    for i in range(len(anoms)-1):
+        end = anoms[i]
+        sample_diff = abs(anoms[i] - anoms[i+1]) * rate
+        # We require a tolerance because sample_diff may be slightly off due to
+        # float rounding errors in Python.
+        if sample_diff > block_size / 2 + tolerance:
+            bounds.append((start, end))
+            start = anoms[i+1]
+    bounds.append((start, end))
+    return bounds
+
+
+def _generate_golden_pattern(rate, freq, block_size):
+    """Generates a golden pattern of certain frequency.
+
+    The golden pattern must cover all the possibilities of waveforms in a
+    block. So, we need a golden pattern covering 1 period + 1 block size,
+    such that the test block can start anywhere in a period, and extends
+    a block size.
+
+    |period |1 bk|
+    |       |    |
+     . .     . .
+    .   .   .   .
+         . .     .
+
+    Args:
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        freq: The frequency of golden pattern.
+        block_size: The block size in samples to detect anomaly.
+
+    Returns:
+        A 1-D array for golden pattern.
+
+    """
+    samples_in_a_period = int(rate / freq) + 1
+    samples_in_golden_pattern = samples_in_a_period + block_size
+    golden_x = numpy.linspace(0.0, (samples_in_golden_pattern - 1) * 1.0 /
+                              rate, samples_in_golden_pattern)
+    golden_y = numpy.sin(freq * 2.0 * numpy.pi * golden_x)
+    return golden_y
+
+
+def _moving_pattern_matching(golden_signal, test_signal, threshold):
+    """Checks if test_signal is similar to any block of golden_signal.
+
+    Compares test signal with each block of golden signal by correlation
+    index. If there is any block of golden signal that is similar to
+    test signal, then it is matched.
+
+    Args:
+        golden_signal: A 1-D array for golden signal.
+        test_signal: A 1-D array for test signal.
+        threshold: The threshold of correlation index to be judge as matched.
+
+    Returns:
+        True if there is a match. False otherwise.
+
+        ValueError: if test signal is longer than golden signal.
+
+    """
+    if len(golden_signal) < len(test_signal):
+        raise ValueError('Test signal is longer than golden signal')
+
+    block_length = len(test_signal)
+    number_of_movings = len(golden_signal) - block_length + 1
+    correlation_indices = []
+    for moving_index in range(number_of_movings):
+        # Cuts one block of golden signal from start index.
+        # The block length is the same as test signal.
+        start = moving_index
+        end = start + block_length
+        golden_signal_block = golden_signal[start:end]
+        try:
+            correlation_index = _get_correlation_index(golden_signal_block,
+                                                       test_signal)
+        except TestSignalNormTooSmallError:
+            logging.info(
+                'Caught one block of test signal that has no meaningful norm')
+            return False
+        correlation_indices.append(correlation_index)
+
+    # Checks if the maximum correlation index is high enough.
+    max_corr = max(correlation_indices)
+    if max_corr < threshold:
+        logging.debug('Got one unmatched block with max_corr: %s', max_corr)
+        return False
+    return True
+
+
+class GoldenSignalNormTooSmallError(Exception):
+    """Exception when golden signal norm is too small."""
+    pass
+
+
+class TestSignalNormTooSmallError(Exception):
+    """Exception when test signal norm is too small."""
+    pass
+
+
+def _get_correlation_index(golden_signal, test_signal):
+    """Computes correlation index of two signal of same length.
+
+    Args:
+        golden_signal: An 1-D array-like object.
+        test_signal: An 1-D array-like object.
+
+    Raises:
+        ValueError: if two signal have different lengths.
+        GoldenSignalNormTooSmallError: if golden signal norm is too small
+        TestSignalNormTooSmallError: if test signal norm is too small.
+
+    Returns:
+        The correlation index.
+    """
+    if len(golden_signal) != len(test_signal):
+        raise ValueError('Only accepts signal of same length: %s, %s' %
+                         (len(golden_signal), len(test_signal)))
+
+    norm_golden = numpy.linalg.norm(golden_signal)
+    norm_test = numpy.linalg.norm(test_signal)
+    if norm_golden <= _MINIMUM_SIGNAL_NORM:
+        raise GoldenSignalNormTooSmallError(
+            'No meaningful data as norm is too small.')
+    if norm_test <= _MINIMUM_SIGNAL_NORM:
+        raise TestSignalNormTooSmallError(
+            'No meaningful data as norm is too small.')
+
+    # The 'valid' cross correlation result of two signals of same length will
+    # contain only one number.
+    correlation = numpy.correlate(golden_signal, test_signal, 'valid')[0]
+    return correlation / (norm_golden * norm_test)
+
+
+def fundamental_freq(signal, rate):
+    """Return fundamental frequency of signal by finding max in freq domain.
+    """
+    dft = numpy.fft.rfft(signal)
+    fund_freq = rate * (numpy.argmax(numpy.abs(dft)) / len(signal))
+    return fund_freq
+
+
+def rms(array):
+    """Return the root mean square of array.
+    """
+    return numpy.sqrt(numpy.mean(numpy.absolute(array)**2))
+
+
+def THDN(signal, rate, q, freq):
+    """Measure the THD+N for a signal and return the results.
+    Subtract mean to center signal around 0, remove fundamental frequency from
+    dft using notch filter and transform back into signal to get noise. Compute
+    ratio of RMS of noise signal to RMS of entire signal.
+
+    Args:
+        signal: array of values representing an audio signal.
+        rate: sample rate in Hz of the signal.
+        q: quality factor for the notch filter.
+        freq: fundamental frequency of the signal. All other frequencies
+            are noise. If not specified, will be calculated using FFT.
+    Returns:
+        THDN: THD+N ratio calculated from the ratio of RMS of pure harmonics
+            and noise signal to RMS of original signal.
+    """
+    # Normalize and window signal.
+    signal -= numpy.mean(signal)
+    windowed = signal * blackmanharris(len(signal))
+    # Find fundamental frequency to remove if not specified.
+    freq = freq or fundamental_freq(windowed, rate)
+    # Create notch filter to isolate noise.
+    w0 = freq / (rate / 2.0)
+    b, a = iirnotch(w0, q)
+    noise = lfilter(b, a, windowed)
+    # Calculate THD+N.
+    THDN = rms(noise) / rms(windowed)
+    return THDN
+
+
+def max_THDN(signal, rate, step_size, window_size, q, freq):
+    """Analyze signal with moving window and find maximum THD+N value.
+    Args:
+        signal: array representing the signal
+        rate: sample rate of the signal.
+        step_size: how many samples to move the window by for each analysis.
+        window_size: how many samples to analyze each time.
+        q: quality factor for the notch filter.
+        freq: fundamental frequency of the signal. All other frequencies
+            are noise. If not specified, will be calculated using FFT.
+    Returns:
+        greatest_THDN: the greatest THD+N value found across all windows
+    """
+    greatest_THDN = 0
+    cur = 0
+    while cur + window_size < len(signal):
+        window = signal[cur:cur + window_size]
+        res = THDN(window, rate, q, freq)
+        cur += step_size
+        if res > greatest_THDN:
+            greatest_THDN = res
+    return greatest_THDN
+
+
+def get_file_THDN(filename, q, freq=None):
+    """Get THD+N values for each channel of an audio file.
+
+    Args:
+        filename (str): path to the audio file.
+          (supported file types: http://www.mega-nerd.com/libsndfile/#Features)
+        q (float): quality factor for the notch filter.
+        freq (int|float): fundamental frequency of the signal. All other
+            frequencies are noise. If None, will be calculated with FFT.
+    Returns:
+        channel_results (list): THD+N value for each channel's signal.
+            List index corresponds to channel index.
+    """
+    audio_file = soundfile.SoundFile(filename)
+    channel_results = []
+    if audio_file.channels == 1:
+        channel_results.append(THDN(signal=audio_file.read(),
+                                    rate=audio_file.samplerate,
+                                    q=q,
+                                    freq=freq))
+    else:
+        for ch_no, channel in enumerate(audio_file.read().transpose()):
+            channel_results.append(THDN(signal=channel,
+                                        rate=audio_file.samplerate,
+                                        q=q,
+                                        freq=freq))
+    return channel_results
+
+
+def get_file_max_THDN(filename, step_size, window_size, q, freq=None):
+    """Get max THD+N value across analysis windows for each channel of file.
+
+    Args:
+        filename (str): path to the audio file.
+          (supported file types: http://www.mega-nerd.com/libsndfile/#Features)
+        step_size: how many samples to move the window by for each analysis.
+        window_size: how many samples to analyze each time.
+        q (float): quality factor for the notch filter.
+        freq (int|float): fundamental frequency of the signal. All other
+            frequencies are noise. If None, will be calculated with FFT.
+    Returns:
+        channel_results (list): max THD+N value for each channel's signal.
+            List index corresponds to channel index.
+    """
+    audio_file = soundfile.SoundFile(filename)
+    channel_results = []
+    if audio_file.channels == 1:
+        channel_results.append(max_THDN(signal=audio_file.read(),
+                                        rate=audio_file.samplerate,
+                                        step_size=step_size,
+                                        window_size=window_size,
+                                        q=q,
+                                        freq=freq))
+    else:
+        for ch_no, channel in enumerate(audio_file.read().transpose()):
+            channel_results.append(max_THDN(signal=channel,
+                                            rate=audio_file.samplerate,
+                                            step_size=step_size,
+                                            window_size=window_size,
+                                            q=q,
+                                            freq=freq))
+    return channel_results
+
+
+def get_file_anomaly_durations(filename, freq=None,
+                               block_size=ANOMALY_DETECTION_BLOCK_SIZE,
+                               threshold=PATTERN_MATCHING_THRESHOLD,
+                               tolerance=ANOMALY_GROUPING_TOLERANCE):
+    """Get durations of anomalies for each channel of audio file.
+
+    Args:
+        filename (str): path to the audio file.
+          (supported file types: http://www.mega-nerd.com/libsndfile/#Features)
+        freq (int|float): fundamental frequency of the signal. All other
+            frequencies are noise. If None, will be calculated with FFT.
+        block_size (int): The block size in samples to detect anomaly.
+        threshold (float): The threshold of correlation index to be judge as
+            matched.
+        tolerance (float): The number of samples greater than block_size / 2
+            that the sample distance between two anomaly time values can be and
+            still be grouped as the same anomaly.
+    Returns:
+        channel_results (list): anomaly durations for each channel's signal.
+            List index corresponds to channel index.
+    """
+    audio_file = soundfile.SoundFile(filename)
+    signal = audio_file.read()
+    freq = freq or fundamental_freq(signal, audio_file.samplerate)
+    channel_results = []
+    if audio_file.channels == 1:
+        channel_results.append(get_anomaly_durations(
+            signal=signal,
+            rate=audio_file.samplerate,
+            freq=freq,
+            block_size=block_size,
+            threshold=threshold,
+            tolerance=tolerance))
+    else:
+        for ch_no, channel in enumerate(signal.transpose()):
+            channel_results.append(get_anomaly_durations(
+                signal=channel,
+                rate=audio_file.samplerate,
+                freq=freq,
+                block_size=block_size,
+                threshold=threshold,
+                tolerance=tolerance))
+    return channel_results
diff --git a/acts_tests/acts_contrib/test_utils/audio_analysis_lib/audio_data.py b/acts_tests/acts_contrib/test_utils/audio_analysis_lib/audio_data.py
new file mode 100644
index 0000000..5867a48
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/audio_analysis_lib/audio_data.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+"""This module provides abstraction of audio data."""
+
+import contextlib
+import copy
+import numpy
+import struct
+from io import StringIO
+"""The dict containing information on how to parse sample from raw data.
+
+Keys: The sample format as in aplay command.
+Values: A dict containing:
+    message: Human-readable sample format.
+    dtype_str: Data type used in numpy dtype.  Check
+               https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html
+               for supported data type.
+    size_bytes: Number of bytes for one sample.
+"""
+SAMPLE_FORMATS = dict(
+    S32_LE=dict(
+        message='Signed 32-bit integer, little-endian',
+        dtype_str='<i',
+        size_bytes=4),
+    S16_LE=dict(
+        message='Signed 16-bit integer, little-endian',
+        dtype_str='<i',
+        size_bytes=2))
+
+
+def get_maximum_value_from_sample_format(sample_format):
+    """Gets the maximum value from sample format.
+
+    Args:
+        sample_format: A key in SAMPLE_FORMAT.
+
+    Returns:The maximum value the sample can hold + 1.
+
+    """
+    size_bits = SAMPLE_FORMATS[sample_format]['size_bytes'] * 8
+    return 1 << (size_bits - 1)
+
+
+class AudioRawDataError(Exception):
+    """Error in AudioRawData."""
+    pass
+
+
+class AudioRawData(object):
+    """The abstraction of audio raw data.
+
+    @property channel: The number of channels.
+    @property channel_data: A list of lists containing samples in each channel.
+                            E.g., The third sample in the second channel is
+                            channel_data[1][2].
+    @property sample_format: The sample format which should be one of the keys
+                             in audio_data.SAMPLE_FORMATS.
+    """
+
+    def __init__(self, binary, channel, sample_format):
+        """Initializes an AudioRawData.
+
+        Args:
+            binary: A string containing binary data. If binary is not None,
+                       The samples in binary will be parsed and be filled into
+                       channel_data.
+            channel: The number of channels.
+            sample_format: One of the keys in audio_data.SAMPLE_FORMATS.
+        """
+        self.channel = channel
+        self.channel_data = [[] for _ in range(self.channel)]
+        self.sample_format = sample_format
+        if binary:
+            self.read_binary(binary)
+
+    def read_binary(self, binary):
+        """Reads samples from binary and fills channel_data.
+
+        Reads samples of fixed width from binary string into a numpy array
+        and shapes them into each channel.
+
+        Args:
+            binary: A string containing binary data.
+        """
+        sample_format_dict = SAMPLE_FORMATS[self.sample_format]
+
+        # The data type used in numpy fromstring function. For example,
+        # <i4 for 32-bit signed int.
+        np_dtype = '%s%d' % (sample_format_dict['dtype_str'],
+                             sample_format_dict['size_bytes'])
+
+        # Reads data from a string into 1-D array.
+        np_array = numpy.fromstring(binary, dtype=np_dtype)
+
+        n_frames = len(np_array) / self.channel
+        # Reshape np_array into an array of shape (n_frames, channel).
+        np_array = np_array.reshape(int(n_frames), self.channel)
+        # Transpose np_arrya so it becomes of shape (channel, n_frames).
+        self.channel_data = np_array.transpose()
diff --git a/acts_tests/acts_contrib/test_utils/audio_analysis_lib/audio_quality_measurement.py b/acts_tests/acts_contrib/test_utils/audio_analysis_lib/audio_quality_measurement.py
new file mode 100644
index 0000000..c241fde
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/audio_analysis_lib/audio_quality_measurement.py
@@ -0,0 +1,929 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+"""This module provides utilities to detect some artifacts and measure the
+    quality of audio."""
+
+import logging
+import math
+import numpy
+
+import acts_contrib.test_utils.audio_analysis_lib.audio_analysis as audio_analysis
+
+# The input signal should be one sine wave with fixed frequency which
+# can have silence before and/or after sine wave.
+# For example:
+#   silence      sine wave      silence
+#  -----------|VVVVVVVVVVVVV|-----------
+#     (a)           (b)           (c)
+# This module detects these artifacts:
+#   1. Detect noise in (a) and (c).
+#   2. Detect delay in (b).
+#   3. Detect burst in (b).
+# Assume the transitions between (a)(b) and (b)(c) are smooth and
+# amplitude increases/decreases linearly.
+# This module will detect artifacts in the sine wave.
+# This module also estimates the equivalent noise level by teager operator.
+# This module also detects volume changes in the sine wave. However, volume
+# changes may be affected by delay or burst.
+# Some artifacts may cause each other.
+
+# In this module, amplitude and frequency are derived from Hilbert transform.
+# Both amplitude and frequency are a function of time.
+
+# To detect each artifact, each point will be compared with
+# average amplitude of its block. The block size will be 1.5 ms.
+# Using average amplitude can mitigate the error caused by
+# Hilbert transform and noise.
+# In some case, for more accuracy, the block size may be modified
+# to other values.
+DEFAULT_BLOCK_SIZE_SECS = 0.0015
+
+# If the difference between average frequency of this block and
+# dominant frequency of full signal is less than 0.5 times of
+# dominant frequency, this block is considered to be within the
+# sine wave. In most cases, if there is no sine wave(only noise),
+# average frequency will be much greater than 5 times of
+# dominant frequency.
+# Also, for delay during playback, the frequency will be about 0
+# in perfect situation or much greater than 5 times of dominant
+# frequency if it's noised.
+DEFAULT_FREQUENCY_ERROR = 0.5
+
+# If the amplitude of some sample is less than 0.6 times of the
+# average amplitude of its left/right block, it will be considered
+# as a delay during playing.
+DEFAULT_DELAY_AMPLITUDE_THRESHOLD = 0.6
+
+# If the average amplitude of the block before or after playing
+# is more than 0.5 times to the average amplitude of the wave,
+# it will be considered as a noise artifact.
+DEFAULT_NOISE_AMPLITUDE_THRESHOLD = 0.5
+
+# In the sine wave, if the amplitude is more than 1.4 times of
+# its left side and its right side, it will be considered as
+# a burst.
+DEFAULT_BURST_AMPLITUDE_THRESHOLD = 1.4
+
+# When detecting burst, if the amplitude is lower than 0.5 times
+# average amplitude, we ignore it.
+DEFAULT_BURST_TOO_SMALL = 0.5
+
+# For a signal which is the combination of sine wave with fixed frequency f and
+# amplitude 1 and standard noise with amplitude k, the average teager value is
+# nearly linear to the noise level k.
+# Given frequency f, we simulate a sine wave with default noise level and
+# calculate its average teager value. Then, we can estimate the equivalent
+# noise level of input signal by the average teager value of input signal.
+DEFAULT_STANDARD_NOISE = 0.005
+
+# For delay, burst, volume increasing/decreasing, if two delay(
+# burst, volume increasing/decreasing) happen within
+# DEFAULT_SAME_EVENT_SECS seconds, we consider they are the
+# same event.
+DEFAULT_SAME_EVENT_SECS = 0.001
+
+# When detecting increasing/decreasing volume of signal, if the amplitude
+# is lower than 0.1 times average amplitude, we ignore it.
+DEFAULT_VOLUME_CHANGE_TOO_SMALL = 0.1
+
+# If average amplitude of right block is less/more than average
+# amplitude of left block times DEFAULT_VOLUME_CHANGE_AMPLITUDE, it will be
+# considered as decreasing/increasing on volume.
+DEFAULT_VOLUME_CHANGE_AMPLITUDE = 0.1
+
+# If the increasing/decreasing volume event is too close to the start or the end
+# of sine wave, we consider its volume change as part of rising/falling phase in
+# the start/end.
+NEAR_START_OR_END_SECS = 0.01
+
+# After applying Hilbert transform, the resulting amplitude and frequency may be
+# extremely large in the start and/or the end part. Thus, we will append zeros
+# before and after the whole wave for 0.1 secs.
+APPEND_ZEROS_SECS = 0.1
+
+# If the noise event is too close to the start or the end of the data, we
+# consider its noise as part of artifacts caused by edge effect of Hilbert
+# transform.
+# For example, originally, the data duration is 10 seconds.
+# We append 0.1 seconds of zeros in the beginning and the end of the data, so
+# the data becomes 10.2 seocnds long.
+# Then, we apply Hilbert transform to 10.2 seconds of data.
+# Near 0.1 seconds and 10.1 seconds, there will be edge effect of Hilbert
+# transform. We do not want these be treated as noise.
+# If NEAR_DATA_START_OR_END_SECS is set to 0.01, then the noise happened
+# at [0, 0.11] and [10.09, 10.1] will be ignored.
+NEAR_DATA_START_OR_END_SECS = 0.01
+
+# If the noise event is too close to the start or the end of the sine wave in
+# the data, we consider its noise as part of artifacts caused by edge effect of
+# Hilbert transform.
+# A |-------------|vvvvvvvvvvvvvvvvvvvvvvv|-------------|
+# B |ooooooooo| d |                       | d |ooooooooo|
+#
+# A is full signal. It contains a sine wave and silence before and after sine
+# wave.
+# In B, |oooo| shows the parts that we are going to check for noise before/after
+# sine wave. | d | is determined by NEAR_SINE_START_OR_END_SECS.
+NEAR_SINE_START_OR_END_SECS = 0.01
+
+
+class SineWaveNotFound(Exception):
+    """Error when there's no sine wave found in the signal"""
+    pass
+
+
+def hilbert(x):
+    """Hilbert transform copied from scipy.
+
+    More information can be found here:
+    http://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.hilbert.html
+
+    Args:
+        x: Real signal data to transform.
+
+    Returns:
+        Analytic signal of x, we can further extract amplitude and
+              frequency from it.
+
+    """
+    x = numpy.asarray(x)
+    if numpy.iscomplexobj(x):
+        raise ValueError("x must be real.")
+    axis = -1
+    N = x.shape[axis]
+    if N <= 0:
+        raise ValueError("N must be positive.")
+
+    Xf = numpy.fft.fft(x, N, axis=axis)
+    h = numpy.zeros(N)
+    if N % 2 == 0:
+        h[0] = h[N // 2] = 1
+        h[1:N // 2] = 2
+    else:
+        h[0] = 1
+        h[1:(N + 1) // 2] = 2
+
+    if len(x.shape) > 1:
+        ind = [newaxis] * x.ndim
+        ind[axis] = slice(None)
+        h = h[ind]
+    x = numpy.fft.ifft(Xf * h, axis=axis)
+    return x
+
+
+def noised_sine_wave(frequency, rate, noise_level):
+    """Generates a sine wave of 2 second with specified noise level.
+
+    Args:
+        frequency: Frequency of sine wave.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        noise_level: Required noise level.
+
+    Returns:
+        A sine wave with specified noise level.
+
+    """
+    wave = []
+    for index in range(0, rate * 2):
+        sample = 2.0 * math.pi * frequency * float(index) / float(rate)
+        sine_wave = math.sin(sample)
+        noise = noise_level * numpy.random.standard_normal()
+        wave.append(sine_wave + noise)
+    return wave
+
+
+def average_teager_value(wave, amplitude):
+    """Computes the normalized average teager value.
+
+    After averaging the teager value, we will normalize the value by
+    dividing square of amplitude.
+
+    Args:
+        wave: Wave to apply teager operator.
+        amplitude: Average amplitude of given wave.
+
+    Returns:
+        Average teager value.
+
+    """
+    teager_value, length = 0, len(wave)
+    for i in range(1, length - 1):
+        ith_teager_value = abs(wave[i] * wave[i] - wave[i - 1] * wave[i + 1])
+        ith_teager_value *= max(1, abs(wave[i]))
+        teager_value += ith_teager_value
+    teager_value = (float(teager_value) / length) / (amplitude**2)
+    return teager_value
+
+
+def noise_level(amplitude, frequency, rate, teager_value_of_input):
+    """Computes the noise level compared with standard_noise.
+
+    For a signal which is the combination of sine wave with fixed frequency f
+    and amplitude 1 and standard noise with amplitude k, the average teager
+    value is nearly linear to the noise level k.
+    Thus, we can compute the average teager value of a sine wave with
+    standard_noise. Then, we can estimate the noise level of given input.
+
+    Args:
+        amplitude: Amplitude of input audio.
+        frequency: Dominant frequency of input audio.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        teager_value_of_input: Average teager value of input audio.
+
+    Returns:
+        A float value denotes the audio is equivalent to have how many times of
+            noise compared with its amplitude.For example, 0.02 denotes that the
+            wave has a noise which has standard distribution with standard
+            deviation being 0.02 times the amplitude of the wave.
+
+    """
+    standard_noise = DEFAULT_STANDARD_NOISE
+
+    # Generates the standard sine wave with stdandard_noise level of noise.
+    standard_wave = noised_sine_wave(frequency, rate, standard_noise)
+
+    # Calculates the average teager value.
+    teager_value_of_std_wave = average_teager_value(standard_wave, amplitude)
+
+    return (teager_value_of_input / teager_value_of_std_wave) * standard_noise
+
+
+def error(f1, f2):
+    """Calculates the relative error between f1 and f2.
+
+    Args:
+        f1: Exact value.
+        f2: Test value.
+
+    Returns:
+        Relative error between f1 and f2.
+
+    """
+    return abs(float(f1) - float(f2)) / float(f1)
+
+
+def hilbert_analysis(signal, rate, block_size):
+    """Finds amplitude and frequency of each time of signal by Hilbert transform.
+
+    Args:
+        signal: The wave to analyze.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        block_size: The size of block to transform.
+
+    Returns:
+        A tuple of list: (amplitude, frequency) composed of amplitude and
+            frequency of each time.
+
+    """
+    # To apply Hilbert transform, the wave will be transformed
+    # segment by segment. For each segment, its size will be
+    # block_size and we will only take middle part of it.
+    # Thus, each segment looks like: |-----|=====|=====|-----|.
+    # "=...=" part will be taken while "-...-" part will be ignored.
+    #
+    # The whole size of taken part will be half of block_size
+    # which will be hilbert_block.
+    # The size of each ignored part will be half of hilbert_block
+    # which will be half_hilbert_block.
+    hilbert_block = block_size // 2
+    half_hilbert_block = hilbert_block // 2
+    # As mentioned above, for each block, we will only take middle
+    # part of it. Thus, the whole transformation will be completed as:
+    # |=====|=====|-----|           |-----|=====|=====|-----|
+    #       |-----|=====|=====|-----|           |-----|=====|=====|
+    #                   |-----|=====|=====|-----|
+    # Specially, beginning and ending part may not have ignored part.
+    length = len(signal)
+    result = []
+    for left_border in range(0, length, hilbert_block):
+        right_border = min(length, left_border + hilbert_block)
+        temp_left_border = max(0, left_border - half_hilbert_block)
+        temp_right_border = min(length, right_border + half_hilbert_block)
+        temp = hilbert(signal[temp_left_border:temp_right_border])
+        for index in range(left_border, right_border):
+            result.append(temp[index - temp_left_border])
+    result = numpy.asarray(result)
+    amplitude = numpy.abs(result)
+    phase = numpy.unwrap(numpy.angle(result))
+    frequency = numpy.diff(phase) / (2.0 * numpy.pi) * rate
+    #frequency.append(frequency[len(frequency)-1])
+    frequecny = numpy.append(frequency, frequency[len(frequency) - 1])
+    return (amplitude, frequency)
+
+
+def find_block_average_value(arr, side_block_size, block_size):
+    """For each index, finds average value of its block, left block, right block.
+
+    It will find average value for each index in the range.
+
+    For each index, the range of its block is
+        [max(0, index - block_size / 2), min(length - 1, index + block_size / 2)]
+    For each index, the range of its left block is
+        [max(0, index - size_block_size), index]
+    For each index, the range of its right block is
+        [index, min(length - 1, index + side_block_size)]
+
+    Args:
+        arr: The array to be computed.
+        side_block_size: the size of the left_block and right_block.
+        block_size: the size of the block.
+
+    Returns:
+        A tuple of lists: (left_block_average_array,
+                                 right_block_average_array,
+                                 block_average_array)
+    """
+    length = len(arr)
+    left_border, right_border = 0, 1
+    left_block_sum = arr[0]
+    right_block_sum = arr[0]
+    left_average_array = numpy.zeros(length)
+    right_average_array = numpy.zeros(length)
+    block_average_array = numpy.zeros(length)
+    for index in range(0, length):
+        while left_border < index - side_block_size:
+            left_block_sum -= arr[left_border]
+            left_border += 1
+        while right_border < min(length, index + side_block_size):
+            right_block_sum += arr[right_border]
+            right_border += 1
+
+        left_average_value = float(left_block_sum) / (index - left_border + 1)
+        right_average_value = float(right_block_sum) / (right_border - index)
+        left_average_array[index] = left_average_value
+        right_average_array[index] = right_average_value
+
+        if index + 1 < length:
+            left_block_sum += arr[index + 1]
+        right_block_sum -= arr[index]
+    left_border, right_border = 0, 1
+    block_sum = 0
+    for index in range(0, length):
+        while left_border < index - block_size / 2:
+            block_sum -= arr[left_border]
+            left_border += 1
+        while right_border < min(length, index + block_size / 2):
+            block_sum += arr[right_border]
+            right_border += 1
+
+        average_value = float(block_sum) / (right_border - left_border)
+        block_average_array[index] = average_value
+    return (left_average_array, right_average_array, block_average_array)
+
+
+def find_start_end_index(dominant_frequency, block_frequency_delta, block_size,
+                         frequency_error_threshold):
+    """Finds start and end index of sine wave.
+
+    For each block with size of block_size, we check that whether its frequency
+    is close enough to the dominant_frequency. If yes, we will consider this
+    block to be within the sine wave.
+    Then, it will return the start and end index of sine wave indicating that
+    sine wave is between [start_index, end_index)
+    It's okay if the whole signal only contains sine wave.
+
+    Args:
+        dominant_frequency: Dominant frequency of signal.
+        block_frequency_delta: Average absolute difference between dominant
+                                  frequency and frequency of each block. For
+                                  each index, its block is
+                                  [max(0, index - block_size / 2),
+                                   min(length - 1, index + block_size / 2)]
+        block_size: Block size in samples.
+
+    Returns:
+        A tuple composed of (start_index, end_index)
+
+    """
+    length = len(block_frequency_delta)
+
+    # Finds the start/end time index of playing based on dominant frequency
+    start_index, end_index = length - 1, 0
+    for index in range(0, length):
+        left_border = max(0, index - block_size / 2)
+        right_border = min(length - 1, index + block_size / 2)
+        frequency_error = block_frequency_delta[index] / dominant_frequency
+        if frequency_error < frequency_error_threshold:
+            start_index = min(start_index, left_border)
+            end_index = max(end_index, right_border + 1)
+    return (start_index, end_index)
+
+
+def noise_detection(start_index, end_index, block_amplitude, average_amplitude,
+                    rate, noise_amplitude_threshold):
+    """Detects noise before/after sine wave.
+
+    If average amplitude of some sample's block before start of wave or after
+    end of wave is more than average_amplitude times noise_amplitude_threshold,
+    it will be considered as a noise.
+
+    Args:
+        start_index: Start index of sine wave.
+        end_index: End index of sine wave.
+        block_amplitude: An array for average amplitude of each block, where
+                            amplitude is computed from Hilbert transform.
+        average_amplitude: Average amplitude of sine wave.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        noise_amplitude_threshold: If the average amplitude of a block is
+                        higher than average amplitude of the wave times
+                        noise_amplitude_threshold, it will be considered as
+                        noise before/after playback.
+
+    Returns:
+        A tuple of lists indicating the time that noise happens:
+            (noise_before_playing, noise_after_playing).
+
+    """
+    length = len(block_amplitude)
+    amplitude_threshold = average_amplitude * noise_amplitude_threshold
+    same_event_samples = rate * DEFAULT_SAME_EVENT_SECS
+
+    # Detects noise before playing.
+    noise_time_point = []
+    last_noise_end_time_point = []
+    previous_noise_index = None
+    times = 0
+    for index in range(0, length):
+        # Ignore noise too close to the beginning or the end of sine wave.
+        # Check the docstring of NEAR_SINE_START_OR_END_SECS.
+        if ((start_index - rate * NEAR_SINE_START_OR_END_SECS) <= index and
+            (index < end_index + rate * NEAR_SINE_START_OR_END_SECS)):
+            continue
+
+        # Ignore noise too close to the beginning or the end of original data.
+        # Check the docstring of NEAR_DATA_START_OR_END_SECS.
+        if (float(index) / rate <=
+                NEAR_DATA_START_OR_END_SECS + APPEND_ZEROS_SECS):
+            continue
+        if (float(length - index) / rate <=
+                NEAR_DATA_START_OR_END_SECS + APPEND_ZEROS_SECS):
+            continue
+        if block_amplitude[index] > amplitude_threshold:
+            same_event = False
+            if previous_noise_index:
+                same_event = (index - previous_noise_index
+                              ) < same_event_samples
+            if not same_event:
+                index_start_sec = float(index) / rate - APPEND_ZEROS_SECS
+                index_end_sec = float(index + 1) / rate - APPEND_ZEROS_SECS
+                noise_time_point.append(index_start_sec)
+                last_noise_end_time_point.append(index_end_sec)
+                times += 1
+            index_end_sec = float(index + 1) / rate - APPEND_ZEROS_SECS
+            last_noise_end_time_point[times - 1] = index_end_sec
+            previous_noise_index = index
+
+    noise_before_playing, noise_after_playing = [], []
+    for i in range(times):
+        duration = last_noise_end_time_point[i] - noise_time_point[i]
+        if noise_time_point[i] < float(start_index) / rate - APPEND_ZEROS_SECS:
+            noise_before_playing.append((noise_time_point[i], duration))
+        else:
+            noise_after_playing.append((noise_time_point[i], duration))
+
+    return (noise_before_playing, noise_after_playing)
+
+
+def delay_detection(start_index, end_index, block_amplitude, average_amplitude,
+                    dominant_frequency, rate, left_block_amplitude,
+                    right_block_amplitude, block_frequency_delta,
+                    delay_amplitude_threshold, frequency_error_threshold):
+    """Detects delay during playing.
+
+    For each sample, we will check whether the average amplitude of its block
+    is less than average amplitude of its left block and its right block times
+    delay_amplitude_threshold. Also, we will check whether the frequency of
+    its block is far from the dominant frequency.
+    If at least one constraint fulfilled, it will be considered as a delay.
+
+    Args:
+        start_index: Start index of sine wave.
+        end_index: End index of sine wave.
+        block_amplitude: An array for average amplitude of each block, where
+                            amplitude is computed from Hilbert transform.
+        average_amplitude: Average amplitude of sine wave.
+        dominant_frequency: Dominant frequency of signal.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        left_block_amplitude: Average amplitude of left block of each index.
+                                Ref to find_block_average_value function.
+        right_block_amplitude: Average amplitude of right block of each index.
+                                Ref to find_block_average_value function.
+        block_frequency_delta: Average absolute difference frequency to
+                                dominant frequency of block of each index.
+                                Ref to find_block_average_value function.
+        delay_amplitude_threshold: If the average amplitude of a block is
+                        lower than average amplitude of the wave times
+                        delay_amplitude_threshold, it will be considered
+                        as delay.
+        frequency_error_threshold: Ref to DEFAULT_FREQUENCY_ERROR
+
+    Returns:
+        List of delay occurrence:
+                [(time_1, duration_1), (time_2, duration_2), ...],
+              where time and duration are in seconds.
+
+    """
+    delay_time_points = []
+    last_delay_end_time_points = []
+    previous_delay_index = None
+    times = 0
+    same_event_samples = rate * DEFAULT_SAME_EVENT_SECS
+    start_time = float(start_index) / rate - APPEND_ZEROS_SECS
+    end_time = float(end_index) / rate - APPEND_ZEROS_SECS
+    for index in range(int(start_index), int(end_index)):
+        if block_amplitude[
+                index] > average_amplitude * delay_amplitude_threshold:
+            continue
+        now_time = float(index) / rate - APPEND_ZEROS_SECS
+        if abs(now_time - start_time) < NEAR_START_OR_END_SECS:
+            continue
+        if abs(now_time - end_time) < NEAR_START_OR_END_SECS:
+            continue
+        # If amplitude less than its left/right side and small enough,
+        # it will be considered as a delay.
+        amp_threshold = average_amplitude * delay_amplitude_threshold
+        left_threshold = delay_amplitude_threshold * left_block_amplitude[
+            index]
+        amp_threshold = min(amp_threshold, left_threshold)
+        right_threshold = delay_amplitude_threshold * right_block_amplitude[
+            index]
+        amp_threshold = min(amp_threshold, right_threshold)
+
+        frequency_error = block_frequency_delta[index] / dominant_frequency
+
+        amplitude_too_small = block_amplitude[index] < amp_threshold
+        frequency_not_match = frequency_error > frequency_error_threshold
+
+        if amplitude_too_small or frequency_not_match:
+            same_event = False
+            if previous_delay_index:
+                same_event = (index - previous_delay_index
+                              ) < same_event_samples
+            if not same_event:
+                index_start_sec = float(index) / rate - APPEND_ZEROS_SECS
+                index_end_sec = float(index + 1) / rate - APPEND_ZEROS_SECS
+                delay_time_points.append(index_start_sec)
+                last_delay_end_time_points.append(index_end_sec)
+                times += 1
+            previous_delay_index = index
+            index_end_sec = float(index + 1) / rate - APPEND_ZEROS_SECS
+            last_delay_end_time_points[times - 1] = index_end_sec
+
+    delay_list = []
+    for i in range(len(delay_time_points)):
+        duration = last_delay_end_time_points[i] - delay_time_points[i]
+        delay_list.append((delay_time_points[i], duration))
+    return delay_list
+
+
+def burst_detection(start_index, end_index, block_amplitude, average_amplitude,
+                    dominant_frequency, rate, left_block_amplitude,
+                    right_block_amplitude, block_frequency_delta,
+                    burst_amplitude_threshold, frequency_error_threshold):
+    """Detects burst during playing.
+
+    For each sample, we will check whether the average amplitude of its block is
+    more than average amplitude of its left block and its right block times
+    burst_amplitude_threshold. Also, we will check whether the frequency of
+    its block is not compatible to the dominant frequency.
+    If at least one constraint fulfilled, it will be considered as a burst.
+
+    Args:
+        start_index: Start index of sine wave.
+        end_index: End index of sine wave.
+        block_amplitude: An array for average amplitude of each block, where
+                            amplitude is computed from Hilbert transform.
+        average_amplitude: Average amplitude of sine wave.
+        dominant_frequency: Dominant frequency of signal.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        left_block_amplitude: Average amplitude of left block of each index.
+                                Ref to find_block_average_value function.
+        right_block_amplitude: Average amplitude of right block of each index.
+                                Ref to find_block_average_value function.
+        block_frequency_delta: Average absolute difference frequency to
+                                dominant frequency of block of each index.
+        burst_amplitude_threshold: If the amplitude is higher than average
+                            amplitude of its left block and its right block
+                            times burst_amplitude_threshold. It will be
+                            considered as a burst.
+        frequency_error_threshold: Ref to DEFAULT_FREQUENCY_ERROR
+
+    Returns:
+        List of burst occurence: [time_1, time_2, ...],
+              where time is in seconds.
+
+    """
+    burst_time_points = []
+    previous_burst_index = None
+    same_event_samples = rate * DEFAULT_SAME_EVENT_SECS
+    for index in range(int(start_index), int(end_index)):
+        # If amplitude higher than its left/right side and large enough,
+        # it will be considered as a burst.
+        if block_amplitude[
+                index] <= average_amplitude * DEFAULT_BURST_TOO_SMALL:
+            continue
+        if abs(index - start_index) < rate * NEAR_START_OR_END_SECS:
+            continue
+        if abs(index - end_index) < rate * NEAR_START_OR_END_SECS:
+            continue
+        amp_threshold = average_amplitude * DEFAULT_BURST_TOO_SMALL
+        left_threshold = burst_amplitude_threshold * left_block_amplitude[
+            index]
+        amp_threshold = max(amp_threshold, left_threshold)
+        right_threshold = burst_amplitude_threshold * right_block_amplitude[
+            index]
+        amp_threshold = max(amp_threshold, right_threshold)
+
+        frequency_error = block_frequency_delta[index] / dominant_frequency
+
+        amplitude_too_large = block_amplitude[index] > amp_threshold
+        frequency_not_match = frequency_error > frequency_error_threshold
+
+        if amplitude_too_large or frequency_not_match:
+            same_event = False
+            if previous_burst_index:
+                same_event = index - previous_burst_index < same_event_samples
+            if not same_event:
+                burst_time_points.append(
+                    float(index) / rate - APPEND_ZEROS_SECS)
+            previous_burst_index = index
+
+    return burst_time_points
+
+
+def changing_volume_detection(start_index, end_index, average_amplitude, rate,
+                              left_block_amplitude, right_block_amplitude,
+                              volume_changing_amplitude_threshold):
+    """Finds volume changing during playback.
+
+    For each index, we will compare average amplitude of its left block and its
+    right block. If average amplitude of right block is more than average
+    amplitude of left block times (1 + DEFAULT_VOLUME_CHANGE_AMPLITUDE), it will
+    be considered as an increasing volume. If the one of right block is less
+    than that of left block times (1 - DEFAULT_VOLUME_CHANGE_AMPLITUDE), it will
+    be considered as a decreasing volume.
+
+    Args:
+        start_index: Start index of sine wave.
+        end_index: End index of sine wave.
+        average_amplitude: Average amplitude of sine wave.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        left_block_amplitude: Average amplitude of left block of each index.
+                                Ref to find_block_average_value function.
+        right_block_amplitude: Average amplitude of right block of each index.
+                                Ref to find_block_average_value function.
+        volume_changing_amplitude_threshold: If the average amplitude of right
+                                                block is higher or lower than
+                                                that of left one times this
+                                                value, it will be considered as
+                                                a volume change.
+                                                Also refer to
+                                                DEFAULT_VOLUME_CHANGE_AMPLITUDE
+
+    Returns:
+        List of volume changing composed of 1 for increasing and -1 for
+            decreasing.
+
+    """
+    length = len(left_block_amplitude)
+
+    # Detects rising and/or falling volume.
+    previous_rising_index, previous_falling_index = None, None
+    changing_time = []
+    changing_events = []
+    amplitude_threshold = average_amplitude * DEFAULT_VOLUME_CHANGE_TOO_SMALL
+    same_event_samples = rate * DEFAULT_SAME_EVENT_SECS
+    for index in range(int(start_index), int(end_index)):
+        # Skips if amplitude is too small.
+        if left_block_amplitude[index] < amplitude_threshold:
+            continue
+        if right_block_amplitude[index] < amplitude_threshold:
+            continue
+        # Skips if changing is from start or end time
+        if float(abs(start_index - index)) / rate < NEAR_START_OR_END_SECS:
+            continue
+        if float(abs(end_index - index)) / rate < NEAR_START_OR_END_SECS:
+            continue
+
+        delta_margin = volume_changing_amplitude_threshold
+        if left_block_amplitude[index] > 0:
+            delta_margin *= left_block_amplitude[index]
+
+        increasing_threshold = left_block_amplitude[index] + delta_margin
+        decreasing_threshold = left_block_amplitude[index] - delta_margin
+
+        if right_block_amplitude[index] > increasing_threshold:
+            same_event = False
+            if previous_rising_index:
+                same_event = index - previous_rising_index < same_event_samples
+            if not same_event:
+                changing_time.append(float(index) / rate - APPEND_ZEROS_SECS)
+                changing_events.append(+1)
+            previous_rising_index = index
+        if right_block_amplitude[index] < decreasing_threshold:
+            same_event = False
+            if previous_falling_index:
+                same_event = index - previous_falling_index < same_event_samples
+            if not same_event:
+                changing_time.append(float(index) / rate - APPEND_ZEROS_SECS)
+                changing_events.append(-1)
+            previous_falling_index = index
+
+    # Combines consecutive increasing/decreasing event.
+    combined_changing_events, prev = [], 0
+    for i in range(len(changing_events)):
+        if changing_events[i] == prev:
+            continue
+        combined_changing_events.append((changing_time[i], changing_events[i]))
+        prev = changing_events[i]
+    return combined_changing_events
+
+
+def quality_measurement(
+        signal,
+        rate,
+        dominant_frequency=None,
+        block_size_secs=DEFAULT_BLOCK_SIZE_SECS,
+        frequency_error_threshold=DEFAULT_FREQUENCY_ERROR,
+        delay_amplitude_threshold=DEFAULT_DELAY_AMPLITUDE_THRESHOLD,
+        noise_amplitude_threshold=DEFAULT_NOISE_AMPLITUDE_THRESHOLD,
+        burst_amplitude_threshold=DEFAULT_BURST_AMPLITUDE_THRESHOLD,
+        volume_changing_amplitude_threshold=DEFAULT_VOLUME_CHANGE_AMPLITUDE):
+    """Detects several artifacts and estimates the noise level.
+
+    This method detects artifact before playing, after playing, and delay
+    during playing. Also, it estimates the noise level of the signal.
+    To avoid the influence of noise, it calculates amplitude and frequency
+    block by block.
+
+    Args:
+        signal: A list of numbers for one-channel PCM data. The data should
+                   be normalized to [-1,1].
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        dominant_frequency: Dominant frequency of signal. Set None to
+                               recalculate the frequency in this function.
+        block_size_secs: Block size in seconds. The measurement will be done
+                            block-by-block using average amplitude and frequency
+                            in each block to avoid noise.
+        frequency_error_threshold: Ref to DEFAULT_FREQUENCY_ERROR.
+        delay_amplitude_threshold: If the average amplitude of a block is
+                                      lower than average amplitude of the wave
+                                      times delay_amplitude_threshold, it will
+                                      be considered as delay.
+                                      Also refer to delay_detection and
+                                      DEFAULT_DELAY_AMPLITUDE_THRESHOLD.
+        noise_amplitude_threshold: If the average amplitude of a block is
+                                      higher than average amplitude of the wave
+                                      times noise_amplitude_threshold, it will
+                                      be considered as noise before/after
+                                      playback.
+                                      Also refer to noise_detection and
+                                      DEFAULT_NOISE_AMPLITUDE_THRESHOLD.
+        burst_amplitude_threshold: If the average amplitude of a block is
+                                      higher than average amplitude of its left
+                                      block and its right block times
+                                      burst_amplitude_threshold. It will be
+                                      considered as a burst.
+                                      Also refer to burst_detection and
+                                      DEFAULT_BURST_AMPLITUDE_THRESHOLD.
+        volume_changing_amplitude_threshold: If the average amplitude of right
+                                                block is higher or lower than
+                                                that of left one times this
+                                                value, it will be considered as
+                                                a volume change.
+                                                Also refer to
+                                                changing_volume_detection and
+                                                DEFAULT_VOLUME_CHANGE_AMPLITUDE
+
+    Returns:
+        A dictoinary of detection/estimation:
+              {'artifacts':
+                {'noise_before_playback':
+                    [(time_1, duration_1), (time_2, duration_2), ...],
+                 'noise_after_playback':
+                    [(time_1, duration_1), (time_2, duration_2), ...],
+                 'delay_during_playback':
+                    [(time_1, duration_1), (time_2, duration_2), ...],
+                 'burst_during_playback':
+                    [time_1, time_2, ...]
+                },
+               'volume_changes':
+                 [(time_1, flag_1), (time_2, flag_2), ...],
+               'equivalent_noise_level': level
+              }
+              where durations and time points are in seconds. And,
+              equivalence_noise_level is the quotient of noise and wave which
+              refers to DEFAULT_STANDARD_NOISE. volume_changes is a list of
+              tuples containing time stamps and decreasing/increasing flags for
+              volume change events.
+
+    """
+    # Calculates the block size, from seconds to samples.
+    block_size = int(block_size_secs * rate)
+
+    signal = numpy.concatenate(
+        (numpy.zeros(int(rate * APPEND_ZEROS_SECS)), signal,
+         numpy.zeros(int(rate * APPEND_ZEROS_SECS))))
+    signal = numpy.array(signal, dtype=float)
+    length = len(signal)
+
+    # Calculates the amplitude and frequency.
+    amplitude, frequency = hilbert_analysis(signal, rate, block_size)
+
+    # Finds the dominant frequency.
+    if not dominant_frequency:
+        dominant_frequency = audio_analysis.spectral_analysis(signal,
+                                                              rate)[0][0]
+
+    # Finds the array which contains absolute difference between dominant
+    # frequency and frequency at each time point.
+    frequency_delta = abs(frequency - dominant_frequency)
+
+    # Computes average amplitude of each type of block
+    res = find_block_average_value(amplitude, block_size * 2, block_size)
+    left_block_amplitude, right_block_amplitude, block_amplitude = res
+
+    # Computes average absolute difference of frequency and dominant frequency
+    # of the block of each index
+    _, _, block_frequency_delta = find_block_average_value(
+        frequency_delta, block_size * 2, block_size)
+
+    # Finds start and end index of sine wave.
+    start_index, end_index = find_start_end_index(
+        dominant_frequency, block_frequency_delta, block_size,
+        frequency_error_threshold)
+
+    if start_index > end_index:
+        raise SineWaveNotFound('No sine wave found in signal')
+
+    logging.debug('Found sine wave: start: %s, end: %s',
+                  float(start_index) / rate - APPEND_ZEROS_SECS,
+                  float(end_index) / rate - APPEND_ZEROS_SECS)
+
+    sum_of_amplitude = float(sum(amplitude[int(start_index):int(end_index)]))
+    # Finds average amplitude of sine wave.
+    average_amplitude = sum_of_amplitude / (end_index - start_index)
+
+    # Finds noise before and/or after playback.
+    noise_before_playing, noise_after_playing = noise_detection(
+        start_index, end_index, block_amplitude, average_amplitude, rate,
+        noise_amplitude_threshold)
+
+    # Finds delay during playback.
+    delays = delay_detection(start_index, end_index, block_amplitude,
+                             average_amplitude, dominant_frequency, rate,
+                             left_block_amplitude, right_block_amplitude,
+                             block_frequency_delta, delay_amplitude_threshold,
+                             frequency_error_threshold)
+
+    # Finds burst during playback.
+    burst_time_points = burst_detection(
+        start_index, end_index, block_amplitude, average_amplitude,
+        dominant_frequency, rate, left_block_amplitude, right_block_amplitude,
+        block_frequency_delta, burst_amplitude_threshold,
+        frequency_error_threshold)
+
+    # Finds volume changing during playback.
+    volume_changes = changing_volume_detection(
+        start_index, end_index, average_amplitude, rate, left_block_amplitude,
+        right_block_amplitude, volume_changing_amplitude_threshold)
+
+    # Calculates the average teager value.
+    teager_value = average_teager_value(
+        signal[int(start_index):int(end_index)], average_amplitude)
+
+    # Finds out the noise level.
+    noise = noise_level(average_amplitude, dominant_frequency, rate,
+                        teager_value)
+
+    return {
+        'artifacts': {
+            'noise_before_playback': noise_before_playing,
+            'noise_after_playback': noise_after_playing,
+            'delay_during_playback': delays,
+            'burst_during_playback': burst_time_points
+        },
+        'volume_changes': volume_changes,
+        'equivalent_noise_level': noise
+    }
diff --git a/acts_tests/acts_contrib/test_utils/audio_analysis_lib/check_quality.py b/acts_tests/acts_contrib/test_utils/audio_analysis_lib/check_quality.py
new file mode 100644
index 0000000..ad41759
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/audio_analysis_lib/check_quality.py
@@ -0,0 +1,555 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+"""Audio Analysis tool to analyze wave file and detect artifacts."""
+
+import argparse
+import collections
+import json
+import logging
+import math
+import numpy
+import os
+import pprint
+import subprocess
+import tempfile
+import wave
+
+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
+import acts_contrib.test_utils.audio_analysis_lib.audio_quality_measurement as \
+ audio_quality_measurement
+
+# Holder for quality parameters used in audio_quality_measurement module.
+QualityParams = collections.namedtuple('QualityParams', [
+    'block_size_secs', 'frequency_error_threshold',
+    'delay_amplitude_threshold', 'noise_amplitude_threshold',
+    'burst_amplitude_threshold'
+])
+
+DEFAULT_QUALITY_BLOCK_SIZE_SECS = 0.0015
+DEFAULT_BURST_AMPLITUDE_THRESHOLD = 1.4
+DEFAULT_DELAY_AMPLITUDE_THRESHOLD = 0.6
+DEFAULT_FREQUENCY_ERROR_THRESHOLD = 0.5
+DEFAULT_NOISE_AMPLITUDE_THRESHOLD = 0.5
+
+
+class WaveFileException(Exception):
+    """Error in WaveFile."""
+    pass
+
+
+class WaveFormatExtensibleException(Exception):
+    """Wave file is in WAVE_FORMAT_EXTENSIBLE format which is not supported."""
+    pass
+
+
+class WaveFile(object):
+    """Class which handles wave file reading.
+
+    Properties:
+        raw_data: audio_data.AudioRawData object for data in wave file.
+        rate: sampling rate.
+
+    """
+
+    def __init__(self, filename):
+        """Inits a wave file.
+
+        Args:
+            filename: file name of the wave file.
+
+        """
+        self.raw_data = None
+        self.rate = None
+
+        self._wave_reader = None
+        self._n_channels = None
+        self._sample_width_bits = None
+        self._n_frames = None
+        self._binary = None
+
+        try:
+            self._read_wave_file(filename)
+        except WaveFormatExtensibleException:
+            logging.warning(
+                'WAVE_FORMAT_EXTENSIBLE is not supproted. '
+                'Try command "sox in.wav -t wavpcm out.wav" to convert '
+                'the file to WAVE_FORMAT_PCM format.')
+            self._convert_and_read_wav_file(filename)
+
+    def _convert_and_read_wav_file(self, filename):
+        """Converts the wav file and read it.
+
+        Converts the file into WAVE_FORMAT_PCM format using sox command and
+        reads its content.
+
+        Args:
+            filename: The wave file to be read.
+
+        Raises:
+            RuntimeError: sox is not installed.
+
+        """
+        # Checks if sox is installed.
+        try:
+            subprocess.check_output(['sox', '--version'])
+        except:
+            raise RuntimeError('sox command is not installed. '
+                               'Try sudo apt-get install sox')
+
+        with tempfile.NamedTemporaryFile(suffix='.wav') as converted_file:
+            command = ['sox', filename, '-t', 'wavpcm', converted_file.name]
+            logging.debug('Convert the file using sox: %s', command)
+            subprocess.check_call(command)
+            self._read_wave_file(converted_file.name)
+
+    def _read_wave_file(self, filename):
+        """Reads wave file header and samples.
+
+        Args:
+            filename: The wave file to be read.
+
+        @raises WaveFormatExtensibleException: Wave file is in
+                                               WAVE_FORMAT_EXTENSIBLE format.
+        @raises WaveFileException: Wave file format is not supported.
+
+        """
+        try:
+            self._wave_reader = wave.open(filename, 'r')
+            self._read_wave_header()
+            self._read_wave_binary()
+        except wave.Error as e:
+            if 'unknown format: 65534' in str(e):
+                raise WaveFormatExtensibleException()
+            else:
+                logging.exception('Unsupported wave format')
+                raise WaveFileException()
+        finally:
+            if self._wave_reader:
+                self._wave_reader.close()
+
+    def _read_wave_header(self):
+        """Reads wave file header.
+
+        @raises WaveFileException: wave file is compressed.
+
+        """
+        # Header is a tuple of
+        # (nchannels, sampwidth, framerate, nframes, comptype, compname).
+        header = self._wave_reader.getparams()
+        logging.debug('Wave header: %s', header)
+
+        self._n_channels = header[0]
+        self._sample_width_bits = header[1] * 8
+        self.rate = header[2]
+        self._n_frames = header[3]
+        comptype = header[4]
+        compname = header[5]
+
+        if comptype != 'NONE' or compname != 'not compressed':
+            raise WaveFileException('Can not support compressed wav file.')
+
+    def _read_wave_binary(self):
+        """Reads in samples in wave file."""
+        self._binary = self._wave_reader.readframes(self._n_frames)
+        format_str = 'S%d_LE' % self._sample_width_bits
+        self.raw_data = audio_data.AudioRawData(
+            binary=self._binary,
+            channel=self._n_channels,
+            sample_format=format_str)
+
+
+class QualityCheckerError(Exception):
+    """Error in QualityChecker."""
+    pass
+
+
+class CompareFailure(QualityCheckerError):
+    """Exception when frequency comparison fails."""
+    pass
+
+
+class QualityFailure(QualityCheckerError):
+    """Exception when quality check fails."""
+    pass
+
+
+class QualityChecker(object):
+    """Quality checker controls the flow of checking quality of raw data."""
+
+    def __init__(self, raw_data, rate):
+        """Inits a quality checker.
+
+        Args:
+            raw_data: An audio_data.AudioRawData object.
+            rate: Sampling rate in samples per second. Example inputs: 44100,
+            48000
+
+        """
+        self._raw_data = raw_data
+        self._rate = rate
+        self._spectrals = []
+        self._quality_result = []
+
+    def do_spectral_analysis(self, ignore_high_freq, check_quality,
+                             quality_params):
+        """Gets the spectral_analysis result.
+
+        Args:
+            ignore_high_freq: Ignore high frequencies above this threshold.
+            check_quality: Check quality of each channel.
+            quality_params: A QualityParams object for quality measurement.
+
+        """
+        self.has_data()
+        for channel_idx in range(self._raw_data.channel):
+            signal = self._raw_data.channel_data[channel_idx]
+            max_abs = max(numpy.abs(signal))
+            logging.debug('Channel %d max abs signal: %f', channel_idx,
+                          max_abs)
+            if max_abs == 0:
+                logging.info('No data on channel %d, skip this channel',
+                             channel_idx)
+                continue
+
+            saturate_value = audio_data.get_maximum_value_from_sample_format(
+                self._raw_data.sample_format)
+            normalized_signal = audio_analysis.normalize_signal(
+                signal, saturate_value)
+            logging.debug('saturate_value: %f', saturate_value)
+            logging.debug('max signal after normalized: %f',
+                          max(normalized_signal))
+            spectral = audio_analysis.spectral_analysis(
+                normalized_signal, self._rate)
+
+            logging.debug('Channel %d spectral:\n%s', channel_idx,
+                          pprint.pformat(spectral))
+
+            # Ignore high frequencies above the threshold.
+            spectral = [(f, c) for (f, c) in spectral if f < ignore_high_freq]
+
+            logging.info('Channel %d spectral after ignoring high frequencies '
+                         'above %f:\n%s', channel_idx, ignore_high_freq,
+                         pprint.pformat(spectral))
+
+            try:
+                if check_quality:
+                    quality = audio_quality_measurement.quality_measurement(
+                        signal=normalized_signal,
+                        rate=self._rate,
+                        dominant_frequency=spectral[0][0],
+                        block_size_secs=quality_params.block_size_secs,
+                        frequency_error_threshold=quality_params.
+                        frequency_error_threshold,
+                        delay_amplitude_threshold=quality_params.
+                        delay_amplitude_threshold,
+                        noise_amplitude_threshold=quality_params.
+                        noise_amplitude_threshold,
+                        burst_amplitude_threshold=quality_params.
+                        burst_amplitude_threshold)
+
+                    logging.debug('Channel %d quality:\n%s', channel_idx,
+                                  pprint.pformat(quality))
+                    self._quality_result.append(quality)
+                self._spectrals.append(spectral)
+            except Exception as error:
+                logging.warning(
+                    "Failed to analyze channel {} with error: {}".format(
+                        channel_idx, error))
+
+    def has_data(self):
+        """Checks if data has been set.
+
+        Raises:
+            QualityCheckerError: if data or rate is not set yet.
+
+        """
+        if not self._raw_data or not self._rate:
+            raise QualityCheckerError('Data and rate is not set yet')
+
+    def check_freqs(self, expected_freqs, freq_threshold):
+        """Checks the dominant frequencies in the channels.
+
+        Args:
+            expected_freq: A list of frequencies. If frequency is 0, it
+                              means this channel should be ignored.
+            freq_threshold: The difference threshold to compare two
+                               frequencies.
+
+        """
+        logging.debug('expected_freqs: %s', expected_freqs)
+        for idx, expected_freq in enumerate(expected_freqs):
+            if expected_freq == 0:
+                continue
+            if not self._spectrals[idx]:
+                raise CompareFailure(
+                    'Failed at channel %d: no dominant frequency' % idx)
+            dominant_freq = self._spectrals[idx][0][0]
+            if abs(dominant_freq - expected_freq) > freq_threshold:
+                raise CompareFailure(
+                    'Failed at channel %d: %f is too far away from %f' %
+                    (idx, dominant_freq, expected_freq))
+
+    def check_quality(self):
+        """Checks the quality measurement results on each channel.
+
+        Raises:
+            QualityFailure when there is artifact.
+
+        """
+        error_msgs = []
+
+        for idx, quality_res in enumerate(self._quality_result):
+            artifacts = quality_res['artifacts']
+            if artifacts['noise_before_playback']:
+                error_msgs.append('Found noise before playback: %s' %
+                                  (artifacts['noise_before_playback']))
+            if artifacts['noise_after_playback']:
+                error_msgs.append('Found noise after playback: %s' %
+                                  (artifacts['noise_after_playback']))
+            if artifacts['delay_during_playback']:
+                error_msgs.append('Found delay during playback: %s' %
+                                  (artifacts['delay_during_playback']))
+            if artifacts['burst_during_playback']:
+                error_msgs.append('Found burst during playback: %s' %
+                                  (artifacts['burst_during_playback']))
+        if error_msgs:
+            raise QualityFailure('Found bad quality: %s',
+                                 '\n'.join(error_msgs))
+
+    def dump(self, output_file):
+        """Dumps the result into a file in json format.
+
+        Args:
+            output_file: A file path to dump spectral and quality
+                            measurement result of each channel.
+
+        """
+        dump_dict = {
+            'spectrals': self._spectrals,
+            'quality_result': self._quality_result
+        }
+        with open(output_file, 'w') as f:
+            json.dump(dump_dict, f)
+
+    def has_data(self):
+        """Checks if data has been set.
+
+        Raises:
+            QualityCheckerError: if data or rate is not set yet.
+
+        """
+        if not self._raw_data or not self._rate:
+            raise QualityCheckerError('Data and rate is not set yet')
+
+    def check_freqs(self, expected_freqs, freq_threshold):
+        """Checks the dominant frequencies in the channels.
+
+        Args:
+            expected_freq: A list of frequencies. If frequency is 0, it
+                              means this channel should be ignored.
+            freq_threshold: The difference threshold to compare two
+                               frequencies.
+
+        """
+        logging.debug('expected_freqs: %s', expected_freqs)
+        for idx, expected_freq in enumerate(expected_freqs):
+            if expected_freq == 0:
+                continue
+            if not self._spectrals[idx]:
+                raise CompareFailure(
+                    'Failed at channel %d: no dominant frequency' % idx)
+            dominant_freq = self._spectrals[idx][0][0]
+            if abs(dominant_freq - expected_freq) > freq_threshold:
+                raise CompareFailure(
+                    'Failed at channel %d: %f is too far away from %f' %
+                    (idx, dominant_freq, expected_freq))
+
+    def check_quality(self):
+        """Checks the quality measurement results on each channel.
+
+        Raises:
+            QualityFailure when there is artifact.
+
+        """
+        error_msgs = []
+
+        for idx, quality_res in enumerate(self._quality_result):
+            artifacts = quality_res['artifacts']
+            if artifacts['noise_before_playback']:
+                error_msgs.append('Found noise before playback: %s' %
+                                  (artifacts['noise_before_playback']))
+            if artifacts['noise_after_playback']:
+                error_msgs.append('Found noise after playback: %s' %
+                                  (artifacts['noise_after_playback']))
+            if artifacts['delay_during_playback']:
+                error_msgs.append('Found delay during playback: %s' %
+                                  (artifacts['delay_during_playback']))
+            if artifacts['burst_during_playback']:
+                error_msgs.append('Found burst during playback: %s' %
+                                  (artifacts['burst_during_playback']))
+        if error_msgs:
+            raise QualityFailure('Found bad quality: %s',
+                                 '\n'.join(error_msgs))
+
+    def dump(self, output_file):
+        """Dumps the result into a file in json format.
+
+        Args:
+            output_file: A file path to dump spectral and quality
+                            measurement result of each channel.
+
+        """
+        dump_dict = {
+            'spectrals': self._spectrals,
+            'quality_result': self._quality_result
+        }
+        with open(output_file, 'w') as f:
+            json.dump(dump_dict, f)
+
+
+class CheckQualityError(Exception):
+    """Error in check_quality main function."""
+    pass
+
+
+def read_audio_file(filename, channel, bit_width, rate):
+    """Reads audio file.
+
+    Args:
+        filename: The wav or raw file to check.
+        channel: For raw file. Number of channels.
+        bit_width: For raw file. Bit width of a sample.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+
+
+    Returns:
+        A tuple (raw_data, rate) where raw_data is audio_data.AudioRawData, rate
+            is sampling rate.
+
+    """
+    if filename.endswith('.wav'):
+        wavefile = WaveFile(filename)
+        raw_data = wavefile.raw_data
+        rate = wavefile.rate
+    elif filename.endswith('.raw'):
+        binary = None
+        with open(filename, 'rb') as f:
+            binary = f.read()
+        raw_data = audio_data.AudioRawData(
+            binary=binary, channel=channel, sample_format='S%d_LE' % bit_width)
+    else:
+        raise CheckQualityError(
+            'File format for %s is not supported' % filename)
+
+    return raw_data, rate
+
+
+def get_quality_params(
+        quality_block_size_secs, quality_frequency_error_threshold,
+        quality_delay_amplitude_threshold, quality_noise_amplitude_threshold,
+        quality_burst_amplitude_threshold):
+    """Gets quality parameters in arguments.
+
+    Args:
+        quality_block_size_secs: Input block size in seconds.
+        quality_frequency_error_threshold: Input the frequency error
+        threshold.
+        quality_delay_amplitude_threshold: Input the delay aplitutde
+        threshold.
+        quality_noise_amplitude_threshold: Input the noise aplitutde
+        threshold.
+        quality_burst_amplitude_threshold: Input the burst aplitutde
+        threshold.
+
+    Returns:
+        A QualityParams object.
+
+    """
+    quality_params = QualityParams(
+        block_size_secs=quality_block_size_secs,
+        frequency_error_threshold=quality_frequency_error_threshold,
+        delay_amplitude_threshold=quality_delay_amplitude_threshold,
+        noise_amplitude_threshold=quality_noise_amplitude_threshold,
+        burst_amplitude_threshold=quality_burst_amplitude_threshold)
+
+    return quality_params
+
+
+def quality_analysis(
+        filename,
+        output_file,
+        bit_width,
+        rate,
+        channel,
+        freqs=None,
+        freq_threshold=5,
+        ignore_high_freq=5000,
+        spectral_only=False,
+        quality_block_size_secs=DEFAULT_QUALITY_BLOCK_SIZE_SECS,
+        quality_burst_amplitude_threshold=DEFAULT_BURST_AMPLITUDE_THRESHOLD,
+        quality_delay_amplitude_threshold=DEFAULT_DELAY_AMPLITUDE_THRESHOLD,
+        quality_frequency_error_threshold=DEFAULT_FREQUENCY_ERROR_THRESHOLD,
+        quality_noise_amplitude_threshold=DEFAULT_NOISE_AMPLITUDE_THRESHOLD,
+):
+    """ Runs various functions to measure audio quality base on user input.
+
+    Args:
+        filename: The wav or raw file to check.
+        output_file: Output file to dump analysis result in JSON format.
+        bit_width: For raw file. Bit width of a sample.
+        rate: Sampling rate in samples per second. Example inputs: 44100,
+        48000
+        channel: For raw file. Number of channels.
+        freqs: Expected frequencies in the channels.
+        freq_threshold: Frequency difference threshold in Hz.
+        ignore_high_freq: Frequency threshold in Hz to be ignored for high
+        frequency. Default is 5KHz
+        spectral_only: Only do spectral analysis on each channel.
+        quality_block_size_secs: Input block size in seconds.
+        quality_frequency_error_threshold: Input the frequency error
+        threshold.
+        quality_delay_amplitude_threshold: Input the delay aplitutde
+        threshold.
+        quality_noise_amplitude_threshold: Input the noise aplitutde
+        threshold.
+        quality_burst_amplitude_threshold: Input the burst aplitutde
+        threshold.
+    """
+
+    raw_data, rate = read_audio_file(filename, channel, bit_width, rate)
+
+    checker = QualityChecker(raw_data, rate)
+
+    quality_params = get_quality_params(
+        quality_block_size_secs, quality_frequency_error_threshold,
+        quality_delay_amplitude_threshold, quality_noise_amplitude_threshold,
+        quality_burst_amplitude_threshold)
+
+    checker.do_spectral_analysis(
+        ignore_high_freq=ignore_high_freq,
+        check_quality=(not spectral_only),
+        quality_params=quality_params)
+
+    checker.dump(output_file)
+
+    if freqs:
+        checker.check_freqs(freqs, freq_threshold)
+
+    if not spectral_only:
+        checker.check_quality()
+    logging.debug("Audio analysis completed.")
diff --git a/acts_tests/acts_contrib/test_utils/bt/A2dpBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/A2dpBaseTest.py
new file mode 100644
index 0000000..ba4cec3
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/A2dpBaseTest.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Stream music through connected device from phone test implementation."""
+import acts
+import os
+import shutil
+import time
+
+import acts_contrib.test_utils.coex.audio_test_utils as atu
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
+from acts import asserts
+from acts_contrib.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bt_factory
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+
+PHONE_MUSIC_FILE_DIRECTORY = '/sdcard/Music'
+INIT_ATTEN = 0
+WAIT_TIME = 1
+
+
+class A2dpBaseTest(BluetoothBaseTest):
+    """Stream audio file over desired Bluetooth codec configurations.
+
+    Audio file should be a sine wave. Other audio files will not work for the
+    test analysis metrics.
+
+    Device under test is Android phone, connected to headset with a controller
+    that can generate a BluetoothHandsfreeAbstractDevice from test_utils.
+    abstract_devices.bluetooth_handsfree_abstract_device.
+    BuetoothHandsfreeAbstractDeviceFactory.
+    """
+    def setup_class(self):
+
+        super().setup_class()
+        self.dut = self.android_devices[0]
+        req_params = ['audio_params', 'music_files']
+        #'audio_params' is a dict, contains the audio device type, audio streaming
+        #settings such as volumn, duration, audio recording parameters such as
+        #channel, sampling rate/width, and thdn parameters for audio processing
+        self.unpack_userparams(req_params)
+        # Find music file and push it to the dut
+        music_src = self.music_files[0]
+        music_dest = PHONE_MUSIC_FILE_DIRECTORY
+        success = self.dut.push_system_file(music_src, music_dest)
+        if success:
+            self.music_file = os.path.join(PHONE_MUSIC_FILE_DIRECTORY,
+                                           os.path.basename(music_src))
+        # Initialize media_control class
+        self.media = btutils.MediaControlOverSl4a(self.dut, self.music_file)
+        # Set attenuator to minimum attenuation
+        if hasattr(self, 'attenuators'):
+            self.attenuator = self.attenuators[0]
+            self.attenuator.set_atten(INIT_ATTEN)
+        # Create the BTOE(Bluetooth-Other-End) device object
+        bt_devices = self.user_params.get('bt_devices', [])
+        if bt_devices:
+            attr, idx = bt_devices.split(':')
+            self.bt_device_controller = getattr(self, attr)[int(idx)]
+            self.bt_device = bt_factory().generate(self.bt_device_controller)
+        else:
+            self.log.error('No BT devices config is provided!')
+
+    def teardown_class(self):
+
+        super().teardown_class()
+        if hasattr(self, 'media'):
+            self.media.stop()
+        if hasattr(self, 'attenuator'):
+            self.attenuator.set_atten(INIT_ATTEN)
+        self.dut.droid.bluetoothFactoryReset()
+        self.bt_device.reset()
+        self.bt_device.power_off()
+        btutils.disable_bluetooth(self.dut.droid)
+
+    def setup_test(self):
+
+        super().setup_test()
+        # Initialize audio capture devices
+        self.audio_device = atu.get_audio_capture_device(
+            self.bt_device_controller, self.audio_params)
+        # Reset BT to factory defaults
+        self.dut.droid.bluetoothFactoryReset()
+        self.bt_device.reset()
+        self.bt_device.power_on()
+        btutils.enable_bluetooth(self.dut.droid, self.dut.ed)
+        btutils.connect_phone_to_headset(self.dut, self.bt_device, 60)
+        vol = self.dut.droid.getMaxMediaVolume() * self.audio_params['volume']
+        self.dut.droid.setMediaVolume(0)
+        time.sleep(1)
+        self.dut.droid.setMediaVolume(int(vol))
+
+    def teardown_test(self):
+
+        super().teardown_test()
+        self.dut.droid.bluetoothFactoryReset()
+        self.media.stop()
+        # Set Attenuator to the initial attenuation
+        if hasattr(self, 'attenuator'):
+            self.attenuator.set_atten(INIT_ATTEN)
+        self.bt_device.reset()
+        self.bt_device.power_off()
+        btutils.disable_bluetooth(self.dut.droid)
+
+    def play_and_record_audio(self, duration):
+        """Play and record audio for a set duration.
+
+        Args:
+            duration: duration in seconds for music playing
+        Returns:
+            audio_captured: captured audio file path
+        """
+
+        self.log.info('Play and record audio for {} second'.format(duration))
+        self.media.play()
+        self.audio_device.start()
+        time.sleep(duration + WAIT_TIME)
+        audio_captured = self.audio_device.stop()
+        self.media.stop()
+        self.log.info('Audio play and record stopped')
+        asserts.assert_true(audio_captured, 'Audio not recorded')
+        return audio_captured
+
+    def _get_bt_link_metrics(self):
+        """Get bt link metrics such as rssi and tx pwls.
+
+        Returns:
+            rssi_master: master rssi
+            pwl_master: master tx pwl
+            rssi_slave: slave rssi
+        """
+
+        self.media.play()
+        # Get master rssi and power level
+        rssi_master = btutils.get_bt_metric(self.dut)['rssi']
+        pwl_master = btutils.get_bt_metric(self.dut)['pwlv']
+        # Get slave rssi if possible
+        if isinstance(self.bt_device_controller,
+                      acts.controllers.android_device.AndroidDevice):
+            rssi_slave = btutils.get_bt_rssi(self.bt_device_controller)
+        else:
+            rssi_slave = None
+        self.media.stop()
+        return [rssi_master, pwl_master, rssi_slave]
+
+    def run_thdn_analysis(self, audio_captured, tag):
+        """Calculate Total Harmonic Distortion plus Noise for latest recording.
+
+        Store result in self.metrics.
+
+        Args:
+            audio_captured: the captured audio file
+        Returns:
+            thdn: thdn value in a list
+        """
+        # Calculate Total Harmonic Distortion + Noise
+        audio_result = atu.AudioCaptureResult(audio_captured,
+                                              self.audio_params)
+        thdn = audio_result.THDN(**self.audio_params['thdn_params'])
+        file_name = tag + os.path.basename(audio_result.path)
+        file_new = os.path.join(os.path.dirname(audio_result.path), file_name)
+        shutil.copyfile(audio_result.path, file_new)
+        for ch_no, t in enumerate(thdn):
+            self.log.info('THD+N for channel %s: %.4f%%' % (ch_no, t * 100))
+        return thdn
+
+    def run_anomaly_detection(self, audio_captured):
+        """Detect anomalies in latest recording.
+
+        Store result in self.metrics.
+
+        Args:
+            audio_captured: the captured audio file
+        Returns:
+            anom: anom detected in the captured file
+        """
+        # Detect Anomalies
+        audio_result = atu.AudioCaptureResult(audio_captured)
+        anom = audio_result.detect_anomalies(
+            **self.audio_params['anomaly_params'])
+        num_anom = 0
+        for ch_no, anomalies in enumerate(anom):
+            if anomalies:
+                for anomaly in anomalies:
+                    num_anom += 1
+                    start, end = anomaly
+                    self.log.warning(
+                        'Anomaly on channel {} at {}:{}. Duration '
+                        '{} sec'.format(ch_no, start // 60, start % 60,
+                                        end - start))
+        else:
+            self.log.info('%i anomalies detected.' % num_anom)
+        return anom
diff --git a/acts_tests/acts_contrib/test_utils/bt/AvrcpBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/AvrcpBaseTest.py
new file mode 100644
index 0000000..9109ae3
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/AvrcpBaseTest.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Perform base Avrcp command from headset to dut"""
+import time
+import os
+import queue
+
+from acts import asserts
+from acts_contrib.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as Factory
+from acts_contrib.test_utils.bt.simulated_carkit_device import SimulatedCarkitDevice
+from acts_contrib.test_utils.bt.bt_test_utils import connect_phone_to_headset
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.car.car_media_utils import EVENT_PLAY_RECEIVED
+from acts_contrib.test_utils.car.car_media_utils import EVENT_PAUSE_RECEIVED
+from acts_contrib.test_utils.car.car_media_utils import EVENT_SKIP_NEXT_RECEIVED
+from acts_contrib.test_utils.car.car_media_utils import EVENT_SKIP_PREV_RECEIVED
+from acts_contrib.test_utils.car.car_media_utils import CMD_MEDIA_PLAY
+from acts_contrib.test_utils.car.car_media_utils import CMD_MEDIA_PAUSE
+
+ADB_FILE_EXISTS = 'test -e %s && echo True'
+DEFAULT_TIMEOUT = 5
+EVENT_TIMEOUT = 1
+
+
+class AvrcpBaseTest(BluetoothBaseTest):
+    def __init__(self, configs):
+        super(AvrcpBaseTest, self).__init__(configs)
+        self.dut = self.android_devices[0]
+        serial = self.user_params['simulated_carkit_device']
+        controller = SimulatedCarkitDevice(serial)
+        self.controller = Factory().generate(controller)
+
+        self.phone_music_files = []
+        self.host_music_files = []
+        for music_file in self.user_params['music_file_names']:
+            self.phone_music_files.append(os.path.join(
+                self.user_params['phone_music_file_dir'], music_file))
+            self.host_music_files.append(os.path.join(
+                self.user_params['host_music_file_dir'], music_file))
+
+        self.ensure_phone_has_music_file()
+
+    def setup_class(self):
+        super().setup_class()
+        self.controller.power_on()
+        time.sleep(DEFAULT_TIMEOUT)
+
+    def teardown_class(self):
+        super().teardown_class()
+        self.dut.droid.mediaPlayStop()
+        self.controller.destroy()
+
+    def setup_test(self):
+        self.dut.droid.bluetoothMediaPhoneSL4AMBSStart()
+        time.sleep(DEFAULT_TIMEOUT)
+
+        self.dut.droid.bluetoothStartPairingHelper(True)
+        if not connect_phone_to_headset(self.dut, self.controller, 600):
+            asserts.fail('Not able to connect to hands-free device')
+
+        #make sure SL4AMBS is active MediaSession
+        self.dut.droid.bluetoothMediaHandleMediaCommandOnPhone(CMD_MEDIA_PLAY)
+        time.sleep(0.5)
+        self.dut.droid.bluetoothMediaHandleMediaCommandOnPhone(CMD_MEDIA_PAUSE)
+
+    def teardown_test(self):
+        self.dut.droid.bluetoothMediaPhoneSL4AMBSStop()
+
+    def ensure_phone_has_music_file(self):
+        """Make sure music file (based on config values) is on the phone."""
+        for host_file, phone_file in zip(self.host_music_files,
+                                         self.phone_music_files):
+            if self.dut.adb.shell(ADB_FILE_EXISTS % phone_file):
+                self.log.info(
+                    'Music file {} already on phone. Skipping file transfer.'
+                    .format(host_file))
+            else:
+                self.dut.adb.push(host_file, phone_file)
+                has_file = self.dut.adb.shell(
+                        ADB_FILE_EXISTS % phone_file)
+                if not has_file:
+                    self.log.error(
+                        'Audio file {} not pushed to phone.'.format(host_file))
+                self.log.info('Music file successfully pushed to phone.')
+
+    def play_from_controller(self):
+        self.dut.ed.clear_all_events()
+        self.controller.play()
+        try:
+            self.dut.ed.pop_event(EVENT_PLAY_RECEIVED, EVENT_TIMEOUT)
+        except queue.Empty as e:
+            asserts.fail('{} Event Not received'.format(EVENT_PLAY_RECEIVED))
+        self.log.info('Event Received : {}'.format(EVENT_PLAY_RECEIVED))
+
+    def pause_from_controller(self):
+        self.dut.ed.clear_all_events()
+        self.controller.pause()
+        try:
+            self.dut.ed.pop_event(EVENT_PAUSE_RECEIVED, EVENT_TIMEOUT)
+        except queue.Empty as e:
+            asserts.fail('{} Event Not received'.format(EVENT_PAUSE_RECEIVED))
+        self.log.info('Event Received : {}'.format(EVENT_PAUSE_RECEIVED))
+
+    def skip_next_from_controller(self):
+        self.dut.ed.clear_all_events()
+        self.controller.next_track()
+        try:
+            self.dut.ed.pop_event(EVENT_SKIP_NEXT_RECEIVED, EVENT_TIMEOUT)
+        except queue.Empty as e:
+            asserts.fail('{} Event Not '
+                         'received'.format(EVENT_SKIP_NEXT_RECEIVED))
+        self.log.info('Event Received : {}'.format(EVENT_SKIP_NEXT_RECEIVED))
+
+    def skip_prev_from_controller(self):
+        self.dut.ed.clear_all_events()
+        self.controller.previous_track()
+        try:
+            self.dut.ed.pop_event(EVENT_SKIP_PREV_RECEIVED, EVENT_TIMEOUT)
+        except queue.Empty as e:
+            asserts.fail('{} Event Not '
+                         'received'.format(EVENT_SKIP_PREV_RECEIVED))
+        self.log.info('Event Received : {}'.format(EVENT_SKIP_PREV_RECEIVED))
diff --git a/acts_tests/acts_contrib/test_utils/bt/BleEnum.py b/acts_tests/acts_contrib/test_utils/bt/BleEnum.py
new file mode 100644
index 0000000..00763aa
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/BleEnum.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from enum import Enum
+
+
+class ScanSettingsCallbackType(Enum):
+    CALLBACK_TYPE_ALL_MATCHES = 1
+    CALLBACK_TYPE_FIRST_MATCH = 2
+    CALLBACK_TYPE_MATCH_LOST = 4
+    CALLBACK_TYPE_FOUND_AND_LOST = 6
+
+
+class ScanSettingsMatchMode(Enum):
+    AGGRESIVE = 1
+    STICKY = 2
+
+
+class ScanSettingsMatchNum(Enum):
+    MATCH_NUM_ONE_ADVERTISEMENT = 1
+    MATCH_NUM_FEW_ADVERTISEMENT = 2
+    MATCH_NUM_MAX_ADVERTISEMENT = 3
+
+
+class ScanSettingsScanResultType(Enum):
+    SCAN_RESULT_TYPE_FULL = 0
+    SCAN_RESULT_TYPE_ABBREVIATED = 1
+
+
+class ScanSettingsScanMode(Enum):
+    SCAN_MODE_OPPORTUNISTIC = -1
+    SCAN_MODE_LOW_POWER = 0
+    SCAN_MODE_BALANCED = 1
+    SCAN_MODE_LOW_LATENCY = 2
+
+class ScanSettingsReportDelaySeconds(Enum):
+    MIN = 0
+    MAX = 9223372036854775807
+
+class ScanSettingsPhy(Enum):
+    PHY_LE_1M = 1
+    PHY_LE_CODED = 3
+    PHY_LE_ALL_SUPPORTED = 255
+
+class AdvertiseSettingsAdvertiseType(Enum):
+    ADVERTISE_TYPE_NON_CONNECTABLE = 0
+    ADVERTISE_TYPE_CONNECTABLE = 1
+
+
+class AdvertiseSettingsAdvertiseMode(Enum):
+    ADVERTISE_MODE_LOW_POWER = 0
+    ADVERTISE_MODE_BALANCED = 1
+    ADVERTISE_MODE_LOW_LATENCY = 2
+
+
+class AdvertiseSettingsAdvertiseTxPower(Enum):
+    ADVERTISE_TX_POWER_ULTRA_LOW = 0
+    ADVERTISE_TX_POWER_LOW = 1
+    ADVERTISE_TX_POWER_MEDIUM = 2
+    ADVERTISE_TX_POWER_HIGH = 3
+
+
+class JavaInteger(Enum):
+    MIN = -2147483648
+    MAX = 2147483647
+
+
+class Uuids(Enum):
+    P_Service = "0000feef-0000-1000-8000-00805f9b34fb"
+    HR_SERVICE = "0000180d-0000-1000-8000-00805f9b34fb"
+
+
+class AdvertiseErrorCode(Enum):
+    DATA_TOO_LARGE = 1
+    TOO_MANY_ADVERTISERS = 2
+    ADVERTISE_ALREADY_STARTED = 3
+    BLUETOOTH_INTERNAL_FAILURE = 4
+    FEATURE_NOT_SUPPORTED = 5
+
+
+class BluetoothAdapterState(Enum):
+    STATE_OFF = 10
+    STATE_TURNING_ON = 11
+    STATE_ON = 12
+    STATE_TURNING_OFF = 13
+    STATE_BLE_TURNING_ON = 14
+    STATE_BLE_ON = 15
+    STATE_BLE_TURNING_OFF = 16
diff --git a/acts_tests/acts_contrib/test_utils/bt/BluetoothBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/BluetoothBaseTest.py
new file mode 100644
index 0000000..a531bea
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/BluetoothBaseTest.py
@@ -0,0 +1,224 @@
+#!/usr/bin/env python3
+#
+#   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.
+"""
+    Base Class for Defining Common Bluetooth Test Functionality
+"""
+
+import threading
+import time
+import traceback
+import os
+from acts.base_test import BaseTestClass
+from acts.signals import TestSignal
+from acts.utils import dump_string_to_file
+
+from acts.libs.proto.proto_utils import parse_proto_to_ascii
+from acts_contrib.test_utils.bt.bt_metrics_utils import get_bluetooth_metrics
+from acts_contrib.test_utils.bt.bt_test_utils import get_device_selector_dictionary
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.ble_lib import BleLib
+from acts_contrib.test_utils.bt.bta_lib import BtaLib
+from acts_contrib.test_utils.bt.config_lib import ConfigLib
+from acts_contrib.test_utils.bt.gattc_lib import GattClientLib
+from acts_contrib.test_utils.bt.gatts_lib import GattServerLib
+from acts_contrib.test_utils.bt.rfcomm_lib import RfcommLib
+from acts_contrib.test_utils.bt.shell_commands_lib import ShellCommands
+
+
+class BluetoothBaseTest(BaseTestClass):
+    DEFAULT_TIMEOUT = 10
+    start_time = 0
+    timer_list = []
+
+    def collect_bluetooth_manager_metrics_logs(self, ads, test_name):
+        """
+        Collect Bluetooth metrics logs, save an ascii log to disk and return
+        both binary and ascii logs to caller
+        :param ads: list of active Android devices
+        :return: List of binary metrics logs,
+                List of ascii metrics logs
+        """
+        bluetooth_logs = []
+        bluetooth_logs_ascii = []
+        for ad in ads:
+            serial = ad.serial
+            out_name = "{}_{}_{}".format(serial, test_name,
+                                         "bluetooth_metrics.txt")
+            bluetooth_log = get_bluetooth_metrics(ad)
+            bluetooth_log_ascii = parse_proto_to_ascii(bluetooth_log)
+            dump_string_to_file(bluetooth_log_ascii,
+                                os.path.join(ad.metrics_path, out_name))
+            bluetooth_logs.append(bluetooth_log)
+            bluetooth_logs_ascii.append(bluetooth_log_ascii)
+        return bluetooth_logs, bluetooth_logs_ascii
+
+    # Use for logging in the test cases to facilitate
+    # faster log lookup and reduce ambiguity in logging.
+    @staticmethod
+    def bt_test_wrap(fn):
+        def _safe_wrap_test_case(self, *args, **kwargs):
+            test_id = "{}:{}:{}".format(self.__class__.__name__, fn.__name__,
+                                        time.time())
+            log_string = "[Test ID] {}".format(test_id)
+            self.log.info(log_string)
+            try:
+                for ad in self.android_devices:
+                    ad.droid.logI("Started " + log_string)
+                result = fn(self, *args, **kwargs)
+                for ad in self.android_devices:
+                    ad.droid.logI("Finished " + log_string)
+                if result is not True and "bt_auto_rerun" in self.user_params:
+                    self.teardown_test()
+                    log_string = "[Rerun Test ID] {}. 1st run failed.".format(
+                        test_id)
+                    self.log.info(log_string)
+                    self.setup_test()
+                    for ad in self.android_devices:
+                        ad.droid.logI("Rerun Started " + log_string)
+                    result = fn(self, *args, **kwargs)
+                    if result is True:
+                        self.log.info("Rerun passed.")
+                    elif result is False:
+                        self.log.info("Rerun failed.")
+                    else:
+                        # In the event that we have a non-bool or null
+                        # retval, we want to clearly distinguish this in the
+                        # logs from an explicit failure, though the test will
+                        # still be considered a failure for reporting purposes.
+                        self.log.info("Rerun indeterminate.")
+                        result = False
+                return result
+            except TestSignal:
+                raise
+            except Exception as e:
+                self.log.error(traceback.format_exc())
+                self.log.error(str(e))
+                raise
+            return fn(self, *args, **kwargs)
+
+        return _safe_wrap_test_case
+
+    def setup_class(self):
+        super().setup_class()
+        for ad in self.android_devices:
+            self._setup_bt_libs(ad)
+        if 'preferred_device_order' in self.user_params:
+            prefered_device_order = self.user_params['preferred_device_order']
+            for i, ad in enumerate(self.android_devices):
+                if ad.serial in prefered_device_order:
+                    index = prefered_device_order.index(ad.serial)
+                    self.android_devices[i], self.android_devices[index] = \
+                        self.android_devices[index], self.android_devices[i]
+
+        if "reboot_between_test_class" in self.user_params:
+            threads = []
+            for a in self.android_devices:
+                thread = threading.Thread(
+                    target=self._reboot_device, args=([a]))
+                threads.append(thread)
+                thread.start()
+            for t in threads:
+                t.join()
+        if not setup_multiple_devices_for_bt_test(self.android_devices):
+            return False
+        self.device_selector = get_device_selector_dictionary(
+            self.android_devices)
+        if "bluetooth_proto_path" in self.user_params:
+            for ad in self.android_devices:
+                ad.metrics_path = os.path.join(ad.log_path, "BluetoothMetrics")
+                os.makedirs(ad.metrics_path, exist_ok=True)
+                # Clear metrics.
+                get_bluetooth_metrics(ad)
+        return True
+
+    def teardown_class(self):
+        if "bluetooth_proto_path" in self.user_params:
+            # Collect metrics here bassed off class name
+            bluetooth_logs, bluetooth_logs_ascii = \
+                self.collect_bluetooth_manager_metrics_logs(
+                    self.android_devices, self.__class__.__name__)
+
+    def setup_test(self):
+        self.timer_list = []
+        for a in self.android_devices:
+            a.ed.clear_all_events()
+            a.droid.setScreenTimeout(500)
+            a.droid.wakeUpNow()
+        return True
+
+    def on_fail(self, test_name, begin_time):
+        self.log.debug(
+            "Test {} failed. Gathering bugreport and btsnoop logs".format(
+                test_name))
+        take_btsnoop_logs(self.android_devices, self, test_name)
+        self._take_bug_report(test_name, begin_time)
+        for _ in range(5):
+            if reset_bluetooth(self.android_devices):
+                break
+            else:
+                self.log.error("Failed to reset Bluetooth... retrying.")
+        return
+
+    def _get_time_in_milliseconds(self):
+        return int(round(time.time() * 1000))
+
+    def start_timer(self):
+        self.start_time = self._get_time_in_milliseconds()
+
+    def end_timer(self):
+        total_time = self._get_time_in_milliseconds() - self.start_time
+        self.timer_list.append(total_time)
+        self.start_time = 0
+        return total_time
+
+    def log_stats(self):
+        if self.timer_list:
+            self.log.info("Overall list {}".format(self.timer_list))
+            self.log.info("Average of list {}".format(
+                sum(self.timer_list) / float(len(self.timer_list))))
+            self.log.info("Maximum of list {}".format(max(self.timer_list)))
+            self.log.info("Minimum of list {}".format(min(self.timer_list)))
+            self.log.info("Total items in list {}".format(
+                len(self.timer_list)))
+        self.timer_list = []
+
+    def _setup_bt_libs(self, android_device):
+        # Bluetooth Low Energy library.
+        setattr(android_device, "ble", BleLib(
+            log=self.log, dut=android_device))
+        # Bluetooth Adapter library.
+        setattr(android_device, "bta", BtaLib(
+            log=self.log, dut=android_device))
+        # Bluetooth stack config library.
+        setattr(android_device, "config",
+                ConfigLib(log=self.log, dut=android_device))
+        # GATT Client library.
+        setattr(android_device, "gattc",
+                GattClientLib(log=self.log, dut=android_device))
+        # GATT Server library.
+        setattr(android_device, "gatts",
+                GattServerLib(log=self.log, dut=android_device))
+        # RFCOMM library.
+        setattr(android_device, "rfcomm",
+                RfcommLib(log=self.log, dut=android_device))
+        # Shell command library
+        setattr(android_device, "shell",
+                ShellCommands(log=self.log, dut=android_device))
+        # Setup Android Device feature list
+        setattr(android_device, "features",
+                android_device.adb.shell("pm list features").split("\n"))
diff --git a/acts_tests/acts_contrib/test_utils/bt/BluetoothCarHfpBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/BluetoothCarHfpBaseTest.py
new file mode 100644
index 0000000..303b961
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/BluetoothCarHfpBaseTest.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""
+This is base class for tests that exercises different GATT procedures between two connected devices.
+Setup/Teardown methods take care of establishing connection, and doing GATT DB initialization/discovery.
+"""
+
+import os
+import time
+from queue import Empty
+
+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_test_utils import get_phone_number
+from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
+
+
+class BluetoothCarHfpBaseTest(BluetoothBaseTest):
+    DEFAULT_TIMEOUT = 15
+    ag_phone_number = ""
+    re_phone_number = ""
+
+    def __init__(self, controllers):
+        BluetoothBaseTest.__init__(self, controllers)
+        # HF : HandsFree (CarKit role)
+        self.hf = self.android_devices[0]
+        self.hf.log.info("Role set to HF (HandsFree Carkit role).")
+        # AG : Audio Gateway (Phone role)
+        self.ag = self.android_devices[1]
+        self.ag.log.info("Role set to AG (Audio Gateway Phone role).")
+        # RE : Remote Device (Phone being talked to role)
+        if len(self.android_devices) > 2:
+            self.re = self.android_devices[2]
+            self.re.log.info("Role set to RE (Remote device).")
+        else:
+            self.re = None
+        if len(self.android_devices) > 3:
+            self.re2 = self.android_devices[3]
+            self.re2.log.info("Role set to RE2 (Remote device 2).")
+        else:
+            self.re2 = None
+
+    def setup_class(self):
+        super(BluetoothCarHfpBaseTest, self).setup_class()
+        if not "sim_conf_file" in self.user_params.keys():
+            self.log.error("Missing mandatory user config \"sim_conf_file\"!")
+            return False
+        sim_conf_file = self.user_params["sim_conf_file"][0]
+        if not os.path.isfile(sim_conf_file):
+            sim_conf_file = os.path.join(
+                self.user_params[Config.key_config_path.value], sim_conf_file)
+            if not os.path.isfile(sim_conf_file):
+                self.log.error("Unable to load user config " + sim_conf_file +
+                               " from test config file.")
+                return False
+        setup_droid_properties(self.log, self.ag, sim_conf_file)
+        self.ag_phone_number = get_phone_number(self.log, self.ag)
+        self.ag.log.info("ag tel: {}".format(self.ag_phone_number))
+        if self.re:
+            setup_droid_properties(self.log, self.re, sim_conf_file)
+            self.re_phone_number = get_phone_number(self.log, self.re)
+            self.re.log.info("re tel: {}".format(self.re_phone_number))
+        if self.re2:
+            setup_droid_properties(self.log, self.re2, sim_conf_file)
+            self.re2_phone_number = get_phone_number(self.log, self.re2)
+            self.re2.log.info("re2 tel: {}".format(self.re2_phone_number))
+        # Pair and connect the devices.
+        # Grace time inbetween stack state changes
+        time.sleep(5)
+        if not pair_pri_to_sec(
+                self.hf, self.ag, attempts=4, auto_confirm=False):
+            self.log.error("Failed to pair")
+            return False
+        return True
+
+    def setup_test(self):
+        if not super(BluetoothCarHfpBaseTest, self).setup_test():
+            return False
+        return ensure_phones_default_state(self.log, self.android_devices[1:])
+
+    def teardown_test(self):
+        if not super(BluetoothCarHfpBaseTest, self).teardown_test():
+            return False
+        return ensure_phones_default_state(self.log, self.android_devices[1:])
+
+    def on_fail(self, test_name, begin_time):
+        result = True
+        if not super(BluetoothCarHfpBaseTest, self).on_fail(test_name,
+                                                            begin_time):
+            result = False
+        ensure_phones_default_state(self.log, self.android_devices[1:])
+        return result
diff --git a/acts_tests/acts_contrib/test_utils/bt/BtEnum.py b/acts_tests/acts_contrib/test_utils/bt/BtEnum.py
new file mode 100644
index 0000000..b9fe6e2
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/BtEnum.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from enum import Enum
+from enum import IntEnum
+
+
+class BluetoothScanModeType(IntEnum):
+    STATE_OFF = -1
+    SCAN_MODE_NONE = 0
+    SCAN_MODE_CONNECTABLE = 1
+    SCAN_MODE_CONNECTABLE_DISCOVERABLE = 3
+
+
+class BluetoothAdapterState(IntEnum):
+    STATE_OFF = 10
+    STATE_TURNING_ON = 11
+    STATE_ON = 12
+    STATE_TURNING_OFF = 13
+    STATE_BLE_TURNING_ON = 14
+    STATE_BLE_ON = 15
+    STATE_BLE_TURNING_OFF = 16
+
+
+class BluetoothProfile(IntEnum):
+    # Should be kept in sync with BluetoothProfile.java
+    HEADSET = 1
+    A2DP = 2
+    HEALTH = 3
+    INPUT_DEVICE = 4
+    PAN = 5
+    PBAP_SERVER = 6
+    GATT = 7
+    GATT_SERVER = 8
+    MAP = 9
+    SAP = 10
+    A2DP_SINK = 11
+    AVRCP_CONTROLLER = 12
+    HEADSET_CLIENT = 16
+    PBAP_CLIENT = 17
+    MAP_MCE = 18
+
+
+class RfcommUuid(Enum):
+    DEFAULT_UUID = "457807c0-4897-11df-9879-0800200c9a66"
+    BASE_UUID = "00000000-0000-1000-8000-00805F9B34FB"
+    SDP = "00000001-0000-1000-8000-00805F9B34FB"
+    UDP = "00000002-0000-1000-8000-00805F9B34FB"
+    RFCOMM = "00000003-0000-1000-8000-00805F9B34FB"
+    TCP = "00000004-0000-1000-8000-00805F9B34FB"
+    TCS_BIN = "00000005-0000-1000-8000-00805F9B34FB"
+    TCS_AT = "00000006-0000-1000-8000-00805F9B34FB"
+    ATT = "00000007-0000-1000-8000-00805F9B34FB"
+    OBEX = "00000008-0000-1000-8000-00805F9B34FB"
+    IP = "00000009-0000-1000-8000-00805F9B34FB"
+    FTP = "0000000A-0000-1000-8000-00805F9B34FB"
+    HTTP = "0000000C-0000-1000-8000-00805F9B34FB"
+    WSP = "0000000E-0000-1000-8000-00805F9B34FB"
+    BNEP = "0000000F-0000-1000-8000-00805F9B34FB"
+    UPNP = "00000010-0000-1000-8000-00805F9B34FB"
+    HIDP = "00000011-0000-1000-8000-00805F9B34FB"
+    HARDCOPY_CONTROL_CHANNEL = "00000012-0000-1000-8000-00805F9B34FB"
+    HARDCOPY_DATA_CHANNEL = "00000014-0000-1000-8000-00805F9B34FB"
+    HARDCOPY_NOTIFICATION = "00000016-0000-1000-8000-00805F9B34FB"
+    AVCTP = "00000017-0000-1000-8000-00805F9B34FB"
+    AVDTP = "00000019-0000-1000-8000-00805F9B34FB"
+    CMTP = "0000001B-0000-1000-8000-00805F9B34FB"
+    MCAP_CONTROL_CHANNEL = "0000001E-0000-1000-8000-00805F9B34FB"
+    MCAP_DATA_CHANNEL = "0000001F-0000-1000-8000-00805F9B34FB"
+    L2CAP = "00000100-0000-1000-8000-00805F9B34FB"
+
+
+class BluetoothProfileState(Enum):
+    # Should be kept in sync with BluetoothProfile#STATE_* constants.
+    STATE_DISCONNECTED = 0
+    STATE_CONNECTING = 1
+    STATE_CONNECTED = 2
+    STATE_DISCONNECTING = 3
+
+
+class BluetoothAccessLevel(Enum):
+    # Access Levels from BluetoothDevice.
+    ACCESS_ALLOWED = 1
+    ACCESS_DENIED = 2
+
+
+class BluetoothPriorityLevel(Enum):
+    # Priority levels as defined in BluetoothProfile.java.
+    PRIORITY_AUTO_CONNECT = 1000
+    PRIORITY_ON = 100
+    PRIORITY_OFF = 0
+    PRIORITY_UNDEFINED = -1
+
+class BluetoothA2dpCodecType(Enum):
+    SBC = 0
+    AAC = 1
+    APTX = 2
+    APTX_HD = 3
+    LDAC = 4
+    MAX = 5
diff --git a/acts_tests/acts_contrib/test_utils/bt/BtFunhausBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/BtFunhausBaseTest.py
new file mode 100644
index 0000000..127289e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/BtFunhausBaseTest.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""
+Test script to automate the Bluetooth Audio Funhaus.
+"""
+from acts.keys import Config
+from acts_contrib.test_utils.bt.BtMetricsBaseTest import BtMetricsBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import bluetooth_enabled_check
+from acts.utils import bypass_setup_wizard
+from acts.utils import exe_cmd
+from acts.utils import sync_device_time
+import json
+import time
+import os
+
+BT_CONF_PATH = "/data/misc/bluedroid/bt_config.conf"
+
+
+class BtFunhausBaseTest(BtMetricsBaseTest):
+    """
+    Base class for Bluetooth A2DP audio tests, this class is in charge of
+    pushing link key to Android device so that it could be paired with remote
+    A2DP device, pushing music to Android device, playing audio, monitoring
+    audio play, and stop playing audio
+    """
+    music_file_to_play = ""
+    device_fails_to_connect_list = []
+
+    def __init__(self, controllers):
+        BtMetricsBaseTest.__init__(self, controllers)
+        self.ad = self.android_devices[0]
+        self.dongle = self.relay_devices[0]
+
+    def _pair_devices(self):
+        self.ad.droid.bluetoothStartPairingHelper(False)
+        self.dongle.enter_pairing_mode()
+
+        self.ad.droid.bluetoothBond(self.dongle.mac_address)
+
+        end_time = time.time() + 20
+        self.ad.log.info("Verifying devices are bonded")
+        while time.time() < end_time:
+            bonded_devices = self.ad.droid.bluetoothGetBondedDevices()
+
+            for d in bonded_devices:
+                if d['address'] == self.dongle.mac_address:
+                    self.ad.log.info("Successfully bonded to device.")
+                    self.log.info("Bonded devices:\n{}".format(bonded_devices))
+                return True
+        self.ad.log.info("Failed to bond devices.")
+        return False
+
+    def setup_test(self):
+        super(BtFunhausBaseTest, self).setup_test()
+        self.dongle.setup()
+        tries = 5
+        # Since we are not concerned with pairing in this test, try 5 times.
+        while tries > 0:
+            if self._pair_devices():
+                return True
+            else:
+                tries -= 1
+        return False
+
+    def teardown_test(self):
+        super(BtFunhausBaseTest, self).teardown_test()
+        self.dongle.clean_up()
+        return True
+
+    def on_fail(self, test_name, begin_time):
+        self.dongle.clean_up()
+        self._collect_bluetooth_manager_dumpsys_logs(self.android_devices)
+        super(BtFunhausBaseTest, self).on_fail(test_name, begin_time)
+
+    def setup_class(self):
+        if not super(BtFunhausBaseTest, self).setup_class():
+            return False
+        for ad in self.android_devices:
+            sync_device_time(ad)
+            # Disable Bluetooth HCI Snoop Logs for audio tests
+            ad.adb.shell("setprop persist.bluetooth.btsnoopenable false")
+            if not bypass_setup_wizard(ad):
+                self.log.debug(
+                    "Failed to bypass setup wizard, continuing test.")
+                # Add music to the Android device
+        return self._add_music_to_android_device(ad)
+
+    def _add_music_to_android_device(self, ad):
+        """
+        Add music to Android device as specified by the test config
+        :param ad: Android device
+        :return: True on success, False on failure
+        """
+        self.log.info("Pushing music to the Android device.")
+        music_path_str = "bt_music"
+        android_music_path = "/sdcard/Music/"
+        if music_path_str not in self.user_params:
+            self.log.error("Need music for audio testcases...")
+            return False
+        music_path = self.user_params[music_path_str]
+        if type(music_path) is list:
+            self.log.info("Media ready to push as is.")
+        elif not os.path.isdir(music_path):
+            music_path = os.path.join(
+                self.user_params[Config.key_config_path.value], music_path)
+            if not os.path.isdir(music_path):
+                self.log.error(
+                    "Unable to find music directory {}.".format(music_path))
+                return False
+        if type(music_path) is list:
+            for item in music_path:
+                self.music_file_to_play = item
+                ad.adb.push("{} {}".format(item, android_music_path))
+        else:
+            for dirname, dirnames, filenames in os.walk(music_path):
+                for filename in filenames:
+                    self.music_file_to_play = filename
+                    file = os.path.join(dirname, filename)
+                    # TODO: Handle file paths with spaces
+                    ad.adb.push("{} {}".format(file, android_music_path))
+        ad.reboot()
+        return True
+
+    def _collect_bluetooth_manager_dumpsys_logs(self, ads):
+        """
+        Collect "adb shell dumpsys bluetooth_manager" logs
+        :param ads: list of active Android devices
+        :return: None
+        """
+        for ad in ads:
+            serial = ad.serial
+            out_name = "{}_{}".format(serial, "bluetooth_dumpsys.txt")
+            dumpsys_path = ''.join((ad.log_path, "/BluetoothDumpsys"))
+            os.makedirs(dumpsys_path, exist_ok=True)
+            cmd = ''.join(
+                ("adb -s ", serial, " shell dumpsys bluetooth_manager > ",
+                 dumpsys_path, "/", out_name))
+            exe_cmd(cmd)
+
+    def start_playing_music_on_all_devices(self):
+        """
+        Start playing music
+        :return: None
+        """
+        self.ad.droid.mediaPlayOpen("file:///sdcard/Music/{}".format(
+            self.music_file_to_play.split("/")[-1]))
+        self.ad.droid.mediaPlaySetLooping(True)
+        self.ad.log.info("Music is now playing.")
+
+    def monitor_music_play_util_deadline(self, end_time, sleep_interval=1):
+        """
+        Monitor music play on all devices, if a device's Bluetooth adapter is
+        OFF or if a device is not connected to any remote Bluetooth devices,
+        we add them to failure lists bluetooth_off_list and
+        device_not_connected_list respectively
+        :param end_time: The deadline in epoch floating point seconds that we
+            must stop playing
+        :param sleep_interval: How often to monitor, too small we may drain
+            too much resources on Android, too big the deadline might be passed
+            by a maximum of this amount
+        :return:
+            status: False iff all devices are off or disconnected otherwise True
+            bluetooth_off_list: List of ADs that have Bluetooth at OFF state
+            device_not_connected_list: List of ADs with no remote device
+                                        connected
+        """
+        device_not_connected_list = []
+        while time.time() < end_time:
+            if not self.ad.droid.bluetoothCheckState():
+                self.ad.log.error("Device {}'s Bluetooth state is off.".format(
+                    self.ad.serial))
+                return False
+            if self.ad.droid.bluetoothGetConnectedDevices() == 0:
+                self.ad.log.error(
+                    "Bluetooth device not connected. Failing test.")
+            time.sleep(sleep_interval)
+        return True
+
+    def play_music_for_duration(self, duration, sleep_interval=1):
+        """
+        A convenience method for above methods. It starts run music on all
+        devices, monitors the health of music play and stops playing them when
+        time passes the duration
+        :param duration: Duration in floating point seconds
+        :param sleep_interval: How often to check the health of music play
+        :return:
+            status: False iff all devices are off or disconnected otherwise True
+            bluetooth_off_list: List of ADs that have Bluetooth at OFF state
+            device_not_connected_list: List of ADs with no remote device
+                                        connected
+        """
+        start_time = time.time()
+        end_time = start_time + duration
+        self.start_playing_music_on_all_devices()
+        status = self.monitor_music_play_util_deadline(end_time,
+                                                       sleep_interval)
+        self.ad.droid.mediaPlayStopAll()
+        return status
diff --git a/acts_tests/acts_contrib/test_utils/bt/BtInterferenceBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/BtInterferenceBaseTest.py
new file mode 100644
index 0000000..3c49bee
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/BtInterferenceBaseTest.py
@@ -0,0 +1,238 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Stream music through connected device from phone across different
+attenuations."""
+
+import json
+import math
+import time
+import acts.controllers.iperf_client as ipc
+import acts.controllers.iperf_server as ipf
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
+from acts import asserts
+from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
+from acts_contrib.test_utils.bt.loggers import bluetooth_metric_logger as log
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wpeutils
+from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.power.PowerBaseTest import ObjNew
+
+MAX_ATTENUATION = 95
+TEMP_FILE = '/sdcard/Download/tmp.log'
+IPERF_CLIENT_ERROR = 'the client has unexpectedly closed the connection'
+
+
+def setup_ap_connection(dut, network, ap, bandwidth=20):
+    """Setup AP and connect DUT to it.
+
+    Args:
+        dut: the android device to connect and run traffic
+        network: the network config for the AP to be setup
+        ap: access point object
+        bandwidth: bandwidth of the WiFi network to be setup
+    Returns:
+        self.brconfigs: dict for bridge interface configs
+    """
+    wutils.wifi_toggle_state(dut, True)
+    brconfigs = wputils.ap_setup(ap, network, bandwidth=bandwidth)
+    wutils.wifi_connect(dut, network, num_of_tries=3)
+    return brconfigs
+
+
+def start_iperf_client(traffic_pair_obj, duration):
+    """Setup iperf traffic for TCP downlink.
+    Args:
+        traffic_pair_obj: obj to contain info on traffic pair
+        duration: duration of iperf traffic to run
+    """
+    # Construct the iperf command based on the test params
+    iperf_cmd = 'iperf3 -c {} -i 1 -t {} -p {} -J -R > {}'.format(
+        traffic_pair_obj.server_address, duration,
+        traffic_pair_obj.iperf_server.port, TEMP_FILE)
+    # Start IPERF client
+    traffic_pair_obj.dut.adb.shell_nb(iperf_cmd)
+
+
+def unpack_custom_file(file):
+    """Unpack the json file to .
+
+    Args:
+        file: custom json file.
+    """
+    with open(file, 'r') as f:
+        params = json.load(f)
+    return params
+
+
+def get_iperf_results(iperf_server_obj):
+    """Get the iperf results and process.
+
+    Args:
+        iperf_server_obj: the IperfServer object
+    Returns:
+         throughput: the average throughput during tests.
+    """
+    # Get IPERF results and add this to the plot title
+    iperf_file = iperf_server_obj.log_files[-1]
+    try:
+        iperf_result = ipf.IPerfResult(iperf_file)
+        # Compute the throughput in Mbit/s
+        if iperf_result.error == IPERF_CLIENT_ERROR:
+            rates = []
+            for item in iperf_result.result['intervals']:
+                rates.append(item['sum']['bits_per_second'] / 8 / 1024 / 1024)
+            throughput = ((math.fsum(rates) / len(rates))) * 8 * (1.024**2)
+        else:
+            throughput = (math.fsum(iperf_result.instantaneous_rates) / len(
+                iperf_result.instantaneous_rates)) * 8 * (1.024**2)
+    except (ValueError, TypeError):
+        throughput = 0
+    return throughput
+
+
+class BtInterferenceBaseTest(A2dpBaseTest):
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.bt_logger = log.BluetoothMetricLogger.for_test_case()
+        self.start_time = time.time()
+        req_params = [
+            'attenuation_vector', 'wifi_networks', 'codecs', 'custom_files',
+            'audio_params'
+        ]
+        self.unpack_userparams(req_params)
+        for file in self.custom_files:
+            if 'static_interference' in file:
+                self.static_wifi_interference = unpack_custom_file(file)
+            elif 'dynamic_interference' in file:
+                self.dynamic_wifi_interference = unpack_custom_file(file)
+
+    def setup_class(self):
+        super().setup_class()
+        # Build object to store all necessary information for each pair of wifi
+        # interference setup: phone, ap, network, channel, iperf server port/ip
+        # object and bridge interface configs
+        if len(self.android_devices) < 5 or len(self.attenuators) < 4:
+            self.log.error('Need a 4 channel attenuator and 5 android phones'
+                           'please update the config file')
+        self.wifi_int_pairs = []
+        for i in range(len(self.attenuators) - 1):
+            tmp_dict = {
+                'dut': self.android_devices[i + 1],
+                'ap': self.access_points[i],
+                'network': self.wifi_networks[i],
+                'channel': self.wifi_networks[i]['channel'],
+                'iperf_server': self.iperf_servers[i],
+                'attenuator': self.attenuators[i + 1],
+                'ether_int': self.packet_senders[i],
+                'iperf_client':
+                ipc.IPerfClientOverAdb(self.android_devices[i + 1])
+            }
+            tmp_obj = ObjNew(**tmp_dict)
+            self.wifi_int_pairs.append(tmp_obj)
+        ##Setup connection between WiFi APs and Phones and get DHCP address
+        # for the interface
+        for obj in self.wifi_int_pairs:
+            brconfigs = setup_ap_connection(obj.dut, obj.network, obj.ap)
+            iperf_server_address = wputils.wait_for_dhcp(
+                obj.ether_int.interface)
+            setattr(obj, 'server_address', iperf_server_address)
+            setattr(obj, 'brconfigs', brconfigs)
+            obj.attenuator.set_atten(MAX_ATTENUATION)
+        # Enable BQR on master and slave Android device
+        btutils.enable_bqr(self.dut)
+        btutils.enable_bqr(self.bt_device_controller)
+
+    def teardown_class(self):
+        super().teardown_class()
+        for obj in self.wifi_int_pairs:
+            obj.ap.bridge.teardown(obj.brconfigs)
+            self.log.info('Stop IPERF server at port {}'.format(
+                obj.iperf_server.port))
+            obj.iperf_server.stop()
+            self.log.info('Stop IPERF process on {}'.format(obj.dut.serial))
+            obj.dut.adb.shell('pkill -9 iperf3')
+            #only for glinux machine
+            #            wputils.bring_down_interface(obj.ether_int.interface)
+            obj.attenuator.set_atten(MAX_ATTENUATION)
+            obj.ap.close()
+
+    def teardown_test(self):
+
+        super().teardown_test()
+
+        for obj in self.wifi_int_pairs:
+            obj.attenuator.set_atten(MAX_ATTENUATION)
+
+    def play_and_record_audio(self, duration, queue):
+        """Play and record audio for a set duration.
+
+        Args:
+            duration: duration in seconds for music playing
+            que: multiprocess que to store the return value of this function
+        Returns:
+            audio_captured: captured audio file path
+        """
+
+        self.log.info('Play and record audio for {} second'.format(duration))
+        self.media.play()
+        self.audio_device.start()
+        time.sleep(duration)
+        audio_captured = self.audio_device.stop()
+        self.media.stop()
+        self.log.info('Audio play and record stopped')
+        asserts.assert_true(audio_captured, 'Audio not recorded')
+        queue.put(audio_captured)
+
+    def locate_interference_pair_by_channel(self, interference_channels):
+        """Function to find which attenautor to set based on channel info
+        Args:
+            interference_channels: list of interference channels
+        Return:
+            interference_pair_indices: list of indices for interference pair
+                in self.wifi_int_pairs
+        """
+        interference_pair_indices = []
+        for chan in interference_channels:
+            for i in range(len(self.wifi_int_pairs)):
+                if self.wifi_int_pairs[i].channel == chan:
+                    interference_pair_indices.append(i)
+        return interference_pair_indices
+
+    def get_interference_rssi(self):
+        """Function to read wifi interference RSSI level."""
+
+        bssids = []
+        self.interference_rssi = []
+        wutils.wifi_toggle_state(self.android_devices[0], True)
+        for item in self.wifi_int_pairs:
+            ssid = item.network['SSID']
+            bssid = item.ap.get_bssid_from_ssid(ssid, '2g')
+            bssids.append(bssid)
+            interference_rssi_dict = {
+                "ssid": ssid,
+                "bssid": bssid,
+                "chan": item.channel,
+                "rssi": 0
+            }
+            self.interference_rssi.append(interference_rssi_dict)
+        scaned_rssi = wpeutils.get_scan_rssi(self.android_devices[0],
+                                             bssids,
+                                             num_measurements=2)
+        for item in self.interference_rssi:
+            item['rssi'] = scaned_rssi[item['bssid']]['mean']
+            self.log.info('Interference RSSI at channel {} is {} dBm'.format(
+                item['chan'], item['rssi']))
+        wutils.wifi_toggle_state(self.android_devices[0], False)
diff --git a/acts_tests/acts_contrib/test_utils/bt/BtMetricsBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/BtMetricsBaseTest.py
new file mode 100644
index 0000000..02ed03a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/BtMetricsBaseTest.py
@@ -0,0 +1,74 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+import os
+
+from acts.libs.proto.proto_utils import parse_proto_to_ascii
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_metrics_utils import get_bluetooth_metrics
+from acts.utils import dump_string_to_file
+
+
+class BtMetricsBaseTest(BluetoothBaseTest):
+    """
+    Base class for tests that requires dumping and parsing Bluetooth Metrics
+    """
+
+    def __init__(self, controllers):
+        BluetoothBaseTest.__init__(self, controllers)
+        self.ad = self.android_devices[0]
+
+    def setup_class(self):
+        """
+        This method finds bluetooth protobuf definitions from config file,
+        compile the protobuf and create a log directory for metrics dumping
+        :return: True on success, False on failure
+        """
+        super(BtMetricsBaseTest, self).setup_class()
+        for ad in self.android_devices:
+            ad.metrics_path = os.path.join(ad.log_path, "BluetoothMetrics")
+            os.makedirs(ad.metrics_path, exist_ok=True)
+        return True
+
+    def setup_test(self):
+        """
+        This method clears the current metrics, should be called after child
+        class setup_test()
+        :return: True
+        """
+        super(BtMetricsBaseTest, self).setup_test()
+        # Clear all metrics
+        for ad in self.android_devices:
+            get_bluetooth_metrics(ad)
+        return True
+
+    def collect_bluetooth_manager_metrics_logs(self, ads):
+        """
+        Collect Bluetooth metrics logs, save an ascii log to disk and return
+        both binary and ascii logs to caller
+        :param ads: list of active Android devices
+        :return: List of binary metrics logs,
+                List of ascii metrics logs
+        """
+        bluetooth_logs = []
+        bluetooth_logs_ascii = []
+        for ad in ads:
+            serial = ad.serial
+            out_name = "{}_{}".format(serial, "bluetooth_metrics.txt")
+            bluetooth_log = get_bluetooth_metrics(ad)
+            bluetooth_log_ascii = parse_proto_to_ascii(bluetooth_log)
+            dump_string_to_file(bluetooth_log_ascii,
+                                os.path.join(ad.metrics_path, out_name))
+            bluetooth_logs.append(bluetooth_log)
+            bluetooth_logs_ascii.append(bluetooth_log_ascii)
+        return bluetooth_logs, bluetooth_logs_ascii
diff --git a/acts_tests/acts_contrib/test_utils/bt/BtSarBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/BtSarBaseTest.py
new file mode 100644
index 0000000..6b6e548
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/BtSarBaseTest.py
@@ -0,0 +1,739 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import re
+import time
+import logging
+import pandas as pd
+
+from acts import asserts
+from acts.libs.proc import job
+from acts.base_test import BaseTestClass
+
+from acts.metrics.loggers.blackbox import BlackboxMetricLogger
+from acts_contrib.test_utils.bt.bt_power_test_utils import MediaControl
+from acts_contrib.test_utils.bt.ble_performance_test_utils import run_ble_throughput_and_read_rssi
+from acts_contrib.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bt_factory
+
+import acts_contrib.test_utils.bt.bt_test_utils as bt_utils
+import acts_contrib.test_utils.wifi.wifi_performance_test_utils as wifi_utils
+
+PHONE_MUSIC_FILE_DIRECTORY = '/sdcard/Music'
+
+FORCE_SAR_ADB_COMMAND = ('am broadcast -n'
+                         'com.google.android.apps.scone/.coex.TestReceiver -a '
+                         'com.google.android.apps.scone.coex.SIMULATE_STATE ')
+
+SLEEP_DURATION = 2
+
+DEFAULT_DURATION = 5
+DEFAULT_MAX_ERROR_THRESHOLD = 2
+DEFAULT_AGG_MAX_ERROR_THRESHOLD = 2
+FIXED_ATTENUATION = 36
+
+
+class BtSarBaseTest(BaseTestClass):
+    """ Base class for all BT SAR Test classes.
+
+        This class implements functions common to BT SAR test Classes.
+    """
+    BACKUP_BT_SAR_TABLE_NAME = 'backup_bt_sar_table.csv'
+
+    def __init__(self, controllers):
+        BaseTestClass.__init__(self, controllers)
+        self.power_file_paths = [
+            '/vendor/etc/bluetooth_power_limits.csv',
+            '/data/vendor/radio/bluetooth_power_limits.csv'
+        ]
+        self.sar_test_result = BlackboxMetricLogger.for_test_case(
+            metric_name='pass')
+        self.sar_file_name = os.path.basename(self.power_file_paths[0])
+        self.power_column = 'BluetoothPower'
+        self.REG_DOMAIN_DICT = {
+            ('us', 'ca', 'in'): 'US',
+            ('uk', 'fr', 'es', 'de', 'it', 'ie', 'sg', 'au', 'tw'): 'EU',
+            ('jp', ): 'JP'
+        }
+
+    def setup_class(self):
+        """Initializes common test hardware and parameters.
+
+        This function initializes hardware and compiles parameters that are
+        common to all tests in this class and derived classes.
+        """
+        super().setup_class()
+
+        self.test_params = self.user_params.get('bt_sar_test_params', {})
+        if not self.test_params:
+            self.log.warning(
+                'bt_sar_test_params was not found in the config file.')
+
+        self.user_params.update(self.test_params)
+        req_params = ['bt_devices', 'calibration_params', 'custom_files']
+
+        self.unpack_userparams(
+            req_params,
+            country_code='us',
+            duration=DEFAULT_DURATION,
+            sort_order=None,
+            max_error_threshold=DEFAULT_MAX_ERROR_THRESHOLD,
+            agg_error_threshold=DEFAULT_AGG_MAX_ERROR_THRESHOLD,
+            tpc_threshold=[2, 8],
+            sar_margin={
+                'BDR': 0,
+                'EDR': 0,
+                'BLE': 0
+            })
+
+        self.attenuator = self.attenuators[0]
+        self.dut = self.android_devices[0]
+
+        for key in self.REG_DOMAIN_DICT.keys():
+            if self.country_code.lower() in key:
+                self.reg_domain = self.REG_DOMAIN_DICT[key]
+
+        self.sar_version_2 = False
+
+        if 'Error' not in self.dut.adb.shell('bluetooth_sar_test -r'):
+            #Flag for SAR version 2
+            self.sar_version_2 = True
+            self.power_column = 'BluetoothEDRPower'
+            self.power_file_paths[0] = os.path.join(
+                os.path.dirname(self.power_file_paths[0]),
+                'bluetooth_power_limits_{}.csv'.format(self.reg_domain))
+            self.sar_file_name = os.path.basename(self.power_file_paths[0])
+
+        if self.sar_version_2:
+            custom_file_suffix = 'version2'
+        else:
+            custom_file_suffix = 'version1'
+
+        for file in self.custom_files:
+            if 'custom_sar_table_{}.csv'.format(custom_file_suffix) in file:
+                self.custom_sar_path = file
+                break
+        else:
+            raise RuntimeError('Custom Sar File is missing')
+
+        self.sar_file_path = self.power_file_paths[0]
+        self.atten_min = 0
+        self.atten_max = int(self.attenuator.get_max_atten())
+
+        # Get music file and push it to the phone and initialize Media controller
+        music_files = self.user_params.get('music_files', [])
+        if music_files:
+            music_src = music_files[0]
+            music_dest = PHONE_MUSIC_FILE_DIRECTORY
+            success = self.dut.push_system_file(music_src, music_dest)
+            if success:
+                self.music_file = os.path.join(PHONE_MUSIC_FILE_DIRECTORY,
+                                               os.path.basename(music_src))
+            # Initialize media_control class
+            self.media = MediaControl(self.dut, self.music_file)
+
+        #Initializing BT device controller
+        if self.bt_devices:
+            attr, idx = self.bt_devices.split(':')
+            self.bt_device_controller = getattr(self, attr)[int(idx)]
+            self.bt_device = bt_factory().generate(self.bt_device_controller)
+        else:
+            self.log.error('No BT devices config is provided!')
+
+        bt_utils.enable_bqr(self.android_devices)
+
+        self.log_path = os.path.join(logging.log_path, 'results')
+        os.makedirs(self.log_path, exist_ok=True)
+
+        # Reading BT SAR table from the phone
+        self.bt_sar_df = self.read_sar_table(self.dut)
+
+    def setup_test(self):
+        super().setup_test()
+
+        #Reset SAR test result to 0 before every test
+        self.sar_test_result.metric_value = 0
+
+        # Starting BT on the master
+        self.dut.droid.bluetoothFactoryReset()
+        bt_utils.enable_bluetooth(self.dut.droid, self.dut.ed)
+
+        # Starting BT on the slave
+        self.bt_device.reset()
+        self.bt_device.power_on()
+
+        # Connect master and slave
+        bt_utils.connect_phone_to_headset(self.dut, self.bt_device, 60)
+
+        # Playing music
+        self.media.play()
+
+        # Find and set PL10 level for the DUT
+        self.pl10_atten = self.set_PL10_atten_level(self.dut)
+        self.attenuator.set_atten(self.pl10_atten)
+
+    def teardown_test(self):
+        #Stopping Music
+        if hasattr(self, 'media'):
+            self.media.stop()
+
+        # Stopping BT on slave
+        self.bt_device.reset()
+        self.bt_device.power_off()
+
+        #Stopping BT on master
+        bt_utils.disable_bluetooth(self.dut.droid)
+
+        #Resetting the atten to initial levels
+        self.attenuator.set_atten(self.atten_min)
+        self.log.info('Attenuation set to {} dB'.format(self.atten_min))
+
+    def teardown_class(self):
+
+        super().teardown_class()
+        self.dut.droid.bluetoothFactoryReset()
+
+        # Stopping BT on slave
+        self.bt_device.reset()
+        self.bt_device.power_off()
+
+        #Stopping BT on master
+        bt_utils.disable_bluetooth(self.dut.droid)
+
+    def save_sar_plot(self, df):
+        """ Saves SAR plot to the path given.
+
+        Args:
+            df: Processed SAR table sweep results
+        """
+        self.plot.add_line(
+            df.index,
+            df['expected_tx_power'],
+            legend='expected',
+            marker='circle')
+        self.plot.add_line(
+            df.index,
+            df['measured_tx_power'],
+            legend='measured',
+            marker='circle')
+        self.plot.add_line(
+            df.index, df['delta'], legend='delta', marker='circle')
+
+        results_file_path = os.path.join(self.log_path, '{}.html'.format(
+            self.current_test_name))
+        self.plot.generate_figure()
+        wifi_utils.BokehFigure.save_figures([self.plot], results_file_path)
+
+    def sweep_power_cap(self):
+        sar_df = self.bt_sar_df
+        sar_df['BDR_power_cap'] = -128
+        sar_df['EDR_power_cap'] = -128
+        sar_df['BLE_power_cap'] = -128
+
+        if self.sar_version_2:
+            power_column_dict = {
+                'BDR': 'BluetoothBDRPower',
+                'EDR': 'BluetoothEDRPower',
+                'BLE': 'BluetoothLEPower'
+            }
+        else:
+            power_column_dict = {'EDR': self.power_column}
+
+        power_cap_error = False
+
+        for type, column_name in power_column_dict.items():
+
+            self.log.info("Performing sanity test on {}".format(type))
+            # Iterating through the BT SAR scenarios
+            for scenario in range(0, self.bt_sar_df.shape[0]):
+                # Reading BT SAR table row into dict
+                read_scenario = sar_df.loc[scenario].to_dict()
+                start_time = self.dut.adb.shell('date +%s.%m')
+                time.sleep(SLEEP_DURATION)
+
+                # Setting SAR state to the read BT SAR row
+                self.set_sar_state(self.dut, read_scenario, self.country_code)
+
+                # Reading device power cap from logcat after forcing SAR State
+                scenario_power_cap = self.get_current_power_cap(
+                    self.dut, start_time, type=type)
+                sar_df.loc[scenario, '{}_power_cap'.format(
+                    type)] = scenario_power_cap
+                self.log.info(
+                    'scenario: {}, '
+                    'sar_power: {}, power_cap:{}'.format(
+                        scenario, sar_df.loc[scenario, column_name],
+                        sar_df.loc[scenario, '{}_power_cap'.format(type)]))
+
+        if not sar_df['{}_power_cap'.format(type)].equals(sar_df[column_name]):
+            power_cap_error = True
+
+        results_file_path = os.path.join(self.log_path, '{}.csv'.format(
+            self.current_test_name))
+        sar_df.to_csv(results_file_path)
+
+        return power_cap_error
+
+    def sweep_table(self,
+                    client_ad=None,
+                    server_ad=None,
+                    client_conn_id=None,
+                    gatt_server=None,
+                    gatt_callback=None,
+                    isBLE=False):
+        """Iterates over the BT SAR table and forces signal states.
+
+        Iterates over BT SAR table and forces signal states,
+        measuring RSSI and power level for each state.
+
+        Args:
+            client_ad: the Android device performing the connection.
+            server_ad: the Android device accepting the connection.
+            client_conn_id: the client connection ID.
+            gatt_server: the gatt server
+            gatt_callback: Gatt callback objec
+            isBLE : boolean variable for BLE connection
+        Returns:
+            sar_df : SAR table sweep results in pandas dataframe
+        """
+
+        sar_df = self.bt_sar_df.copy()
+        sar_df['power_cap'] = -128
+        sar_df['slave_rssi'] = -128
+        sar_df['master_rssi'] = -128
+        sar_df['ble_rssi'] = -128
+        sar_df['pwlv'] = -1
+
+        # Sorts the table
+        if self.sort_order:
+            if self.sort_order.lower() == 'ascending':
+                sar_df = sar_df.sort_values(
+                    by=[self.power_column], ascending=True)
+            else:
+                sar_df = sar_df.sort_values(
+                    by=[self.power_column], ascending=False)
+            sar_df = sar_df.reset_index(drop=True)
+
+        # Sweeping BT SAR table
+        for scenario in range(sar_df.shape[0]):
+            # Reading BT SAR Scenario from the table
+            read_scenario = sar_df.loc[scenario].to_dict()
+
+            start_time = self.dut.adb.shell('date +%s.%m')
+            time.sleep(SLEEP_DURATION)
+
+            #Setting SAR State
+            self.set_sar_state(self.dut, read_scenario, self.country_code)
+
+            if isBLE:
+                sar_df.loc[scenario, 'power_cap'] = self.get_current_power_cap(
+                    self.dut, start_time, type='BLE')
+
+                sar_df.loc[
+                    scenario, 'ble_rssi'] = run_ble_throughput_and_read_rssi(
+                        client_ad, server_ad, client_conn_id, gatt_server,
+                        gatt_callback)
+
+                self.log.info('scenario:{}, power_cap:{},  ble_rssi:{}'.format(
+                    scenario, sar_df.loc[scenario, 'power_cap'],
+                    sar_df.loc[scenario, 'ble_rssi']))
+            else:
+                sar_df.loc[scenario, 'power_cap'] = self.get_current_power_cap(
+                    self.dut, start_time)
+
+                processed_bqr_results = bt_utils.get_bt_metric(
+                    self.android_devices, self.duration)
+                sar_df.loc[scenario, 'slave_rssi'] = processed_bqr_results[
+                    'rssi'][self.bt_device_controller.serial]
+                sar_df.loc[scenario, 'master_rssi'] = processed_bqr_results[
+                    'rssi'][self.dut.serial]
+                sar_df.loc[scenario, 'pwlv'] = processed_bqr_results['pwlv'][
+                    self.dut.serial]
+                self.log.info(
+                    'scenario:{}, power_cap:{},  s_rssi:{}, m_rssi:{}, m_pwlv:{}'
+                    .format(scenario, sar_df.loc[scenario, 'power_cap'],
+                            sar_df.loc[scenario, 'slave_rssi'],
+                            sar_df.loc[scenario, 'master_rssi'],
+                            sar_df.loc[scenario, 'pwlv']))
+
+        self.log.info('BT SAR Table swept')
+
+        return sar_df
+
+    def process_table(self, sar_df):
+        """Processes the results of sweep_table and computes BT TX power.
+
+        Processes the results of sweep_table and computes BT TX power
+        after factoring in the path loss and FTM offsets.
+
+        Args:
+             sar_df: BT SAR table after the sweep
+
+        Returns:
+            sar_df: processed BT SAR table
+        """
+
+        sar_df['pathloss'] = self.calibration_params['pathloss']
+
+        if hasattr(self, 'pl10_atten'):
+            sar_df['atten'] = self.pl10_atten
+        else:
+            sar_df['atten'] = FIXED_ATTENUATION
+
+        # BT SAR Backoff for each scenario
+        if self.sar_version_2:
+            #Reads OTP values from the phone
+            self.otp = bt_utils.read_otp(self.dut)
+
+            #OTP backoff
+            edr_otp = min(0, float(self.otp['EDR']['10']))
+            bdr_otp = min(0, float(self.otp['BR']['10']))
+            ble_otp = min(0, float(self.otp['BLE']['10']))
+
+            # EDR TX Power for PL10
+            edr_tx_power_pl10 = self.calibration_params['target_power']['EDR']['10'] - edr_otp
+
+            # BDR TX Power for PL10
+            bdr_tx_power_pl10 = self.calibration_params['target_power']['BDR']['10'] - bdr_otp
+
+            # RSSI being measured is BDR
+            offset = bdr_tx_power_pl10 - edr_tx_power_pl10
+
+            # BDR-EDR offset
+            sar_df['offset'] = offset
+
+            # Max TX power permissible
+            sar_df['max_power'] = self.calibration_params['max_power']
+
+            # Adding a target power column
+            if 'ble_rssi' in sar_df.columns:
+                sar_df[
+                    'target_power'] = self.calibration_params['target_power']['BLE']['10'] - ble_otp
+            else:
+                sar_df['target_power'] = sar_df['pwlv'].astype(str).map(
+                    self.calibration_params['target_power']['EDR']) - edr_otp
+
+            #Translates power_cap values to expected TX power level
+            sar_df['cap_tx_power'] = sar_df['power_cap'] / 4.0
+
+            sar_df['expected_tx_power'] = sar_df[[
+                'cap_tx_power', 'target_power', 'max_power'
+            ]].min(axis=1)
+
+            if hasattr(self, 'pl10_atten'):
+                sar_df[
+                    'measured_tx_power'] = sar_df['slave_rssi'] + sar_df['pathloss'] + self.pl10_atten - offset
+            else:
+                sar_df[
+                    'measured_tx_power'] = sar_df['ble_rssi'] + sar_df['pathloss'] + FIXED_ATTENUATION
+
+        else:
+
+            # Adding a target power column
+            sar_df['target_power'] = sar_df['pwlv'].astype(str).map(
+                self.calibration_params['target_power']['EDR']['10'])
+
+            # Adding a ftm  power column
+            sar_df['ftm_power'] = sar_df['pwlv'].astype(str).map(
+                self.calibration_params['ftm_power']['EDR'])
+            sar_df[
+                'backoff'] = sar_df['target_power'] - sar_df['power_cap'] / 4.0
+
+            sar_df[
+                'expected_tx_power'] = sar_df['ftm_power'] - sar_df['backoff']
+            sar_df[
+                'measured_tx_power'] = sar_df['slave_rssi'] + sar_df['pathloss'] + self.pl10_atten
+
+        sar_df[
+            'delta'] = sar_df['expected_tx_power'] - sar_df['measured_tx_power']
+
+        self.log.info('Sweep results processed')
+
+        results_file_path = os.path.join(self.log_path, self.current_test_name)
+        sar_df.to_csv('{}.csv'.format(results_file_path))
+        self.save_sar_plot(sar_df)
+
+        return sar_df
+
+    def process_results(self, sar_df, type='EDR'):
+        """Determines the test results of the sweep.
+
+         Parses the processed table with computed BT TX power values
+         to return pass or fail.
+
+        Args:
+             sar_df: processed BT SAR table
+        """
+        if self.sar_version_2:
+            breach_error_result = (
+                sar_df['expected_tx_power'] + self.sar_margin[type] >
+                sar_df['measured_tx_power']).all()
+            if not breach_error_result:
+                asserts.fail('Measured TX power exceeds expected')
+
+        else:
+            # checks for errors at particular points in the sweep
+            max_error_result = abs(
+                sar_df['delta']) > self.max_error_threshold[type]
+            if max_error_result:
+                asserts.fail('Maximum Error Threshold Exceeded')
+
+            # checks for error accumulation across the sweep
+            if sar_df['delta'].sum() > self.agg_error_threshold[type]:
+                asserts.fail(
+                    'Aggregate Error Threshold Exceeded. Error: {} Threshold: {}'.
+                    format(sar_df['delta'].sum(), self.agg_error_threshold))
+
+        self.sar_test_result.metric_value = 1
+        asserts.explicit_pass('Measured and Expected Power Values in line')
+
+    def set_sar_state(self, ad, signal_dict, country_code='us'):
+        """Sets the SAR state corresponding to the BT SAR signal.
+
+        The SAR state is forced using an adb command that takes
+        device signals as input.
+
+        Args:
+            ad: android_device object.
+            signal_dict: dict of BT SAR signals read from the SAR file.
+        Returns:
+            enforced_state: dict of device signals.
+        """
+        signal_dict = {k: max(int(v), 0) for (k, v) in signal_dict.items()}
+        signal_dict["Wifi"] = signal_dict['WIFI5Ghz']
+        signal_dict['WIFI2Ghz'] = 0 if signal_dict['WIFI5Ghz'] else 1
+
+        device_state_dict = {
+            ('Earpiece', 'earpiece'): signal_dict['Head'],
+            ('Wifi', 'wifi'): signal_dict['WIFI5Ghz'],
+            ('Wifi 2.4G', 'wifi_24g'): signal_dict['WIFI2Ghz'],
+            ('Voice', 'voice'): 0,
+            ('Wifi AP', 'wifi_ap'): signal_dict['HotspotVoice'],
+            ('Bluetooth', 'bluetooth'): 1,
+            ('Bluetooth media', 'bt_media'): signal_dict['BTMedia'],
+            ('Radio', 'radio_power'): signal_dict['Cell'],
+            ('Motion', 'motion'): signal_dict['IMU'],
+            ('Bluetooth connected', 'bt_connected'): 1
+        }
+
+        if 'BTHotspot' in signal_dict.keys():
+            device_state_dict[('Bluetooth tethering',
+                               'bt_tethering')] = signal_dict['BTHotspot']
+
+        enforced_state = {}
+        sar_state_command = FORCE_SAR_ADB_COMMAND
+        for key in device_state_dict:
+            enforced_state[key[0]] = device_state_dict[key]
+            sar_state_command = '{} --ei {} {}'.format(
+                sar_state_command, key[1], device_state_dict[key])
+        if self.sar_version_2:
+            sar_state_command = '{} --es country_iso "{}"'.format(
+                sar_state_command, country_code.lower())
+
+        #Forcing the SAR state
+        adb_output = ad.adb.shell(sar_state_command)
+
+        # Checking if command was successfully enforced
+        if 'result=0' in adb_output:
+            self.log.info('Requested BT SAR state successfully enforced.')
+            return enforced_state
+        else:
+            self.log.error("Couldn't force BT SAR state.")
+
+    def parse_bt_logs(self, ad, begin_time, regex=''):
+        """Returns bt software stats by parsing logcat since begin_time.
+
+        The quantity to be fetched is dictated by the regex provided.
+
+        Args:
+             ad: android_device object.
+             begin_time: time stamp to start the logcat parsing.
+             regex: regex for fetching the required BT software stats.
+
+        Returns:
+             stat: the desired BT stat.
+        """
+        # Waiting for logcat to update
+        time.sleep(SLEEP_DURATION)
+        bt_adb_log = ad.adb.logcat('-b all -t %s' % begin_time)
+        for line in bt_adb_log.splitlines():
+            if re.findall(regex, line):
+                stat = re.findall(regex, line)[0]
+                return stat
+
+    def set_country_code(self, ad, cc):
+        """Sets the SAR regulatory domain as per given country code
+
+        The SAR regulatory domain is forced using an adb command that takes
+        country code as input.
+
+        Args:
+            ad: android_device object.
+            cc: country code
+        """
+
+        ad.adb.shell("{} --es country_iso {}".format(FORCE_SAR_ADB_COMMAND,
+                                                     cc))
+        self.log.info("Country Code set to {}".format(cc))
+
+    def get_country_code(self, ad, begin_time):
+        """Returns the enforced regulatory domain since begin_time
+
+        Returns enforced regulatory domain since begin_time by parsing logcat.
+        Function should follow a function call to set a country code
+
+        Args:
+            ad : android_device obj
+            begin_time: time stamp to start
+
+        Returns:
+            read enforced regulatory domain
+        """
+
+        reg_domain_regex = "updateRegulatoryDomain:\s+(\S+)"
+        reg_domain = self.parse_bt_logs(ad, begin_time, reg_domain_regex)
+        return reg_domain
+
+    def get_current_power_cap(self, ad, begin_time, type='EDR'):
+        """ Returns the enforced software EDR power cap since begin_time.
+
+        Returns the enforced EDR power cap since begin_time by parsing logcat.
+        Function should follow a function call that forces a SAR state
+
+        Args:
+            ad: android_device obj.
+            begin_time: time stamp to start.
+
+        Returns:
+            read enforced power cap
+        """
+        power_cap_regex_dict = {
+            'BDR': [
+                'Bluetooth powers: BR:\s+(\d+), EDR:\s+\d+',
+                'Bluetooth Tx Power Cap\s+(\d+)'
+            ],
+            'EDR': [
+                'Bluetooth powers: BR:\s+\d+, EDR:\s+(\d+)',
+                'Bluetooth Tx Power Cap\s+(\d+)'
+            ],
+            'BLE': [
+                'Bluetooth powers: BR:\s+\d+, EDR:\s+\d+, BLE:\s+(\d+)',
+                'Bluetooth Tx Power Cap\s+(\d+)'
+            ]
+        }
+
+        power_cap_regex_list = power_cap_regex_dict[type]
+
+        for power_cap_regex in power_cap_regex_list:
+            power_cap = self.parse_bt_logs(ad, begin_time, power_cap_regex)
+            if power_cap:
+                return int(power_cap)
+
+        raise ValueError('Failed to get TX power cap')
+
+    def get_current_device_state(self, ad, begin_time):
+        """ Returns the device state of the android dut since begin_time.
+
+        Returns the device state of the android dut by parsing logcat since
+        begin_time. Function should follow a function call that forces
+        a SAR state.
+
+        Args:
+            ad: android_device obj.
+            begin_time: time stamp to start.
+
+        Returns:
+            device_state: device state of the android device.
+        """
+
+        device_state_regex = 'updateDeviceState: DeviceState: ([\s*\S+\s]+)'
+        time.sleep(SLEEP_DURATION)
+        device_state = self.parse_bt_logs(ad, begin_time, device_state_regex)
+        if device_state:
+            return device_state
+
+        raise ValueError("Couldn't fetch device state")
+
+    def read_sar_table(self, ad, output_path=''):
+        """Extracts the BT SAR table from the phone.
+
+        Extracts the BT SAR table from the phone into the android device
+        log path directory.
+
+        Args:
+            ad: android_device object.
+            output_path: path to custom sar table
+        Returns:
+            df : BT SAR table (as pandas DataFrame).
+        """
+        if not output_path:
+            output_path = os.path.join(ad.device_log_path, self.sar_file_name)
+            ad.adb.pull('{} {}'.format(self.sar_file_path, output_path))
+
+        df = pd.read_csv(output_path)
+        self.log.info('BT SAR table read from the phone')
+        return df
+
+    def push_table(self, ad, src_path, dest_path=''):
+        """Pushes a BT SAR table to the phone.
+
+        Pushes a BT SAR table to the android device and reboots the device.
+        Also creates a backup file if backup flag is True.
+
+        Args:
+            ad: android_device object.
+            src_path: path to the  BT SAR table.
+        """
+        #Copying the to-be-pushed file for logging
+        if os.path.dirname(src_path) != ad.device_log_path:
+            job.run('cp {} {}'.format(src_path, ad.device_log_path))
+
+        #Pushing the file provided in the config
+        if dest_path:
+            ad.push_system_file(src_path, dest_path)
+        else:
+            ad.push_system_file(src_path, self.sar_file_path)
+        self.log.info('BT SAR table pushed')
+        ad.reboot()
+
+        self.bt_sar_df = self.read_sar_table(self.dut, src_path)
+
+    def set_PL10_atten_level(self, ad):
+        """Finds the attenuation level at which the phone is at PL10
+
+        Finds PL10 attenuation level by sweeping the attenuation range.
+        If the power level is not achieved during sweep,
+        returns the max atten level
+
+        Args:
+            ad: android object class
+        Returns:
+            atten : attenuation level when the phone is at PL10
+        """
+        BT_SAR_ATTEN_STEP = 3
+
+        for atten in range(self.atten_min, self.atten_max, BT_SAR_ATTEN_STEP):
+            self.attenuator.set_atten(atten)
+            # Sleep required for BQR to reflect the change in parameters
+            time.sleep(SLEEP_DURATION)
+            metrics = bt_utils.get_bt_metric(ad)
+            if metrics['pwlv'][ad.serial] == 10:
+                self.log.info(
+                    'PL10 located at {}'.format(atten + BT_SAR_ATTEN_STEP))
+                return atten + BT_SAR_ATTEN_STEP
+
+        self.log.warn(
+            "PL10 couldn't be located in the given attenuation range")
diff --git a/acts_tests/acts_contrib/test_utils/bt/GattConnectedBaseTest.py b/acts_tests/acts_contrib/test_utils/bt/GattConnectedBaseTest.py
new file mode 100644
index 0000000..d6fc433
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/GattConnectedBaseTest.py
@@ -0,0 +1,215 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""
+This is base class for tests that exercises different GATT procedures between two connected devices.
+Setup/Teardown methods take care of establishing connection, and doing GATT DB initialization/discovery.
+"""
+
+from queue import Empty
+
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_service_types
+from acts_contrib.test_utils.bt.bt_constants import gatt_event
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_err
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import gatt_mtu_size
+from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_characteristics
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_descriptors
+from acts_contrib.test_utils.bt.bt_constants import gatt_char_desc_uuids
+from acts_contrib.test_utils.bt.bt_constants import bt_default_timeout
+
+
+class GattConnectedBaseTest(BluetoothBaseTest):
+
+    TEST_SERVICE_UUID = "3846D7A0-69C8-11E4-BA00-0002A5D5C51B"
+    READABLE_CHAR_UUID = "21c0a0bf-ad51-4a2d-8124-b74003e4e8c8"
+    READABLE_DESC_UUID = "aa7edd5a-4d1d-4f0e-883a-d145616a1630"
+    WRITABLE_CHAR_UUID = "aa7edd5a-4d1d-4f0e-883a-d145616a1630"
+    WRITABLE_DESC_UUID = "76d5ed92-ca81-4edb-bb6b-9f019665fb32"
+    NOTIFIABLE_CHAR_UUID = "b2c83efa-34ca-11e6-ac61-9e71128cae77"
+
+    def setup_class(self):
+        super().setup_class()
+        self.cen_ad = self.android_devices[0]
+        self.per_ad = self.android_devices[1]
+
+    def setup_test(self):
+        super(GattConnectedBaseTest, self).setup_test()
+
+        self.gatt_server_callback, self.gatt_server = self._setup_multiple_services(
+        )
+        if not self.gatt_server_callback or not self.gatt_server:
+            raise AssertionError('Service setup failed')
+
+        self.bluetooth_gatt, self.gatt_callback, self.adv_callback = (
+            orchestrate_gatt_connection(self.cen_ad, self.per_ad))
+        self.per_ad.droid.bleStopBleAdvertising(self.adv_callback)
+
+        self.mtu = gatt_mtu_size['min']
+
+        if self.cen_ad.droid.gattClientDiscoverServices(self.bluetooth_gatt):
+            event = self._client_wait(gatt_event['gatt_serv_disc'])
+            self.discovered_services_index = event['data']['ServicesIndex']
+        services_count = self.cen_ad.droid.gattClientGetDiscoveredServicesCount(
+            self.discovered_services_index)
+        self.test_service_index = None
+        for i in range(services_count):
+            disc_service_uuid = (
+                self.cen_ad.droid.gattClientGetDiscoveredServiceUuid(
+                    self.discovered_services_index, i).upper())
+            if disc_service_uuid == self.TEST_SERVICE_UUID:
+                self.test_service_index = i
+                break
+
+        if not self.test_service_index:
+            print("Service not found")
+            return False
+
+        connected_device_list = self.per_ad.droid.gattServerGetConnectedDevices(
+            self.gatt_server)
+        if len(connected_device_list) == 0:
+            self.log.info("No devices connected from peripheral.")
+            return False
+
+        return True
+
+    def teardown_test(self):
+        self.per_ad.droid.gattServerClearServices(self.gatt_server)
+        self.per_ad.droid.gattServerClose(self.gatt_server)
+
+        del self.gatt_server_callback
+        del self.gatt_server
+
+        self._orchestrate_gatt_disconnection(self.bluetooth_gatt,
+                                             self.gatt_callback)
+
+        return super(GattConnectedBaseTest, self).teardown_test()
+
+    def _server_wait(self, gatt_event):
+        return self._timed_pop(gatt_event, self.per_ad,
+                               self.gatt_server_callback)
+
+    def _client_wait(self, gatt_event):
+        return self._timed_pop(gatt_event, self.cen_ad, self.gatt_callback)
+
+    def _timed_pop(self, gatt_event, droid, gatt_callback):
+        expected_event = gatt_event["evt"].format(gatt_callback)
+        try:
+            return droid.ed.pop_event(expected_event, bt_default_timeout)
+        except Empty as emp:
+            raise AssertionError(gatt_event["err"].format(expected_event))
+
+    def _setup_characteristics_and_descriptors(self, droid):
+        characteristic_input = [
+            {
+                'uuid': self.WRITABLE_CHAR_UUID,
+                'property': gatt_characteristic['property_write'] |
+                gatt_characteristic['property_write_no_response'],
+                'permission': gatt_characteristic['permission_write']
+            },
+            {
+                'uuid': self.READABLE_CHAR_UUID,
+                'property': gatt_characteristic['property_read'],
+                'permission': gatt_characteristic['permission_read']
+            },
+            {
+                'uuid': self.NOTIFIABLE_CHAR_UUID,
+                'property': gatt_characteristic['property_notify'] |
+                gatt_characteristic['property_indicate'],
+                'permission': gatt_characteristic['permission_read']
+            },
+        ]
+        descriptor_input = [{
+            'uuid': self.WRITABLE_DESC_UUID,
+            'property': gatt_descriptor['permission_read'] |
+            gatt_characteristic['permission_write'],
+        }, {
+            'uuid': self.READABLE_DESC_UUID,
+            'property': gatt_descriptor['permission_read'] |
+            gatt_descriptor['permission_write'],
+        }, {
+            'uuid': gatt_char_desc_uuids['client_char_cfg'],
+            'property': gatt_descriptor['permission_read'] |
+            gatt_descriptor['permission_write'],
+        }]
+        characteristic_list = setup_gatt_characteristics(droid,
+                                                         characteristic_input)
+        self.notifiable_char_index = characteristic_list[2]
+        descriptor_list = setup_gatt_descriptors(droid, descriptor_input)
+        return characteristic_list, descriptor_list
+
+    def _orchestrate_gatt_disconnection(self, bluetooth_gatt, gatt_callback):
+        self.log.info("Disconnecting from peripheral device.")
+        try:
+            disconnect_gatt_connection(self.cen_ad, bluetooth_gatt,
+                                       gatt_callback)
+        except GattTestUtilsError as err:
+            log.error(err)
+            return False
+        self.cen_ad.droid.gattClientClose(bluetooth_gatt)
+        return True
+
+    def _find_service_added_event(self, gatt_server_callback, uuid):
+        expected_event = gatt_cb_strings['serv_added'].format(
+            gatt_server_callback)
+        try:
+            event = self.per_ad.ed.pop_event(expected_event,
+                                             bt_default_timeout)
+        except Empty:
+            self.log.error(gatt_cb_err['serv_added_err'].format(
+                expected_event))
+            return False
+        if event['data']['serviceUuid'].lower() != uuid.lower():
+            self.log.error("Uuid mismatch. Found: {}, Expected {}.".format(
+                event['data']['serviceUuid'], uuid))
+            return False
+        return True
+
+    def _setup_multiple_services(self):
+        gatt_server_callback = (
+            self.per_ad.droid.gattServerCreateGattServerCallback())
+        gatt_server = self.per_ad.droid.gattServerOpenGattServer(
+            gatt_server_callback)
+        characteristic_list, descriptor_list = (
+            self._setup_characteristics_and_descriptors(self.per_ad.droid))
+        self.per_ad.droid.gattServerCharacteristicAddDescriptor(
+            characteristic_list[0], descriptor_list[0])
+        self.per_ad.droid.gattServerCharacteristicAddDescriptor(
+            characteristic_list[1], descriptor_list[1])
+        self.per_ad.droid.gattServerCharacteristicAddDescriptor(
+            characteristic_list[2], descriptor_list[2])
+        gatt_service3 = self.per_ad.droid.gattServerCreateService(
+            self.TEST_SERVICE_UUID, gatt_service_types['primary'])
+        for characteristic in characteristic_list:
+            self.per_ad.droid.gattServerAddCharacteristicToService(
+                gatt_service3, characteristic)
+        self.per_ad.droid.gattServerAddService(gatt_server, gatt_service3)
+        result = self._find_service_added_event(gatt_server_callback,
+                                                self.TEST_SERVICE_UUID)
+        if not result:
+            return False, False
+        return gatt_server_callback, gatt_server
+
+    def assertEqual(self, first, second, msg=None):
+        if not first == second:
+            if not msg:
+                raise AssertionError('%r != %r' % (first, second))
+            else:
+                raise AssertionError(msg + ' %r != %r' % (first, second))
diff --git a/acts_tests/acts_contrib/test_utils/bt/GattEnum.py b/acts_tests/acts_contrib/test_utils/bt/GattEnum.py
new file mode 100644
index 0000000..b65c5f2
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/GattEnum.py
@@ -0,0 +1,304 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from enum import Enum
+from enum import IntEnum
+
+
+class GattCbErr(Enum):
+    CHAR_WRITE_REQ_ERR = "Characteristic Write Request event not found. Expected {}"
+    CHAR_WRITE_ERR = "Characteristic Write event not found. Expected {}"
+    DESC_WRITE_REQ_ERR = "Descriptor Write Request event not found. Expected {}"
+    DESC_WRITE_ERR = "Descriptor Write event not found. Expected {}"
+    CHAR_READ_ERR = "Characteristic Read event not found. Expected {}"
+    CHAR_READ_REQ_ERR = "Characteristic Read Request not found. Expected {}"
+    DESC_READ_ERR = "Descriptor Read event not found. Expected {}"
+    DESC_READ_REQ_ERR = "Descriptor Read Request event not found. Expected {}"
+    RD_REMOTE_RSSI_ERR = "Read Remote RSSI event not found. Expected {}"
+    GATT_SERV_DISC_ERR = "GATT Services Discovered event not found. Expected {}"
+    SERV_ADDED_ERR = "Service Added event not found. Expected {}"
+    MTU_CHANGED_ERR = "MTU Changed event not found. Expected {}"
+    MTU_SERV_CHANGED_ERR = "MTU Server Changed event not found. Expected {}"
+    GATT_CONN_CHANGE_ERR = "GATT Connection Changed event not found. Expected {}"
+    CHAR_CHANGE_ERR = "GATT Characteristic Changed event not fond. Expected {}"
+    PHY_READ_ERR = "Phy Read event not fond. Expected {}"
+    PHY_UPDATE_ERR = "Phy Update event not fond. Expected {}"
+    EXEC_WRITE_ERR = "GATT Execute Write event not found. Expected {}"
+
+
+class GattCbStrings(Enum):
+    CHAR_WRITE_REQ = "GattServer{}onCharacteristicWriteRequest"
+    EXEC_WRITE = "GattServer{}onExecuteWrite"
+    CHAR_WRITE = "GattConnect{}onCharacteristicWrite"
+    DESC_WRITE_REQ = "GattServer{}onDescriptorWriteRequest"
+    DESC_WRITE = "GattConnect{}onDescriptorWrite"
+    CHAR_READ = "GattConnect{}onCharacteristicRead"
+    CHAR_READ_REQ = "GattServer{}onCharacteristicReadRequest"
+    DESC_READ = "GattConnect{}onDescriptorRead"
+    DESC_READ_REQ = "GattServer{}onDescriptorReadRequest"
+    RD_REMOTE_RSSI = "GattConnect{}onReadRemoteRssi"
+    GATT_SERV_DISC = "GattConnect{}onServicesDiscovered"
+    SERV_ADDED = "GattServer{}onServiceAdded"
+    MTU_CHANGED = "GattConnect{}onMtuChanged"
+    MTU_SERV_CHANGED = "GattServer{}onMtuChanged"
+    GATT_CONN_CHANGE = "GattConnect{}onConnectionStateChange"
+    CHAR_CHANGE = "GattConnect{}onCharacteristicChanged"
+    PHY_READ = "GattConnect{}onPhyRead"
+    PHY_UPDATE = "GattConnect{}onPhyUpdate"
+    SERV_PHY_READ = "GattServer{}onPhyRead"
+    SERV_PHY_UPDATE = "GattServer{}onPhyUpdate"
+
+
+class GattEvent(Enum):
+    CHAR_WRITE_REQ = {
+        "evt": GattCbStrings.CHAR_WRITE_REQ.value,
+        "err": GattCbErr.CHAR_WRITE_REQ_ERR.value
+    }
+    EXEC_WRITE = {
+        "evt": GattCbStrings.EXEC_WRITE.value,
+        "err": GattCbErr.EXEC_WRITE_ERR.value
+    }
+    CHAR_WRITE = {
+        "evt": GattCbStrings.CHAR_WRITE.value,
+        "err": GattCbErr.CHAR_WRITE_ERR.value
+    }
+    DESC_WRITE_REQ = {
+        "evt": GattCbStrings.DESC_WRITE_REQ.value,
+        "err": GattCbErr.DESC_WRITE_REQ_ERR.value
+    }
+    DESC_WRITE = {
+        "evt": GattCbStrings.DESC_WRITE.value,
+        "err": GattCbErr.DESC_WRITE_ERR.value
+    }
+    CHAR_READ = {
+        "evt": GattCbStrings.CHAR_READ.value,
+        "err": GattCbErr.CHAR_READ_ERR.value
+    }
+    CHAR_READ_REQ = {
+        "evt": GattCbStrings.CHAR_READ_REQ.value,
+        "err": GattCbErr.CHAR_READ_REQ_ERR.value
+    }
+    DESC_READ = {
+        "evt": GattCbStrings.DESC_READ.value,
+        "err": GattCbErr.DESC_READ_ERR.value
+    }
+    DESC_READ_REQ = {
+        "evt": GattCbStrings.DESC_READ_REQ.value,
+        "err": GattCbErr.DESC_READ_REQ_ERR.value
+    }
+    RD_REMOTE_RSSI = {
+        "evt": GattCbStrings.RD_REMOTE_RSSI.value,
+        "err": GattCbErr.RD_REMOTE_RSSI_ERR.value
+    }
+    GATT_SERV_DISC = {
+        "evt": GattCbStrings.GATT_SERV_DISC.value,
+        "err": GattCbErr.GATT_SERV_DISC_ERR.value
+    }
+    SERV_ADDED = {
+        "evt": GattCbStrings.SERV_ADDED.value,
+        "err": GattCbErr.SERV_ADDED_ERR.value
+    }
+    MTU_CHANGED = {
+        "evt": GattCbStrings.MTU_CHANGED.value,
+        "err": GattCbErr.MTU_CHANGED_ERR.value
+    }
+    GATT_CONN_CHANGE = {
+        "evt": GattCbStrings.GATT_CONN_CHANGE.value,
+        "err": GattCbErr.GATT_CONN_CHANGE_ERR.value
+    }
+    CHAR_CHANGE = {
+        "evt": GattCbStrings.CHAR_CHANGE.value,
+        "err": GattCbErr.CHAR_CHANGE_ERR.value
+    }
+    PHY_READ = {
+        "evt": GattCbStrings.PHY_READ.value,
+        "err": GattCbErr.PHY_READ_ERR.value
+    }
+    PHY_UPDATE = {
+        "evt": GattCbStrings.PHY_UPDATE.value,
+        "err": GattCbErr.PHY_UPDATE_ERR.value
+    }
+    SERV_PHY_READ = {
+        "evt": GattCbStrings.SERV_PHY_READ.value,
+        "err": GattCbErr.PHY_READ_ERR.value
+    }
+    SERV_PHY_UPDATE = {
+        "evt": GattCbStrings.SERV_PHY_UPDATE.value,
+        "err": GattCbErr.PHY_UPDATE_ERR.value
+    }
+
+
+class GattConnectionState(IntEnum):
+    STATE_DISCONNECTED = 0
+    STATE_CONNECTING = 1
+    STATE_CONNECTED = 2
+    STATE_DISCONNECTING = 3
+
+
+class GattCharacteristic(Enum):
+    PROPERTY_BROADCAST = 0x01
+    PROPERTY_READ = 0x02
+    PROPERTY_WRITE_NO_RESPONSE = 0x04
+    PROPERTY_WRITE = 0x08
+    PROPERTY_NOTIFY = 0x10
+    PROPERTY_INDICATE = 0x20
+    PROPERTY_SIGNED_WRITE = 0x40
+    PROPERTY_EXTENDED_PROPS = 0x80
+    PERMISSION_READ = 0x01
+    PERMISSION_READ_ENCRYPTED = 0x02
+    PERMISSION_READ_ENCRYPTED_MITM = 0x04
+    PERMISSION_WRITE = 0x10
+    PERMISSION_WRITE_ENCRYPTED = 0x20
+    PERMISSION_WRITE_ENCRYPTED_MITM = 0x40
+    PERMISSION_WRITE_SIGNED = 0x80
+    PERMISSION_WRITE_SIGNED_MITM = 0x100
+    WRITE_TYPE_DEFAULT = 0x02
+    WRITE_TYPE_NO_RESPONSE = 0x01
+    WRITE_TYPE_SIGNED = 0x04
+    FORMAT_UINT8 = 0x11
+    FORMAT_UINT16 = 0x12
+    FORMAT_UINT32 = 0x14
+    FORMAT_SINT8 = 0x21
+    FORMAT_SINT16 = 0x22
+    FORMAT_SINT32 = 0x24
+    FORMAT_SFLOAT = 0x32
+    FORMAT_FLOAT = 0x34
+
+
+class GattDescriptor(Enum):
+    ENABLE_NOTIFICATION_VALUE = [0x01, 0x00]
+    ENABLE_INDICATION_VALUE = [0x02, 0x00]
+    DISABLE_NOTIFICATION_VALUE = [0x00, 0x00]
+    PERMISSION_READ = 0x01
+    PERMISSION_READ_ENCRYPTED = 0x02
+    PERMISSION_READ_ENCRYPTED_MITM = 0x04
+    PERMISSION_WRITE = 0x10
+    PERMISSION_WRITE_ENCRYPTED = 0x20
+    PERMISSION_WRITE_ENCRYPTED_MITM = 0x40
+    PERMISSION_WRITE_SIGNED = 0x80
+    PERMISSION_WRITE_SIGNED_MITM = 0x100
+
+
+class GattCharDesc(Enum):
+    GATT_CHARAC_EXT_PROPER_UUID = '00002900-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_USER_DESC_UUID = '00002901-0000-1000-8000-00805f9b34fb'
+    GATT_CLIENT_CHARAC_CFG_UUID = '00002902-0000-1000-8000-00805f9b34fb'
+    GATT_SERVER_CHARAC_CFG_UUID = '00002903-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_FMT_UUID = '00002904-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_AGREG_FMT_UUID = '00002905-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_VALID_RANGE_UUID = '00002906-0000-1000-8000-00805f9b34fb'
+    GATT_EXTERNAL_REPORT_REFERENCE = '00002907-0000-1000-8000-00805f9b34fb'
+    GATT_REPORT_REFERENCE = '00002908-0000-1000-8000-00805f9b34fb'
+
+
+class GattCharTypes(Enum):
+    GATT_CHARAC_DEVICE_NAME = '00002a00-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_APPEARANCE = '00002a01-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_PERIPHERAL_PRIV_FLAG = '00002a02-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_RECONNECTION_ADDRESS = '00002a03-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_PERIPHERAL_PREF_CONN = '00002a04-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_SERVICE_CHANGED = '00002a05-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_SYSTEM_ID = '00002a23-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_MODEL_NUMBER_STRING = '00002a24-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_SERIAL_NUMBER_STRING = '00002a25-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_FIRMWARE_REVISION_STRING = '00002a26-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_HARDWARE_REVISION_STRING = '00002a27-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_SOFTWARE_REVISION_STRING = '00002a28-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_MANUFACTURER_NAME_STRING = '00002a29-0000-1000-8000-00805f9b34fb'
+    GATT_CHARAC_PNP_ID = '00002a50-0000-1000-8000-00805f9b34fb'
+
+
+class GattCharacteristicAttrLength(Enum):
+    MTU_ATTR_1 = 1
+    MTU_ATTR_2 = 3
+    MTU_ATTR_3 = 15
+
+
+class CharacteristicValueFormat(Enum):
+    STRING = 0x1
+    BYTE = 0x2
+    FORMAT_SINT8 = 0x21
+    FORMAT_UINT8 = 0x11
+    FORMAT_SINT16 = 0x22
+    FORMAT_UINT16 = 0x12
+    FORMAT_SINT32 = 0x24
+    FORMAT_UINT32 = 0x14
+
+
+class GattService(IntEnum):
+    SERVICE_TYPE_PRIMARY = 0
+    SERVICE_TYPE_SECONDARY = 1
+
+
+class GattConnectionPriority(IntEnum):
+    CONNECTION_PRIORITY_BALANCED = 0
+    CONNECTION_PRIORITY_HIGH = 1
+    CONNECTION_PRIORITY_LOW_POWER = 2
+
+
+class MtuSize(IntEnum):
+    MIN = 23
+    MAX = 217
+
+
+class GattCharacteristicAttrLength(IntEnum):
+    MTU_ATTR_1 = 1
+    MTU_ATTR_2 = 3
+    MTU_ATTR_3 = 15
+
+
+class BluetoothGatt(Enum):
+    GATT_SUCCESS = 0
+    GATT_FAILURE = 0x101
+
+
+class GattTransport(IntEnum):
+    TRANSPORT_AUTO = 0x00
+    TRANSPORT_BREDR = 0x01
+    TRANSPORT_LE = 0x02
+
+
+class GattPhy(IntEnum):
+    PHY_LE_1M = 1
+    PHY_LE_2M = 2
+    PHY_LE_CODED = 3
+
+
+class GattPhyMask(IntEnum):
+    PHY_LE_1M_MASK = 1
+    PHY_LE_2M_MASK = 2
+    PHY_LE_CODED_MASK = 4
+
+
+# TODO Decide whether to continue with Enums or move to dictionaries
+GattServerResponses = {
+    "GATT_SUCCESS": 0x0,
+    "GATT_FAILURE": 0x1,
+    "GATT_READ_NOT_PERMITTED": 0x2,
+    "GATT_WRITE_NOT_PERMITTED": 0x3,
+    "GATT_INVALID_PDU": 0x4,
+    "GATT_INSUFFICIENT_AUTHENTICATION": 0x5,
+    "GATT_REQUEST_NOT_SUPPORTED": 0x6,
+    "GATT_INVALID_OFFSET": 0x7,
+    "GATT_INSUFFICIENT_AUTHORIZATION": 0x8,
+    "GATT_INVALID_ATTRIBUTE_LENGTH": 0xD,
+    "GATT_INSUFFICIENT_ENCRYPTION": 0xF,
+    "GATT_CONNECTION_CONGESTED": 0x8F,
+    "GATT_13_ERR": 0x13,
+    "GATT_12_ERR": 0x12,
+    "GATT_0C_ERR": 0x0C,
+    "GATT_16": 0x16
+}
diff --git a/acts_tests/acts_contrib/test_utils/bt/__init__.py b/acts_tests/acts_contrib/test_utils/bt/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/bt/ble_lib.py b/acts_tests/acts_contrib/test_utils/bt/ble_lib.py
new file mode 100644
index 0000000..4fac563
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/ble_lib.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""
+Ble libraries
+"""
+
+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_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import small_timeout
+from acts_contrib.test_utils.bt.bt_constants import adv_fail
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_on_own_address_read
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_started
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+
+import time
+import os
+
+
+class BleLib():
+    def __init__(self, log, dut):
+        self.advertisement_list = []
+        self.dut = dut
+        self.log = log
+        self.default_timeout = 5
+        self.set_advertisement_list = []
+        self.generic_uuid = "0000{}-0000-1000-8000-00805f9b34fb"
+
+    def _verify_ble_adv_started(self, advertise_callback):
+        """Helper for verifying if an advertisment started or not"""
+        regex = "({}|{})".format(
+            adv_succ.format(advertise_callback),
+            adv_fail.format(advertise_callback))
+        try:
+            event = self.dut.ed.pop_events(regex, 5, small_timeout)
+        except Empty:
+            self.dut.log.error("Failed to get success or failed event.")
+            return
+        if event[0]["name"] == adv_succ.format(advertise_callback):
+            self.dut.log.info("Advertisement started successfully.")
+            return True
+        else:
+            self.dut.log.info("Advertisement failed to start.")
+            return False
+
+    def start_generic_connectable_advertisement(self, line):
+        """Start a connectable LE advertisement"""
+        scan_response = None
+        if line:
+            scan_response = bool(line)
+        self.dut.droid.bleSetAdvertiseSettingsAdvertiseMode(
+            ble_advertise_settings_modes['low_latency'])
+        self.dut.droid.bleSetAdvertiseSettingsIsConnectable(True)
+        advertise_callback, advertise_data, advertise_settings = (
+            generate_ble_advertise_objects(self.dut.droid))
+        if scan_response:
+            self.dut.droid.bleStartBleAdvertisingWithScanResponse(
+                advertise_callback, advertise_data, advertise_settings,
+                advertise_data)
+        else:
+            self.dut.droid.bleStartBleAdvertising(
+                advertise_callback, advertise_data, advertise_settings)
+        if self._verify_ble_adv_started(advertise_callback):
+            self.log.info(
+                "Tracking Callback ID: {}".format(advertise_callback))
+            self.advertisement_list.append(advertise_callback)
+            self.log.info(self.advertisement_list)
+
+    def start_connectable_advertisement_set(self, line):
+        """Start Connectable Advertisement Set"""
+        adv_callback = self.dut.droid.bleAdvSetGenCallback()
+        adv_data = {
+            "includeDeviceName": True,
+        }
+        self.dut.droid.bleAdvSetStartAdvertisingSet(
+            {
+                "connectable": True,
+                "legacyMode": False,
+                "primaryPhy": "PHY_LE_1M",
+                "secondaryPhy": "PHY_LE_1M",
+                "interval": 320
+            }, adv_data, None, None, None, 0, 0, adv_callback)
+        evt = self.dut.ed.pop_event(
+            advertising_set_started.format(adv_callback), self.default_timeout)
+        set_id = evt['data']['setId']
+        self.log.error("did not receive the set started event!")
+        evt = self.dut.ed.pop_event(
+            advertising_set_on_own_address_read.format(set_id),
+            self.default_timeout)
+        address = evt['data']['address']
+        self.log.info("Advertiser address is: {}".format(str(address)))
+        self.set_advertisement_list.append(adv_callback)
+
+    def stop_all_advertisement_set(self, line):
+        """Stop all Advertisement Sets"""
+        for adv in self.set_advertisement_list:
+            try:
+                self.dut.droid.bleAdvSetStopAdvertisingSet(adv)
+            except Exception as err:
+                self.log.error("Failed to stop advertisement: {}".format(err))
+
+    def adv_add_service_uuid_list(self, line):
+        """Add service UUID to the LE advertisement inputs:
+         [uuid1 uuid2 ... uuidN]"""
+        uuids = line.split()
+        uuid_list = []
+        for uuid in uuids:
+            if len(uuid) == 4:
+                uuid = self.generic_uuid.format(line)
+            uuid_list.append(uuid)
+        self.dut.droid.bleSetAdvertiseDataSetServiceUuids(uuid_list)
+
+    def adv_data_include_local_name(self, is_included):
+        """Include local name in the advertisement. inputs: [true|false]"""
+        self.dut.droid.bleSetAdvertiseDataIncludeDeviceName(bool(is_included))
+
+    def adv_data_include_tx_power_level(self, is_included):
+        """Include tx power level in the advertisement. inputs: [true|false]"""
+        self.dut.droid.bleSetAdvertiseDataIncludeTxPowerLevel(
+            bool(is_included))
+
+    def adv_data_add_manufacturer_data(self, line):
+        """Include manufacturer id and data to the advertisment:
+        [id data1 data2 ... dataN]"""
+        info = line.split()
+        manu_id = int(info[0])
+        manu_data = []
+        for data in info[1:]:
+            manu_data.append(int(data))
+        self.dut.droid.bleAddAdvertiseDataManufacturerId(manu_id, manu_data)
+
+    def start_generic_nonconnectable_advertisement(self, line):
+        """Start a nonconnectable LE advertisement"""
+        self.dut.droid.bleSetAdvertiseSettingsAdvertiseMode(
+            ble_advertise_settings_modes['low_latency'])
+        self.dut.droid.bleSetAdvertiseSettingsIsConnectable(False)
+        advertise_callback, advertise_data, advertise_settings = (
+            generate_ble_advertise_objects(self.dut.droid))
+        self.dut.droid.bleStartBleAdvertising(
+            advertise_callback, advertise_data, advertise_settings)
+        if self._verify_ble_adv_started(advertise_callback):
+            self.log.info(
+                "Tracking Callback ID: {}".format(advertise_callback))
+            self.advertisement_list.append(advertise_callback)
+            self.log.info(self.advertisement_list)
+
+    def stop_all_advertisements(self, line):
+        """Stop all LE advertisements"""
+        for callback_id in self.advertisement_list:
+            self.log.info("Stopping Advertisement {}".format(callback_id))
+            self.dut.droid.bleStopBleAdvertising(callback_id)
+            time.sleep(1)
+        self.advertisement_list = []
+
+    def ble_stop_advertisement(self, callback_id):
+        """Stop an LE advertisement"""
+        if not callback_id:
+            self.log.info("Need a callback ID")
+            return
+        callback_id = int(callback_id)
+        if callback_id not in self.advertisement_list:
+            self.log.info("Callback not in list of advertisements.")
+            return
+        self.dut.droid.bleStopBleAdvertising(callback_id)
+        self.advertisement_list.remove(callback_id)
+
+    def start_max_advertisements(self, line):
+        scan_response = None
+        if line:
+            scan_response = bool(line)
+        while (True):
+            try:
+                self.dut.droid.bleSetAdvertiseSettingsAdvertiseMode(
+                    ble_advertise_settings_modes['low_latency'])
+                self.dut.droid.bleSetAdvertiseSettingsIsConnectable(True)
+                advertise_callback, advertise_data, advertise_settings = (
+                    generate_ble_advertise_objects(self.dut.droid))
+                if scan_response:
+                    self.dut.droid.bleStartBleAdvertisingWithScanResponse(
+                        advertise_callback, advertise_data, advertise_settings,
+                        advertise_data)
+                else:
+                    self.dut.droid.bleStartBleAdvertising(
+                        advertise_callback, advertise_data, advertise_settings)
+                if self._verify_ble_adv_started(advertise_callback):
+                    self.log.info(
+                        "Tracking Callback ID: {}".format(advertise_callback))
+                    self.advertisement_list.append(advertise_callback)
+                    self.log.info(self.advertisement_list)
+                else:
+                    self.log.info("Advertisements active: {}".format(
+                        len(self.advertisement_list)))
+                    return False
+            except Exception as err:
+                self.log.info("Advertisements active: {}".format(
+                    len(self.advertisement_list)))
+                return True
diff --git a/acts_tests/acts_contrib/test_utils/bt/ble_performance_test_utils.py b/acts_tests/acts_contrib/test_utils/bt/ble_performance_test_utils.py
new file mode 100644
index 0000000..394e962
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/ble_performance_test_utils.py
@@ -0,0 +1,210 @@
+#!/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 logging
+import time
+import statistics
+from queue import Empty
+from concurrent.futures import ThreadPoolExecutor
+
+from acts_contrib.test_utils.bt.bt_gatt_utils import close_gatt_client
+from acts_contrib.test_utils.bt.bt_coc_test_utils import do_multi_connection_throughput
+from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_err
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import l2cap_coc_header_size
+from acts_contrib.test_utils.bt.bt_gatt_utils import GattTestUtilsError
+from acts_contrib.test_utils.bt.bt_coc_test_utils import orchestrate_coc_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
+
+default_event_timeout = 10
+rssi_read_duration = 25
+
+
+def establish_ble_connection(client_ad, server_ad):
+    """Function to establish BLE connection between two BLE devices.
+
+    Args:
+        client_ad: the Android device performing the connection.
+        server_ad: the Android device accepting the connection.
+    Returns:
+        bluetooth_gatt: GATT object
+        gatt_callback: Gatt callback object
+        adv_callback: advertisement callback object
+        gatt_server: the gatt server
+    """
+    gatt_server_cb = server_ad.droid.gattServerCreateGattServerCallback()
+    gatt_server = server_ad.droid.gattServerOpenGattServer(gatt_server_cb)
+    try:
+        bluetooth_gatt, gatt_callback, adv_callback = (
+            orchestrate_gatt_connection(client_ad, server_ad))
+    except GattTestUtilsError as err:
+        logging.error(err)
+        return False
+    return bluetooth_gatt, gatt_callback, adv_callback, gatt_server
+
+
+def read_ble_rssi(client_ad, gatt_server, gatt_callback):
+    """Function to Read BLE RSSI of the remote BLE device.
+    Args:
+        client_ad: the Android device performing the connection.
+        gatt_server: the gatt server
+        gatt_callback:the gatt connection call back object
+    Returns:
+      ble_rssi: RSSI value of the remote BLE device
+    """
+    AVG_RSSI = []
+    end_time = time.time() + rssi_read_duration
+    logging.info("Reading BLE RSSI for {} sec".format(rssi_read_duration))
+    while time.time() < end_time:
+        expected_event = gatt_cb_strings['rd_remote_rssi'].format(
+            gatt_callback)
+        read_rssi = client_ad.droid.gattClientReadRSSI(gatt_server)
+        if read_rssi:
+            try:
+                event = client_ad.ed.pop_event(expected_event,
+                                               default_event_timeout)
+            except Empty:
+                logging.error(
+                    gatt_cb_err['rd_remote_rssi_err'].format(expected_event))
+                return False
+        rssi_value = event['data']['Rssi']
+        AVG_RSSI.append(rssi_value)
+    logging.debug("First & Last reading of RSSI :{:03d} & {:03d}".format(
+        AVG_RSSI[0], AVG_RSSI[-1]))
+    ble_rssi = statistics.mean(AVG_RSSI)
+    ble_rssi = round(ble_rssi, 2)
+
+    return ble_rssi
+
+
+def ble_coc_connection(client_ad, server_ad):
+    """Sets up the CoC connection between two Android devices.
+
+    Args:
+        client_ad: the Android device performing the connection.
+        server_ad: the Android device accepting the connection.
+
+    Returns:
+        True if connection was successful or false if unsuccessful,
+        gatt_callback: GATT callback object
+        client connection ID: Client connection ID
+        and server connection ID : server connection ID
+    """
+    #secured_conn: True if using secured connection
+    #le_connection_interval: LE Connection interval. 0 means use default.
+    #buffer_size : is the number of bytes per L2CAP data buffer
+    #le_tx_data_length: LE Data Length used by BT Controller to transmit.
+    is_secured = False
+    le_connection_interval = 30
+    buffer_size = 240
+    le_tx_data_length = buffer_size + l2cap_coc_header_size
+    gatt_server_cb = server_ad.droid.gattServerCreateGattServerCallback()
+    gatt_server = server_ad.droid.gattServerOpenGattServer(gatt_server_cb)
+
+    logging.info(
+        "orchestrate_ble_coc_connection. is_secured={}, Connection Interval={}msec, "
+        "buffer_size={}bytes".format(is_secured, le_connection_interval,
+                                     buffer_size))
+    try:
+        status, client_conn_id, server_conn_id, bluetooth_gatt, gatt_callback = orchestrate_coc_connection(
+            client_ad,
+            server_ad,
+            True,
+            is_secured,
+            le_connection_interval,
+            le_tx_data_length,
+            gatt_disconnection=False)
+    except Exception as err:
+        logging.info("Failed to esatablish COC connection".format(err))
+        return 0
+    return True, gatt_callback, gatt_server, bluetooth_gatt, client_conn_id
+
+
+def run_ble_throughput(server_ad, client_conn_id, client_ad,
+                       num_iterations=30):
+    """Function to measure Throughput from one client to one-or-many servers
+
+    Args:
+        server_ad: the Android device accepting the connection.
+        client_conn_id: the client connection ID.
+        client_ad: the Android device performing the connection.
+        num_iterations: The num_iterations is that number of repetitions of each
+        set of buffers r/w.
+    Returns:
+      data_rate: Throughput in terms of bytes per second, 0 if test failed.
+    """
+    # number_buffers is the total number of data buffers to transmit per
+    # set of buffers r/w.
+    # buffer_size is the number of bytes per L2CAP data buffer.
+    number_buffers = 100
+    buffer_size = 240
+    list_server_ad = [server_ad]
+    list_client_conn_id = [client_conn_id]
+    data_rate = do_multi_connection_throughput(client_ad, list_server_ad,
+                                               list_client_conn_id,
+                                               num_iterations, number_buffers,
+                                               buffer_size)
+    if data_rate <= 0:
+        return False
+    data_rate = data_rate * 8
+    logging.info(
+        "run_ble_coc_connection_throughput: throughput=%d bites per sec",
+        data_rate)
+    return data_rate
+
+
+def run_ble_throughput_and_read_rssi(client_ad, server_ad, client_conn_id,
+                                     gatt_server, gatt_callback):
+    """Function to measure ble rssi while sendinng data from client to server
+
+    Args:
+        client_ad: the Android device performing the connection.
+        server_ad: the Android device accepting the connection.
+        client_conn_id: the client connection ID.
+        gatt_server: the gatt server
+        gatt_callback: Gatt callback object
+    Returns:
+      ble_rssi: RSSI value of the remote BLE device.
+    """
+    executor = ThreadPoolExecutor(2)
+    ble_throughput = executor.submit(run_ble_throughput, client_ad,
+                                     client_conn_id, server_ad)
+    ble_rssi = executor.submit(read_ble_rssi, server_ad, gatt_server,
+                               gatt_callback)
+    logging.info("BLE RSSI is:{} dBm with data rate={} bites per sec ".format(
+        ble_rssi.result(), ble_throughput.result()))
+    return ble_rssi.result()
+
+
+def ble_gatt_disconnection(client_ad, bluetooth_gatt, gatt_callback):
+    """Function to disconnect GATT connection between client and server.
+
+    Args:
+        client_ad: the Android device performing the connection.
+        bluetooth_gatt: GATT object
+        gatt_callback:the gatt connection call back object
+    Returns:
+      ble_rssi: RSSI value of the remote BLE device
+    """
+    logging.info("Disconnecting from peripheral device.")
+    try:
+        disconnect_gatt_connection(client_ad, bluetooth_gatt, gatt_callback)
+        close_gatt_client(client_ad, bluetooth_gatt)
+    except GattTestUtilsError as err:
+        logging.error(err)
+        return False
+    return True
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
new file mode 100644
index 0000000..14a42b0
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_carkit_lib.py
@@ -0,0 +1,830 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import time
+import os
+
+from acts.keys import Config
+from acts.utils import rand_ascii_str
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import logcat_strings
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_BLUETOOTH
+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_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 set_audio_route
+from acts_contrib.test_utils.tel.tel_voice_utils import swap_calls
+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.utils import exe_cmd
+from acts.utils import get_current_epoch_time
+
+KEYCODE_VOLUME_UP = "input keyevent 24"
+KEYCODE_VOLUME_DOWN = "input keyevent 25"
+KEYCODE_EVENT_PLAY_PAUSE = "input keyevent 85"
+KEYCODE_MEDIA_STOP = "input keyevent 86"
+KEYCODE_EVENT_NEXT = "input keyevent 87"
+KEYCODE_EVENT_PREVIOUS = "input keyevent 88"
+KEYCODE_MEDIA_REWIND = "input keyevent 89"
+KEYCODE_MEDIA_FAST_FORWARD = "input keyevent 90"
+KEYCODE_MUTE = "input keyevent 91"
+
+default_timeout = 10
+
+
+class E2eBtCarkitLib():
+
+    android_devices = []
+    short_timeout = 3
+    active_call_id = None
+    hold_call_id = None
+    log = None
+    mac_address = None
+
+    def __init__(self, log, target_mac_address=None):
+        self.log = log
+        self.target_mac_address = target_mac_address
+
+    def connect_hsp_helper(self, ad):
+        end_time = time.time() + default_timeout + 10
+        connected_hsp_devices = len(ad.droid.bluetoothHspGetConnectedDevices())
+        while connected_hsp_devices != 1 and time.time() < end_time:
+            try:
+                ad.droid.bluetoothHspConnect(self.target_mac_address)
+                time.sleep(3)
+                if len(ad.droid.bluetoothHspGetConnectedDevices() == 1):
+                    break
+            except Exception:
+                self.log.debug("Failed to connect hsp trying again...")
+            try:
+                ad.droid.bluetoothConnectBonded(self.target_mac_address)
+            except Exception:
+                self.log.info("Failed to connect to bonded device...")
+            connected_hsp_devices = len(
+                ad.droid.bluetoothHspGetConnectedDevices())
+        if connected_hsp_devices != 1:
+            self.log.error("Failed to reconnect to HSP service...")
+            return False
+        self.log.info("Connected to HSP service...")
+        return True
+
+    def setup_multi_call(self, caller0, caller1, callee):
+        outgoing_num = get_phone_number(self.log, callee)
+        if not initiate_call(self.log, caller0, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, callee):
+            self.log.error("Failed to answer call.")
+            return False
+        time.sleep(self.short_timeout)
+        if not initiate_call(self.log, caller1, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, callee):
+            self.log.error("Failed to answer call.")
+            return False
+        return True
+
+    def process_tests(self, tests):
+        for test in tests:
+            try:
+                test()
+            except Exception as err:
+                self.log.error(err)
+
+    def run_suite_hfp_tests(self):
+        tests = [
+            self.outgoing_call_private_number,
+            self.outgoing_call_unknown_contact,
+            self.incomming_call_private_number,
+            self.incomming_call_unknown_contact,
+            self.outgoing_call_multiple_iterations,
+            self.outgoing_call_hsp_disabled_then_enabled_during_call,
+            self.call_audio_routes,
+            self.sms_during_incomming_call,
+            self.multi_incomming_call,
+            self.multi_call_audio_routing,
+            self.multi_call_swap_multiple_times,
+            self.outgoing_call_a2dp_play_before_and_after,
+        ]
+        _process_tests(tests)
+
+    def run_suite_hfp_conf_tests(self):
+        tests = [
+            self.multi_call_join_conference_call,
+            self.multi_call_join_conference_call_hangup_conf_call,
+            self.outgoing_multi_call_join_conference_call,
+            self.multi_call_join_conference_call_audio_routes,
+        ]
+        _process_tests(tests)
+
+    def run_suite_map_tests(self):
+        tests = [
+            self.sms_receive_different_sizes,
+            self.sms_receive_multiple,
+            self.sms_send_outgoing_texts,
+        ]
+        _process_tests(tests)
+
+    def run_suite_avrcp_tests(self):
+        tests = [
+            self.avrcp_play_pause,
+            self.avrcp_next_previous_song,
+            self.avrcp_next_previous,
+            self.avrcp_next_repetative,
+        ]
+        _process_tests(tests)
+
+    def disconnect_reconnect_multiple_iterations(self, pri_dut):
+        iteration_count = 5
+        self.log.info(
+            "Test disconnect-reconnect scenario from phone {} times.".format(
+                iteration_count))
+        self.log.info(
+            "This test will prompt for user interaction after each reconnect.")
+        input("Press enter to execute this testcase...")
+        #Assumes only one devices connected
+        grace_timeout = 4  #disconnect and reconnect timeout
+        for n in range(iteration_count):
+            self.log.info("Test iteration {}.".format(n + 1))
+            self.log.info("Disconnecting device {}...".format(
+                self.target_mac_address))
+            pri_dut.droid.bluetoothDisconnectConnected(self.target_mac_address)
+            # May have to do a longer sleep for carkits.... need to test
+            time.sleep(grace_timeout)
+            self.log.info("Connecting device {}...".format(
+                self.target_mac_address))
+            pri_dut.droid.bluetoothConnectBonded(self.target_mac_address)
+            if not self.connect_hsp_helper(pri_dut):
+                return False
+            start_time = time.time()
+            connected_devices = pri_dut.droid.bluetoothGetConnectedDevices()
+            self.log.info(
+                "Waiting up to 10 seconds for device to reconnect...")
+            while time.time() < start_time + 10 and len(connected_devices) != 1:
+                connected_devices = pri_dut.droid.bluetoothGetConnectedDevices(
+                )
+                time.sleep(1)
+            if len(connected_devices) != 1:
+                self.log.error(
+                    "Failed to reconnect at iteration {}... continuing".format(
+                        n))
+                return False
+            input("Continue to next iteration?")
+        return True
+
+    def disconnect_a2dp_only_then_reconnect(self, pri_dut):
+        self.log.info(
+            "Test disconnect-reconnect a2dp only scenario from phone.")
+        input("Press enter to execute this testcase...")
+        if not pri_dut.droid.bluetoothA2dpDisconnect(self.target_mac_address):
+            self.log.error("Failed to disconnect A2DP service...")
+            return False
+        time.sleep(self.short_timeout)
+        result = input("Confirm A2DP disconnected? (Y/n) ")
+        if result == "n":
+            self.log.error(
+                "Tester confirmed that A2DP did not disconnect. Failing test.")
+            return False
+        if len(pri_dut.droid.bluetoothA2dpGetConnectedDevices()) != 0:
+            self.log.error("Failed to disconnect from A2DP service")
+            return False
+        pri_dut.droid.bluetoothA2dpConnect(self.target_mac_address)
+        time.sleep(self.short_timeout)
+        if len(pri_dut.droid.bluetoothA2dpGetConnectedDevices()) != 1:
+            self.log.error("Failed to reconnect to A2DP service...")
+            return False
+        return True
+
+    def disconnect_hsp_only_then_reconnect(self, pri_dut):
+        self.log.info(
+            "Test disconnect-reconnect hsp only scenario from phone.")
+        input("Press enter to execute this testcase...")
+        if not pri_dut.droid.bluetoothHspDisconnect(self.target_mac_address):
+            self.log.error("Failed to disconnect HSP service...")
+            return False
+        time.sleep(self.short_timeout)
+        result = input("Confirm HFP disconnected? (Y/n) ")
+        pri_dut.droid.bluetoothHspConnect(self.target_mac_address)
+        time.sleep(self.short_timeout)
+        if len(pri_dut.droid.bluetoothHspGetConnectedDevices()) != 1:
+            self.log.error("Failed to connect from HSP service")
+            return False
+        return True
+
+    def disconnect_both_hsp_and_a2dp_then_reconnect(self, pri_dut):
+        self.log.info(
+            "Test disconnect-reconnect hsp and a2dp scenario from phone.")
+        input("Press enter to execute this testcase...")
+        if not pri_dut.droid.bluetoothA2dpDisconnect(self.target_mac_address):
+            self.log.error("Failed to disconnect A2DP service...")
+            return False
+        if not pri_dut.droid.bluetoothHspDisconnect(self.target_mac_address):
+            self.log.error("Failed to disconnect HSP service...")
+            return False
+        time.sleep(self.short_timeout)
+        if len(pri_dut.droid.bluetoothA2dpGetConnectedDevices()) != 0:
+            self.log.error("Failed to disconnect from A2DP service")
+            return False
+        if len(pri_dut.droid.bluetoothHspGetConnectedDevices()) != 0:
+            self.log.error("Failed to disconnect from HSP service")
+            return False
+        result = input("Confirm HFP and A2DP disconnected? (Y/n) ")
+        pri_dut.droid.bluetoothConnectBonded(self.target_mac_address)
+        time.sleep(self.short_timeout)
+        if len(pri_dut.droid.bluetoothA2dpGetConnectedDevices()) != 1:
+            self.log.error("Failed to reconnect to A2DP service...")
+            return False
+        if not self.connect_hsp_helper(pri_dut):
+            return False
+        return True
+
+    def outgoing_call_private_number(self, pri_dut, ter_dut):
+        self.log.info(
+            "Test outgoing call scenario from phone to private number")
+        input("Press enter to execute this testcase...")
+        outgoing_num = "*67" + get_phone_number(self.log, ter_dut)
+        if not initiate_call(self.log, pri_dut, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, ter_dut):
+            self.log.error("Failed to answer call.")
+            return False
+        time.sleep(self.short_timeout)
+        input("Press enter to hangup call...")
+        if not hangup_call(self.log, pri_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def outgoing_call_a2dp_play_before_and_after(self, pri_dut, sec_dut):
+        self.log.info(
+            "Test outgoing call scenario while playing music. Music should resume after call."
+        )
+        pri_dut.adb.shell(KEYCODE_EVENT_PREVIOUS)
+        input(
+            "Press enter to execute this testcase when music is in a play state..."
+        )
+        outgoing_num = get_phone_number(self.log, sec_dut)
+        if not initiate_call(self.log, pri_dut, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, sec_dut):
+            self.log.error("Failed to answer call.")
+            return False
+        time.sleep(self.short_timeout)
+        input("Press enter to hangup call...")
+        if not hangup_call(self.log, pri_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        input("Press enter when music continues to play.")
+        self.log.info("Pausing Music...")
+        pri_dut.adb.shell(KEYCODE_EVENT_PLAY_PAUSE)
+        return True
+
+    def outgoing_call_unknown_contact(self, pri_dut, ter_dut):
+        self.log.info(
+            "Test outgoing call scenario from phone to unknow contact")
+        input("Press enter to execute this testcase...")
+        outgoing_num = get_phone_number(self.log, ter_dut)
+        if not initiate_call(self.log, pri_dut, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, ter_dut):
+            self.log.error("Failed to answer call.")
+            return False
+        time.sleep(self.short_timeout)
+        input("Press enter to hangup call...")
+        if not hangup_call(self.log, pri_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def incomming_call_private_number(self, pri_dut, ter_dut):
+        self.log.info(
+            "Test incomming call scenario to phone from private number")
+        input("Press enter to execute this testcase...")
+        outgoing_num = "*67" + get_phone_number(self.log, pri_dut)
+        if not initiate_call(self.log, ter_dut, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, pri_dut):
+            self.log.error("Failed to answer call.")
+            return False
+        time.sleep(self.short_timeout)
+        input("Press enter to hangup call...")
+        if not hangup_call(self.log, ter_dut):
+            self.log.error("Failed to hangup call")
+            return False
+
+        return True
+
+    def incomming_call_unknown_contact(self, pri_dut, ter_dut):
+        self.log.info(
+            "Test incomming call scenario to phone from unknown contact")
+        input("Press enter to execute this testcase...")
+        outgoing_num = get_phone_number(self.log, pri_dut)
+        if not initiate_call(self.log, ter_dut, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, pri_dut):
+            self.log.error("Failed to answer call.")
+            return False
+        time.sleep(self.short_timeout)
+        input("Press enter to hangup call...")
+        if not hangup_call(self.log, ter_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def outgoing_call_multiple_iterations(self, pri_dut, sec_dut):
+        iteration_count = 3
+        self.log.info(
+            "Test outgoing call scenario from phone {} times from known contact".
+            format(iteration_count))
+        input("Press enter to execute this testcase...")
+        outgoing_num = get_phone_number(self.log, sec_dut)
+        for _ in range(iteration_count):
+            if not initiate_call(self.log, pri_dut, outgoing_num):
+                self.log.error("Failed to initiate call")
+                return False
+            if not wait_and_answer_call(self.log, sec_dut):
+                self.log.error("Failed to answer call.")
+                return False
+            time.sleep(self.short_timeout)
+            if not hangup_call(self.log, pri_dut):
+                self.log.error("Failed to hangup call")
+                return False
+        return True
+
+    def outgoing_call_hsp_disabled_then_enabled_during_call(
+            self, pri_dut, sec_dut):
+        self.log.info(
+            "Test outgoing call hsp disabled then enable during call.")
+        input("Press enter to execute this testcase...")
+        outgoing_num = get_phone_number(self.log, sec_dut)
+        if not pri_dut.droid.bluetoothHspDisconnect(self.target_mac_address):
+            self.log.error("Failed to disconnect HSP service...")
+            return False
+        time.sleep(self.short_timeout)
+        if len(pri_dut.droid.bluetoothHspGetConnectedDevices()) != 0:
+            self.log.error("Failed to disconnect from HSP service")
+            return False
+        if not initiate_call(self.log, pri_dut, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        time.sleep(default_timeout)
+        pri_dut.droid.bluetoothConnectBonded(self.target_mac_address)
+        time.sleep(self.short_timeout)
+        test_result = True
+        if len(pri_dut.droid.bluetoothHspGetConnectedDevices()) != 1:
+            self.log.error("Failed to reconnect to HSP service...")
+            return
+        if not hangup_call(self.log, pri_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return test_result
+
+    def call_audio_routes(self, pri_dut, sec_dut):
+        self.log.info("Test various audio routes scenario from phone.")
+        input("Press enter to execute this testcase...")
+        outgoing_num = get_phone_number(self.log, sec_dut)
+        if not initiate_call(self.log, pri_dut, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, sec_dut):
+            self.log.error("Failed to answer call.")
+            return False
+        time.sleep(self.short_timeout)
+        call_id = pri_dut.droid.telecomCallGetCallIds()[0]
+        pri_dut.droid.telecomCallPlayDtmfTone(call_id, "9")
+        input("Press enter to switch to speaker...")
+        self.log.info("Switching to speaker.")
+        set_audio_route(self.log, pri_dut, AUDIO_ROUTE_SPEAKER)
+        time.sleep(self.short_timeout)
+        if get_audio_route(self.log, pri_dut) != AUDIO_ROUTE_SPEAKER:
+            self.log.error(
+                "Audio Route not set to {}".format(AUDIO_ROUTE_SPEAKER))
+            return False
+        input("Press enter to switch to earpiece...")
+        self.log.info("Switching to earpiece.")
+        set_audio_route(self.log, pri_dut, AUDIO_ROUTE_EARPIECE)
+        time.sleep(self.short_timeout)
+        if get_audio_route(self.log, pri_dut) != AUDIO_ROUTE_EARPIECE:
+            self.log.error(
+                "Audio Route not set to {}".format(AUDIO_ROUTE_EARPIECE))
+            return False
+        input("Press enter to switch to Bluetooth...")
+        self.log.info("Switching to Bluetooth...")
+        set_audio_route(self.log, pri_dut, AUDIO_ROUTE_BLUETOOTH)
+        time.sleep(self.short_timeout)
+        if get_audio_route(self.log, pri_dut) != AUDIO_ROUTE_BLUETOOTH:
+            self.log.error(
+                "Audio Route not set to {}".format(AUDIO_ROUTE_BLUETOOTH))
+            return False
+        input("Press enter to hangup call...")
+        self.log.info("Hanging up call...")
+        pri_dut.droid.telecomCallStopDtmfTone(call_id)
+        if not hangup_call(self.log, pri_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def sms_receive_different_sizes(self, pri_dut, sec_dut):
+        self.log.info("Test recieve sms.")
+        input("Press enter to execute this testcase...")
+        msg = [rand_ascii_str(50), rand_ascii_str(1), rand_ascii_str(500)]
+        if not sms_send_receive_verify(self.log, sec_dut, pri_dut, msg):
+            return False
+        else:
+            self.log.info("Successfully sent sms. Please verify on carkit.")
+        return True
+
+    def sms_receive_multiple(self, pri_dut, sec_dut):
+        text_count = 10
+        self.log.info(
+            "Test sending {} sms messages to phone.".format(text_count))
+        input("Press enter to execute this testcase...")
+        for _ in range(text_count):
+            msg = [rand_ascii_str(50)]
+            if not sms_send_receive_verify(self.log, sec_dut, pri_dut, msg):
+                return False
+            else:
+                self.log.info(
+                    "Successfully sent sms. Please verify on carkit.")
+        return True
+
+    def sms_send_outgoing_texts(self, pri_dut, sec_dut):
+        self.log.info("Test send sms of different sizes.")
+        input("Press enter to execute this testcase...")
+        msg = [rand_ascii_str(50), rand_ascii_str(1), rand_ascii_str(500)]
+        if not sms_send_receive_verify(self.log, pri_dut, sec_dut, msg):
+            return False
+        else:
+            self.log.info("Successfully sent sms. Please verify on carkit.")
+        return True
+
+    def sms_during_incomming_call(self, pri_dut, sec_dut):
+        self.log.info(
+            "Test incomming call scenario to phone from unknown contact")
+        input("Press enter to execute this testcase...")
+        outgoing_num = get_phone_number(self.log, pri_dut)
+        if not initiate_call(self.log, sec_dut, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, pri_dut):
+            self.log.error("Failed to answer call.")
+            return False
+        time.sleep(self.short_timeout)
+        msg = [rand_ascii_str(10)]
+        if not sms_send_receive_verify(self.log, sec_dut, pri_dut, msg):
+            return False
+        else:
+            self.log.info("Successfully sent sms. Please verify on carkit.")
+        input("Press enter to hangup call...")
+        if not hangup_call(self.log, sec_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def multi_incomming_call(self, pri_dut, sec_dut, ter_dut):
+        self.log.info("Test 2 incomming calls scenario to phone.")
+        input("Press enter to execute this testcase...")
+        if not self.setup_multi_call(sec_dut, ter_dut, pri_dut):
+            return False
+        input("Press enter to hangup call 1...")
+        if not hangup_call(self.log, sec_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        input("Press enter to hangup call 2...")
+        if not hangup_call(self.log, ter_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def multi_call_audio_routing(self, pri_dut, sec_dut, ter_dut):
+        self.log.info(
+            "Test 2 incomming calls scenario to phone, then test audio routing."
+        )
+        input("Press enter to execute this testcase...")
+        if not self.setup_multi_call(sec_dut, ter_dut, pri_dut):
+            return False
+        input("Press enter to switch to earpiece...")
+        self.log.info("Switching to earpiece.")
+        set_audio_route(self.log, pri_dut, AUDIO_ROUTE_EARPIECE)
+        time.sleep(self.short_timeout)
+        if get_audio_route(self.log, pri_dut) != AUDIO_ROUTE_EARPIECE:
+            self.log.error(
+                "Audio Route not set to {}".format(AUDIO_ROUTE_EARPIECE))
+            return False
+        input("Press enter to switch to Bluetooth...")
+        self.log.info("Switching to Bluetooth...")
+        set_audio_route(self.log, pri_dut, AUDIO_ROUTE_BLUETOOTH)
+        time.sleep(self.short_timeout)
+        if get_audio_route(self.log, pri_dut) != AUDIO_ROUTE_BLUETOOTH:
+            self.log.error(
+                "Audio Route not set to {}".format(AUDIO_ROUTE_BLUETOOTH))
+            return False
+        input("Press enter to hangup call 1...")
+        if not hangup_call(self.log, sec_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        input("Press enter to hangup call 2...")
+        if not hangup_call(self.log, ter_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def multi_call_swap_multiple_times(self, pri_dut, sec_dut, ter_dut):
+        self.log.info(
+            "Test 2 incomming calls scenario to phone, then test audio routing."
+        )
+        input("Press enter to execute this testcase...")
+        if not self.setup_multi_call(sec_dut, ter_dut, pri_dut):
+            return False
+        input("Press enter to swap active calls...")
+        calls = pri_dut.droid.telecomCallGetCallIds()
+        if not swap_calls(self.log, [pri_dut, sec_dut, ter_dut], calls[0],
+                          calls[1], 5):
+            return False
+        input("Press enter to hangup call 1...")
+        if not hangup_call(self.log, sec_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        input("Press enter to hangup call 2...")
+        if not hangup_call(self.log, ter_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def multi_call_join_conference_call(self, pri_dut, sec_dut, ter_dut):
+        self.log.info(
+            "Test 2 incomming calls scenario to phone then join the calls.")
+        input("Press enter to execute this testcase...")
+        if not self.setup_multi_call(sec_dut, ter_dut, pri_dut):
+            return False
+        input("Press enter to join active calls...")
+        calls = pri_dut.droid.telecomCallGetCallIds()
+        pri_dut.droid.telecomCallJoinCallsInConf(calls[0], calls[1])
+        time.sleep(WAIT_TIME_IN_CALL)
+        if num_active_calls(self.log, pri_dut) != 4:
+            self.log.error("Total number of call ids in {} is not 4.".format(
+                pri_dut.serial))
+            return False
+        input("Press enter to hangup call 1...")
+        if not hangup_call(self.log, sec_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        input("Press enter to hangup call 2...")
+        if not hangup_call(self.log, ter_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def multi_call_join_conference_call_hangup_conf_call(
+            self, pri_dut, sec_dut, ter_dut):
+        self.log.info(
+            "Test 2 incomming calls scenario to phone then join the calls, then terminate the call from the primary dut."
+        )
+        input("Press enter to execute this testcase...")
+        if not self.setup_multi_call(sec_dut, ter_dut, pri_dut):
+            return False
+        input("Press enter to join active calls...")
+        calls = pri_dut.droid.telecomCallGetCallIds()
+        pri_dut.droid.telecomCallJoinCallsInConf(calls[0], calls[1])
+        time.sleep(WAIT_TIME_IN_CALL)
+        if num_active_calls(self.log, pri_dut) != 4:
+            self.log.error("Total number of call ids in {} is not 4.".format(
+                pri_dut.serial))
+            return False
+        input("Press enter to hangup conf call...")
+        if not hangup_call(self.log, pri_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def outgoing_multi_call_join_conference_call(self, pri_dut, sec_dut,
+                                                 ter_dut):
+        self.log.info(
+            "Test 2 outgoing calls scenario from phone then join the calls.")
+        input("Press enter to execute this testcase...")
+        outgoing_num = get_phone_number(self.log, sec_dut)
+        if not initiate_call(self.log, pri_dut, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, sec_dut):
+            self.log.error("Failed to answer call.")
+            return False
+        time.sleep(self.short_timeout)
+        outgoing_num = get_phone_number(self.log, ter_dut)
+        if not initiate_call(self.log, pri_dut, outgoing_num):
+            self.log.error("Failed to initiate call")
+            return False
+        if not wait_and_answer_call(self.log, ter_dut):
+            self.log.error("Failed to answer call.")
+            return False
+        input("Press enter to join active calls...")
+        calls = pri_dut.droid.telecomCallGetCallIds()
+        pri_dut.droid.telecomCallJoinCallsInConf(calls[0], calls[1])
+        time.sleep(WAIT_TIME_IN_CALL)
+        if num_active_calls(self.log, pri_dut) != 4:
+            self.log.error("Total number of call ids in {} is not 4.".format(
+                pri_dut.serial))
+            return False
+        input("Press enter to hangup call 1...")
+        if not hangup_call(self.log, sec_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        input("Press enter to hangup call 2...")
+        if not hangup_call(self.log, ter_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def multi_call_join_conference_call_audio_routes(self, pri_dut, sec_dut,
+                                                     ter_dut):
+        self.log.info(
+            "Test 2 incomming calls scenario to phone then join the calls, then test different audio routes."
+        )
+        input("Press enter to execute this testcase...")
+        if not self.setup_multi_call(sec_dut, ter_dut, pri_dut):
+            return False
+        input("Press enter to join active calls...")
+        calls = pri_dut.droid.telecomCallGetCallIds()
+        pri_dut.droid.telecomCallJoinCallsInConf(calls[0], calls[1])
+        time.sleep(WAIT_TIME_IN_CALL)
+        if num_active_calls(self.log, pri_dut) != 4:
+            self.log.error("Total number of call ids in {} is not 4.".format(
+                pri_dut.serial))
+            return False
+        input("Press enter to switch to phone speaker...")
+        self.log.info("Switching to earpiece.")
+        set_audio_route(self.log, pri_dut, AUDIO_ROUTE_EARPIECE)
+        time.sleep(self.short_timeout)
+        if get_audio_route(self.log, pri_dut) != AUDIO_ROUTE_EARPIECE:
+            self.log.error(
+                "Audio Route not set to {}".format(AUDIO_ROUTE_EARPIECE))
+            return False
+        input("Press enter to switch to Bluetooth...")
+        self.log.info("Switching to Bluetooth...")
+        set_audio_route(self.log, pri_dut, AUDIO_ROUTE_BLUETOOTH)
+        time.sleep(self.short_timeout)
+        if get_audio_route(self.log, pri_dut) != AUDIO_ROUTE_BLUETOOTH:
+            self.log.error(
+                "Audio Route not set to {}".format(AUDIO_ROUTE_BLUETOOTH))
+            return False
+        input("Press enter to hangup conf call...")
+        if not hangup_call(self.log, pri_dut):
+            self.log.error("Failed to hangup call")
+            return False
+        return True
+
+    def avrcp_play_pause(self, pri_dut):
+        play_pause_count = 5
+        self.log.info(
+            "Test AVRCP play/pause {} times.".format(play_pause_count))
+        pri_dut.adb.shell(KEYCODE_EVENT_PREVIOUS)
+        input(
+            "Press enter to execute this testcase when music is in the play state..."
+        )
+        for i in range(play_pause_count):
+            input("Execute iteration {}?".format(i + 1))
+            pri_dut.adb.shell(KEYCODE_EVENT_PLAY_PAUSE)
+        self.log.info("Test should end in a paused state.")
+        return True
+
+    def avrcp_next_previous_song(self, pri_dut):
+        self.log.info("Test AVRCP go to the next song then the previous song.")
+        pri_dut.adb.shell(KEYCODE_EVENT_PREVIOUS)
+        input(
+            "Press enter to execute this testcase when music is in the play state..."
+        )
+        self.log.info("Hitting Next input event...")
+        pri_dut.adb.shell(KEYCODE_EVENT_NEXT)
+        input("Press enter to go to the previous song")
+        pri_dut.adb.shell(KEYCODE_EVENT_PREVIOUS)
+        pri_dut.adb.shell(KEYCODE_EVENT_PREVIOUS)
+        self.log.info("Test should end on original song.")
+        return True
+
+    def avrcp_next_previous(self, pri_dut):
+        self.log.info(
+            "Test AVRCP go to the next song then the press previous after a few seconds."
+        )
+        pri_dut.adb.shell(KEYCODE_EVENT_PREVIOUS)
+        input(
+            "Press enter to execute this testcase when music is in the play state..."
+        )
+        self.log.info("Hitting Next input event...")
+        pri_dut.adb.shell(KEYCODE_EVENT_NEXT)
+        time.sleep(5)
+        self.log.info("Hitting Previous input event...")
+        pri_dut.adb.shell(KEYCODE_EVENT_PREVIOUS)
+        self.log.info("Test should end on \"next\" song.")
+        return True
+
+    def avrcp_next_repetative(self, pri_dut):
+        iterations = 10
+        self.log.info("Test AVRCP go to the next {} times".format(iterations))
+        pri_dut.adb.shell(KEYCODE_EVENT_PREVIOUS)
+        input(
+            "Press enter to execute this testcase when music is in the play state..."
+        )
+        for i in range(iterations):
+            self.log.info(
+                "Hitting Next input event, iteration {}...".format(i + 1))
+            pri_dut.adb.shell(KEYCODE_EVENT_NEXT)
+            # Allow time for the carkit to update.
+            time.sleep(1)
+        return True
+
+    def _cycle_aboslute_volume_control_helper(self, volume_step,
+                                              android_volume_steps, pri_dut):
+        begin_time = get_current_epoch_time()
+        pri_dut.droid.setMediaVolume(volume_step)
+        percentage_to_set = int((volume_step / android_volume_steps) * 100)
+        self.log.info("Setting phone volume to {}%".format(percentage_to_set))
+        volume_info_logcat = pri_dut.search_logcat(
+            logcat_strings['media_playback_vol_changed'], begin_time)
+        if len(volume_info_logcat) > 1:
+            self.log.info("Instant response detected.")
+            carkit_response = volume_info_logcat[-1]['log_message'].split(',')
+            for item in carkit_response:
+                if " volume=" in item:
+                    carkit_vol_response = int((
+                        int(item.split("=")[-1]) / android_volume_steps) * 100)
+                    self.log.info(
+                        "Carkit set volume to {}%".format(carkit_vol_response))
+        result = input(
+            "Did volume change reflect properly on carkit and phone? (Y/n) "
+        ).lower()
+
+    def cycle_absolute_volume_control(self, pri_dut):
+        result = input(
+            "Does carkit support Absolute Volume Control? (Y/n) ").lower()
+        if result == "n":
+            return True
+        android_volume_steps = 25
+        for i in range(android_volume_steps):
+            self._cycle_aboslute_volume_control_helper(i, android_volume_steps,
+                                                       pri_dut)
+        for i in reversed(range(android_volume_steps)):
+            self._cycle_aboslute_volume_control_helper(i, android_volume_steps,
+                                                       pri_dut)
+        return True
+
+    def cycle_battery_level(self, pri_dut):
+        for i in range(11):
+            level = i * 10
+            pri_dut.shell.set_battery_level(level)
+            question = "Phone battery level {}. Has the carkit indicator " \
+                "changed? (Y/n) "
+            result = input(question.format(level)).lower()
+
+    def test_voice_recognition_from_phone(self, pri_dut):
+        result = input(
+            "Does carkit support voice recognition (BVRA)? (Y/n) ").lower()
+        if result == "n":
+            return True
+        input("Press enter to start voice recognition from phone.")
+        self.pri_dut.droid.bluetoothHspStartVoiceRecognition(
+            self.target_mac_address)
+        input("Press enter to stop voice recognition from phone.")
+        self.pri_dut.droid.bluetoothHspStopVoiceRecognition(
+            self.target_mac_address)
+
+    def test_audio_and_voice_recognition_from_phone(self, pri_dut):
+        result = input(
+            "Does carkit support voice recognition (BVRA)? (Y/n) ").lower()
+        if result == "n":
+            return True
+        # Start playing music here
+        input("Press enter to start voice recognition from phone.")
+        self.pri_dut.droid.bluetoothHspStartVoiceRecognition(
+            self.target_mac_address)
+        input("Press enter to stop voice recognition from phone.")
+        self.pri_dut.droid.bluetoothHspStopVoiceRecognition(
+            self.target_mac_address)
+        time.sleep(2)
+        result = input("Did carkit continue music playback after? (Y/n) ")
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_coc_test_utils.py b/acts_tests/acts_contrib/test_utils/bt/bt_coc_test_utils.py
new file mode 100644
index 0000000..14d3e2c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_coc_test_utils.py
@@ -0,0 +1,299 @@
+#!/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 logging
+import time
+from acts import utils
+
+from acts_contrib.test_utils.bt.bt_constants import bt_default_timeout
+from acts_contrib.test_utils.bt.bt_constants import default_bluetooth_socket_timeout_ms
+from acts_contrib.test_utils.bt.bt_constants import default_le_connection_interval_ms
+from acts_contrib.test_utils.bt.bt_constants import default_le_data_length
+from acts_contrib.test_utils.bt.bt_constants import gatt_phy
+from acts_contrib.test_utils.bt.bt_constants import gatt_transport
+from acts_contrib.test_utils.bt.bt_constants import l2cap_coc_header_size
+from acts_contrib.test_utils.bt.bt_constants import le_connection_event_time_step_ms
+from acts_contrib.test_utils.bt.bt_constants import le_connection_interval_time_step_ms
+from acts_contrib.test_utils.bt.bt_constants import le_default_supervision_timeout
+from acts_contrib.test_utils.bt.bt_test_utils import get_mac_address_of_generic_advertisement
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+
+log = logging
+
+
+class BtCoCTestUtilsError(Exception):
+    pass
+
+
+def do_multi_connection_throughput(client_ad, list_server_ad,
+                                   list_client_conn_id, num_iterations,
+                                   number_buffers, buffer_size):
+    """Throughput measurements from one client to one-or-many servers.
+
+    Args:
+        client_ad: the Android device to perform the write.
+        list_server_ad: the list of Android server devices connected to this client.
+        list_client_conn_id: list of client connection IDs
+        num_iterations: the number of test repetitions.
+        number_buffers: the total number of data buffers to transmit per test.
+        buffer_size: the number of bytes per L2CAP data buffer.
+
+    Returns:
+        Throughput in terms of bytes per second, 0 if test failed.
+    """
+
+    total_num_bytes = 0
+    start_write_time = time.perf_counter()
+    client_ad.log.info(
+        "do_multi_connection_throughput: Before write. Start Time={:f}, "
+        "num_iterations={}, number_buffers={}, buffer_size={}, "
+        "number_buffers*buffer_size={}, num_servers={}".format(
+            start_write_time, num_iterations, number_buffers, buffer_size,
+            number_buffers * buffer_size, len(list_server_ad)))
+
+    if (len(list_server_ad) != len(list_client_conn_id)):
+        client_ad.log.error("do_multi_connection_throughput: invalid "
+                            "parameters. Num of list_server_ad({}) != "
+                            "list_client_conn({})".format(
+                                len(list_server_ad), len(list_client_conn_id)))
+        return 0
+
+    try:
+        for _, client_conn_id in enumerate(list_client_conn_id):
+            client_ad.log.info("do_multi_connection_throughput: "
+                               "client_conn_id={}".format(client_conn_id))
+            # Plumb the tx data queue with the first set of data buffers.
+            client_ad.droid.bluetoothConnectionThroughputSend(
+                number_buffers, buffer_size, client_conn_id)
+    except Exception as err:
+        client_ad.log.error("Failed to write data: {}".format(err))
+        return 0
+
+    # Each Loop iteration will write and read one set of buffers.
+    for _ in range(0, (num_iterations - 1)):
+        try:
+            for _, client_conn_id in enumerate(list_client_conn_id):
+                client_ad.droid.bluetoothConnectionThroughputSend(
+                    number_buffers, buffer_size, client_conn_id)
+        except Exception as err:
+            client_ad.log.error("Failed to write data: {}".format(err))
+            return 0
+
+        for _, server_ad in enumerate(list_server_ad):
+            try:
+                server_ad.droid.bluetoothConnectionThroughputRead(
+                    number_buffers, buffer_size)
+                total_num_bytes += number_buffers * buffer_size
+            except Exception as err:
+                server_ad.log.error("Failed to read data: {}".format(err))
+                return 0
+
+    for _, server_ad in enumerate(list_server_ad):
+        try:
+            server_ad.droid.bluetoothConnectionThroughputRead(
+                number_buffers, buffer_size)
+            total_num_bytes += number_buffers * buffer_size
+        except Exception as err:
+            server_ad.log.error("Failed to read data: {}".format(err))
+            return 0
+
+    end_read_time = time.perf_counter()
+
+    test_time = (end_read_time - start_write_time)
+    if (test_time == 0):
+        client_ad.log.error("Buffer transmits cannot take zero time")
+        return 0
+    data_rate = (1.000 * total_num_bytes) / test_time
+    log.info(
+        "Calculated using total write and read times: total_num_bytes={}, "
+        "test_time={}, data rate={:08.0f} bytes/sec, {:08.0f} bits/sec".format(
+            total_num_bytes, test_time, data_rate, (data_rate * 8)))
+    return data_rate
+
+
+def orchestrate_coc_connection(
+        client_ad,
+        server_ad,
+        is_ble,
+        secured_conn=False,
+        le_connection_interval=0,
+        le_tx_data_length=default_le_data_length,
+        accept_timeout_ms=default_bluetooth_socket_timeout_ms,
+        le_min_ce_len=0,
+        le_max_ce_len=0,
+        gatt_disconnection=True):
+    """Sets up the CoC connection between two Android devices.
+
+    Args:
+        client_ad: the Android device performing the connection.
+        server_ad: the Android device accepting the connection.
+        is_ble: using LE transport.
+        secured_conn: using secured connection
+        le_connection_interval: LE Connection interval. 0 means use default.
+        le_tx_data_length: LE Data Length used by BT Controller to transmit.
+        accept_timeout_ms: timeout while waiting for incoming connection.
+        gatt_disconnection: LE GATT disconnection, default is True, False will return
+        bluetooth_gatt and gatt_callback
+    Returns:
+        True if connection was successful or false if unsuccessful,
+        client connection ID,
+        and server connection ID
+    """
+    server_ad.droid.bluetoothStartPairingHelper()
+    client_ad.droid.bluetoothStartPairingHelper()
+
+    adv_callback = None
+    mac_address = None
+    if is_ble:
+        try:
+            # This will start advertising and scanning. Will fail if it could
+            # not find the advertisements from server_ad
+            client_ad.log.info(
+                "Orchestrate_coc_connection: Start BLE advertisement and"
+                "scanning. Secured Connection={}".format(secured_conn))
+            mac_address, adv_callback, scan_callback = (
+                get_mac_address_of_generic_advertisement(client_ad, server_ad))
+        except BtTestUtilsError as err:
+            raise BtCoCTestUtilsError(
+                "Orchestrate_coc_connection: Error in getting mac address: {}".
+                format(err))
+    else:
+        mac_address = server_ad.droid.bluetoothGetLocalAddress()
+        adv_callback = None
+
+    # Adjust the Connection Interval (if necessary)
+    bluetooth_gatt_1 = -1
+    gatt_callback_1 = -1
+    gatt_connected = False
+    if is_ble and (le_connection_interval != 0 or le_min_ce_len != 0 or le_max_ce_len != 0):
+        client_ad.log.info(
+            "Adjusting connection interval={}, le_min_ce_len={}, le_max_ce_len={}"
+            .format(le_connection_interval, le_min_ce_len, le_max_ce_len))
+        try:
+            bluetooth_gatt_1, gatt_callback_1 = setup_gatt_connection(
+                client_ad,
+                mac_address,
+                False,
+                transport=gatt_transport['le'],
+                opportunistic=False)
+            client_ad.droid.bleStopBleScan(scan_callback)
+        except GattTestUtilsError as err:
+            client_ad.log.error(err)
+            if (adv_callback != None):
+                server_ad.droid.bleStopBleAdvertising(adv_callback)
+            return False, None, None
+        client_ad.log.info("setup_gatt_connection returns success")
+        if (le_connection_interval != 0):
+            minInterval = le_connection_interval / le_connection_interval_time_step_ms
+            maxInterval = le_connection_interval / le_connection_interval_time_step_ms
+        else:
+            minInterval = default_le_connection_interval_ms / le_connection_interval_time_step_ms
+            maxInterval = default_le_connection_interval_ms / le_connection_interval_time_step_ms
+        if (le_min_ce_len != 0):
+            le_min_ce_len = le_min_ce_len / le_connection_event_time_step_ms
+        if (le_max_ce_len != 0):
+            le_max_ce_len = le_max_ce_len / le_connection_event_time_step_ms
+
+        return_status = client_ad.droid.gattClientRequestLeConnectionParameters(
+            bluetooth_gatt_1, minInterval, maxInterval, 0,
+            le_default_supervision_timeout, le_min_ce_len, le_max_ce_len)
+        if not return_status:
+            client_ad.log.error(
+                "gattClientRequestLeConnectionParameters returns failure")
+            if (adv_callback != None):
+                server_ad.droid.bleStopBleAdvertising(adv_callback)
+            return False, None, None
+        client_ad.log.info(
+            "gattClientRequestLeConnectionParameters returns success. Interval={}"
+            .format(minInterval))
+        gatt_connected = True
+        # For now, we will only test with 1 Mbit Phy.
+        # TODO: Add explicit tests with 2 MBit Phy.
+        client_ad.droid.gattClientSetPreferredPhy(
+            bluetooth_gatt_1, gatt_phy['1m'], gatt_phy['1m'], 0)
+
+    server_ad.droid.bluetoothSocketConnBeginAcceptThreadPsm(
+        accept_timeout_ms, is_ble, secured_conn)
+
+    psm_value = server_ad.droid.bluetoothSocketConnGetPsm()
+    client_ad.log.info("Assigned PSM value={}".format(psm_value))
+
+    client_ad.droid.bluetoothSocketConnBeginConnectThreadPsm(
+        mac_address, is_ble, psm_value, secured_conn)
+
+    if (le_tx_data_length != default_le_data_length) and is_ble:
+        client_ad.log.info("orchestrate_coc_connection: call "
+                           "bluetoothSocketRequestMaximumTxDataLength")
+        client_ad.droid.bluetoothSocketRequestMaximumTxDataLength()
+
+    end_time = time.time() + bt_default_timeout
+    test_result = False
+    while time.time() < end_time:
+        if len(server_ad.droid.bluetoothSocketConnActiveConnections()) > 0:
+            server_ad.log.info("CoC Server Connection Active")
+            if len(client_ad.droid.bluetoothSocketConnActiveConnections()) > 0:
+                client_ad.log.info("CoC Client Connection Active")
+                test_result = True
+                break
+        time.sleep(1)
+
+    if (adv_callback != None):
+        server_ad.droid.bleStopBleAdvertising(adv_callback)
+
+    if not test_result:
+        client_ad.log.error("Failed to establish an CoC connection")
+        return False, None, None
+
+    if len(client_ad.droid.bluetoothSocketConnActiveConnections()) > 0:
+        server_ad.log.info(
+            "CoC client_ad Connection Active, num=%d",
+            len(client_ad.droid.bluetoothSocketConnActiveConnections()))
+    else:
+        server_ad.log.info("Error CoC client_ad Connection Inactive")
+        client_ad.log.info("Error CoC client_ad Connection Inactive")
+
+    # Wait for the client to be ready
+    client_conn_id = None
+    while (client_conn_id == None):
+        client_conn_id = client_ad.droid.bluetoothGetLastConnId()
+        if (client_conn_id != None):
+            break
+        time.sleep(1)
+
+    # Wait for the server to be ready
+    server_conn_id = None
+    while (server_conn_id == None):
+        server_conn_id = server_ad.droid.bluetoothGetLastConnId()
+        if (server_conn_id != None):
+            break
+        time.sleep(1)
+
+    client_ad.log.info(
+        "orchestrate_coc_connection: client conn id={}, server conn id={}".
+        format(client_conn_id, server_conn_id))
+
+    if gatt_disconnection:
+
+        if gatt_connected:
+            disconnect_gatt_connection(client_ad, bluetooth_gatt_1,
+                                       gatt_callback_1)
+            client_ad.droid.gattClientClose(bluetooth_gatt_1)
+
+        return True, client_conn_id, server_conn_id
+
+    else:
+        return True, client_conn_id, server_conn_id, bluetooth_gatt_1, gatt_callback_1
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_constants.py b/acts_tests/acts_contrib/test_utils/bt/bt_constants.py
new file mode 100644
index 0000000..f7bc93f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_constants.py
@@ -0,0 +1,791 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+### Generic Constants Begin ###
+
+bt_default_timeout = 15
+default_rfcomm_timeout_ms = 10000
+default_bluetooth_socket_timeout_ms = 10000
+pan_connect_timeout = 5
+bt_discovery_timeout = 3
+small_timeout = 0.0001
+
+# Time delay (in seconds) at the end of each LE CoC Test to give sufficient time
+# for the ACL LE link to be disconnected. The ACL link stays connected after
+# L2CAP disconnects.  An example of the timeout is L2CAP_LINK_INACTIVITY_TOUT.
+# This delay must be greater than the maximum of these timeouts.
+# TODO: Investigate the use of broadcast intent
+# BluetoothDevice.ACTION_ACL_DISCONNECTED to replace this delay method.
+l2cap_max_inactivity_delay_after_disconnect = 5
+
+# LE specifications related constants
+le_connection_interval_time_step_ms = 1.25
+le_default_supervision_timeout = 2000
+default_le_data_length = 23
+default_le_connection_interval_ms = 30
+le_connection_event_time_step_ms = 0.625
+
+# Headers of LE L2CAP Connection-oriented Channels. See section 3.4, Vol
+# 3, Part A, Version 5.0.
+l2cap_header_size = 4
+l2cap_coc_sdu_length_field_size = 2
+l2cap_coc_header_size = l2cap_header_size + l2cap_coc_sdu_length_field_size
+
+java_integer = {"min": -2147483648, "max": 2147483647}
+
+btsnoop_log_path_on_device = "/data/misc/bluetooth/logs/btsnoop_hci.log"
+btsnoop_last_log_path_on_device = \
+    "/data/misc/bluetooth/logs/btsnoop_hci.log.last"
+pairing_variant_passkey_confirmation = 2
+
+# Callback strings
+scan_result = "BleScan{}onScanResults"
+scan_failed = "BleScan{}onScanFailed"
+batch_scan_result = "BleScan{}onBatchScanResult"
+adv_fail = "BleAdvertise{}onFailure"
+adv_succ = "BleAdvertise{}onSuccess"
+bluetooth_off = "BluetoothStateChangedOff"
+bluetooth_on = "BluetoothStateChangedOn"
+mtu_changed = "GattConnect{}onMtuChanged"
+advertising_set_started = "AdvertisingSet{}onAdvertisingSetStarted"
+advertising_set_stopped = "AdvertisingSet{}onAdvertisingSetStopped"
+advertising_set_on_own_address_read = "AdvertisingSet{}onOwnAddressRead"
+advertising_set_enabled = "AdvertisingSet{}onAdvertisingEnabled"
+advertising_set_data_set = "AdvertisingSet{}onAdvertisingDataSet"
+advertising_set_scan_response_set = "AdvertisingSet{}onScanResponseDataSet"
+advertising_set_parameters_update = \
+    "AdvertisingSet{}onAdvertisingParametersUpdated"
+advertising_set_periodic_parameters_updated = \
+    "AdvertisingSet{}onPeriodicAdvertisingParametersUpdated"
+advertising_set_periodic_data_set = \
+    "AdvertisingSet{}onPeriodicAdvertisingDataSet"
+advertising_set_periodic_enable = "AdvertisingSet{}onPeriodicAdvertisingEnable"
+bluetooth_profile_connection_state_changed = \
+    "BluetoothProfileConnectionStateChanged"
+bluetooth_le_on = "BleStateChangedOn"
+bluetooth_le_off = "BleStateChangedOff"
+bluetooth_a2dp_codec_config_changed = "BluetoothA2dpCodecConfigChanged"
+# End Callback Strings
+
+batch_scan_not_supported_list = [
+    "Nexus 4",
+    "Nexus 5",
+    "Nexus 7",
+]
+
+### Generic Constants End ###
+
+### Bluetooth Constants Begin ###
+
+# rfcomm test uuids
+rfcomm_secure_uuid = "fa87c0d0-afac-11de-8a39-0800200c9a66"
+rfcomm_insecure_uuid = "8ce255c0-200a-11e0-ac64-0800200c9a66"
+
+# bluetooth socket connection test uuid
+bluetooth_socket_conn_test_uuid = "12345678-1234-5678-9abc-123456789abc"
+
+# Bluetooth Adapter Scan Mode Types
+bt_scan_mode_types = {
+    "state_off": -1,
+    "none": 0,
+    "connectable": 1,
+    "connectable_discoverable": 3
+}
+
+# Bluetooth Adapter State Constants
+bt_adapter_states = {
+    "off": 10,
+    "turning_on": 11,
+    "on": 12,
+    "turning_off": 13,
+    "ble_turning_on": 14,
+    "ble_on": 15,
+    "ble_turning_off": 16
+}
+
+# Should be kept in sync with BluetoothProfile.java
+bt_profile_constants = {
+    "headset": 1,
+    "a2dp": 2,
+    "health": 3,
+    "input_device": 4,
+    "pan": 5,
+    "pbap_server": 6,
+    "gatt": 7,
+    "gatt_server": 8,
+    "map": 9,
+    "sap": 10,
+    "a2dp_sink": 11,
+    "avrcp_controller": 12,
+    "headset_client": 16,
+    "pbap_client": 17,
+    "map_mce": 18
+}
+
+# Bluetooth RFCOMM UUIDs as defined by the SIG
+bt_rfcomm_uuids = {
+    "default_uuid": "457807c0-4897-11df-9879-0800200c9a66",
+    "base_uuid": "00000000-0000-1000-8000-00805F9B34FB",
+    "sdp": "00000001-0000-1000-8000-00805F9B34FB",
+    "udp": "00000002-0000-1000-8000-00805F9B34FB",
+    "rfcomm": "00000003-0000-1000-8000-00805F9B34FB",
+    "tcp": "00000004-0000-1000-8000-00805F9B34FB",
+    "tcs_bin": "00000005-0000-1000-8000-00805F9B34FB",
+    "tcs_at": "00000006-0000-1000-8000-00805F9B34FB",
+    "att": "00000007-0000-1000-8000-00805F9B34FB",
+    "obex": "00000008-0000-1000-8000-00805F9B34FB",
+    "ip": "00000009-0000-1000-8000-00805F9B34FB",
+    "ftp": "0000000A-0000-1000-8000-00805F9B34FB",
+    "http": "0000000C-0000-1000-8000-00805F9B34FB",
+    "wsp": "0000000E-0000-1000-8000-00805F9B34FB",
+    "bnep": "0000000F-0000-1000-8000-00805F9B34FB",
+    "upnp": "00000010-0000-1000-8000-00805F9B34FB",
+    "hidp": "00000011-0000-1000-8000-00805F9B34FB",
+    "hardcopy_control_channel": "00000012-0000-1000-8000-00805F9B34FB",
+    "hardcopy_data_channel": "00000014-0000-1000-8000-00805F9B34FB",
+    "hardcopy_notification": "00000016-0000-1000-8000-00805F9B34FB",
+    "avctp": "00000017-0000-1000-8000-00805F9B34FB",
+    "avdtp": "00000019-0000-1000-8000-00805F9B34FB",
+    "cmtp": "0000001B-0000-1000-8000-00805F9B34FB",
+    "mcap_control_channel": "0000001E-0000-1000-8000-00805F9B34FB",
+    "mcap_data_channel": "0000001F-0000-1000-8000-00805F9B34FB",
+    "l2cap": "00000100-0000-1000-8000-00805F9B34FB"
+}
+
+# Should be kept in sync with BluetoothProfile#STATE_* constants.
+bt_profile_states = {
+    "disconnected": 0,
+    "connecting": 1,
+    "connected": 2,
+    "disconnecting": 3
+}
+
+# Access Levels from BluetoothDevice.
+bt_access_levels = {"access_allowed": 1, "access_denied": 2}
+
+# Priority levels as defined in BluetoothProfile.java.
+bt_priority_levels = {
+    "auto_connect": 1000,
+    "on": 100,
+    "off": 0,
+    "undefined": -1
+}
+
+# A2DP codec configuration constants as defined in
+# frameworks/base/core/java/android/bluetooth/BluetoothCodecConfig.java
+codec_types = {
+    'SBC': 0,
+    'AAC': 1,
+    'APTX': 2,
+    'APTX-HD': 3,
+    'LDAC': 4,
+    'MAX': 5,
+    'INVALID': 1000000
+}
+
+codec_priorities = {'DISABLED': -1, 'DEFAULT': 0, 'HIGHEST': 1000000}
+
+sample_rates = {
+    'NONE': 0,
+    '44100': 0x1 << 0,
+    '48000': 0x1 << 1,
+    '88200': 0x1 << 2,
+    '96000': 0x1 << 3,
+    '176400': 0x1 << 4,
+    '192000': 0x1 << 5
+}
+
+bits_per_samples = {'NONE': 0, '16': 0x1 << 0, '24': 0x1 << 1, '32': 0x1 << 2}
+
+channel_modes = {'NONE': 0, 'MONO': 0x1 << 0, 'STEREO': 0x1 << 1}
+
+# Bluetooth HID constants.
+hid_connection_timeout = 5
+
+# Bluetooth HID EventFacade constants.
+hid_on_set_report_event = "onSetReport"
+hid_on_get_report_event = "onGetReport"
+hid_on_set_protocol_event = "onSetProtocol"
+hid_on_intr_data_event = "onInterruptData"
+hid_on_virtual_cable_unplug_event = "onVirtualCableUnplug"
+hid_id_keyboard = 1
+hid_id_mouse = 2
+hid_default_event_timeout = 15
+hid_default_set_report_payload = "Haha"
+
+### Bluetooth Constants End ###
+
+### Bluetooth Low Energy Constants Begin ###
+
+# Bluetooth Low Energy scan callback types
+ble_scan_settings_callback_types = {
+    "all_matches": 1,
+    "first_match": 2,
+    "match_lost": 4,
+    "found_and_lost": 6
+}
+
+# Bluetooth Low Energy scan settings match mode
+ble_scan_settings_match_modes = {"aggresive": 1, "sticky": 2}
+
+# Bluetooth Low Energy scan settings match nums
+ble_scan_settings_match_nums = {"one": 1, "few": 2, "max": 3}
+
+# Bluetooth Low Energy scan settings result types
+ble_scan_settings_result_types = {"full": 0, "abbreviated": 1}
+
+# Bluetooth Low Energy scan settings mode
+ble_scan_settings_modes = {
+    "opportunistic": -1,
+    "low_power": 0,
+    "balanced": 1,
+    "low_latency": 2
+}
+
+# Bluetooth Low Energy scan settings report delay millis
+ble_scan_settings_report_delay_milli_seconds = {
+    "min": 0,
+    "max": 9223372036854775807
+}
+
+# Bluetooth Low Energy scan settings phy
+ble_scan_settings_phys = {"1m": 1, "coded": 3, "all_supported": 255}
+
+# Bluetooth Low Energy advertise settings types
+ble_advertise_settings_types = {"non_connectable": 0, "connectable": 1}
+
+# Bluetooth Low Energy advertise settings modes
+ble_advertise_settings_modes = {
+    "low_power": 0,
+    "balanced": 1,
+    "low_latency": 2
+}
+
+# Bluetooth Low Energy advertise settings tx power
+ble_advertise_settings_tx_powers = {
+    "ultra_low": 0,
+    "low": 1,
+    "medium": 2,
+    "high": 3
+}
+
+# Bluetooth Low Energy service uuids for specific devices
+ble_uuids = {
+    "p_service": "0000feef-0000-1000-8000-00805f9b34fb",
+    "hr_service": "0000180d-0000-1000-8000-00805f9b34fb"
+}
+
+# Bluetooth Low Energy advertising error codes
+ble_advertise_error_code = {
+    "data_too_large": 1,
+    "too_many_advertisers": 2,
+    "advertisement_already_started": 3,
+    "bluetooth_internal_failure": 4,
+    "feature_not_supported": 5
+}
+
+### Bluetooth Low Energy Constants End ###
+
+### Bluetooth GATT Constants Begin ###
+
+# Gatt Callback error messages
+gatt_cb_err = {
+    "char_write_req_err":
+    "Characteristic Write Request event not found. Expected {}",
+    "char_write_err": "Characteristic Write event not found. Expected {}",
+    "desc_write_req_err":
+    "Descriptor Write Request event not found. Expected {}",
+    "desc_write_err": "Descriptor Write event not found. Expected {}",
+    "char_read_err": "Characteristic Read event not found. Expected {}",
+    "char_read_req_err": "Characteristic Read Request not found. Expected {}",
+    "desc_read_err": "Descriptor Read event not found. Expected {}",
+    "desc_read_req_err":
+    "Descriptor Read Request event not found. Expected {}",
+    "rd_remote_rssi_err": "Read Remote RSSI event not found. Expected {}",
+    "gatt_serv_disc_err":
+    "GATT Services Discovered event not found. Expected {}",
+    "serv_added_err": "Service Added event not found. Expected {}",
+    "mtu_changed_err": "MTU Changed event not found. Expected {}",
+    "mtu_serv_changed_err": "MTU Server Changed event not found. Expected {}",
+    "gatt_conn_changed_err":
+    "GATT Connection Changed event not found. Expected {}",
+    "char_change_err":
+    "GATT Characteristic Changed event not fond. Expected {}",
+    "phy_read_err": "Phy Read event not fond. Expected {}",
+    "phy_update_err": "Phy Update event not fond. Expected {}",
+    "exec_write_err": "GATT Execute Write event not found. Expected {}"
+}
+
+# GATT callback strings as defined in GattClientFacade.java and
+# GattServerFacade.java implemented callbacks.
+gatt_cb_strings = {
+    "char_write_req": "GattServer{}onCharacteristicWriteRequest",
+    "exec_write": "GattServer{}onExecuteWrite",
+    "char_write": "GattConnect{}onCharacteristicWrite",
+    "desc_write_req": "GattServer{}onDescriptorWriteRequest",
+    "desc_write": "GattConnect{}onDescriptorWrite",
+    "char_read": "GattConnect{}onCharacteristicRead",
+    "char_read_req": "GattServer{}onCharacteristicReadRequest",
+    "desc_read": "GattConnect{}onDescriptorRead",
+    "desc_read_req": "GattServer{}onDescriptorReadRequest",
+    "rd_remote_rssi": "GattConnect{}onReadRemoteRssi",
+    "gatt_serv_disc": "GattConnect{}onServicesDiscovered",
+    "serv_added": "GattServer{}onServiceAdded",
+    "mtu_changed": "GattConnect{}onMtuChanged",
+    "mtu_serv_changed": "GattServer{}onMtuChanged",
+    "gatt_conn_change": "GattConnect{}onConnectionStateChange",
+    "char_change": "GattConnect{}onCharacteristicChanged",
+    "phy_read": "GattConnect{}onPhyRead",
+    "phy_update": "GattConnect{}onPhyUpdate",
+    "serv_phy_read": "GattServer{}onPhyRead",
+    "serv_phy_update": "GattServer{}onPhyUpdate",
+}
+
+# GATT event dictionary of expected callbacks and errors.
+gatt_event = {
+    "char_write_req": {
+        "evt": gatt_cb_strings["char_write_req"],
+        "err": gatt_cb_err["char_write_req_err"]
+    },
+    "exec_write": {
+        "evt": gatt_cb_strings["exec_write"],
+        "err": gatt_cb_err["exec_write_err"]
+    },
+    "char_write": {
+        "evt": gatt_cb_strings["char_write"],
+        "err": gatt_cb_err["char_write_err"]
+    },
+    "desc_write_req": {
+        "evt": gatt_cb_strings["desc_write_req"],
+        "err": gatt_cb_err["desc_write_req_err"]
+    },
+    "desc_write": {
+        "evt": gatt_cb_strings["desc_write"],
+        "err": gatt_cb_err["desc_write_err"]
+    },
+    "char_read": {
+        "evt": gatt_cb_strings["char_read"],
+        "err": gatt_cb_err["char_read_err"]
+    },
+    "char_read_req": {
+        "evt": gatt_cb_strings["char_read_req"],
+        "err": gatt_cb_err["char_read_req_err"]
+    },
+    "desc_read": {
+        "evt": gatt_cb_strings["desc_read"],
+        "err": gatt_cb_err["desc_read_err"]
+    },
+    "desc_read_req": {
+        "evt": gatt_cb_strings["desc_read_req"],
+        "err": gatt_cb_err["desc_read_req_err"]
+    },
+    "rd_remote_rssi": {
+        "evt": gatt_cb_strings["rd_remote_rssi"],
+        "err": gatt_cb_err["rd_remote_rssi_err"]
+    },
+    "gatt_serv_disc": {
+        "evt": gatt_cb_strings["gatt_serv_disc"],
+        "err": gatt_cb_err["gatt_serv_disc_err"]
+    },
+    "serv_added": {
+        "evt": gatt_cb_strings["serv_added"],
+        "err": gatt_cb_err["serv_added_err"]
+    },
+    "mtu_changed": {
+        "evt": gatt_cb_strings["mtu_changed"],
+        "err": gatt_cb_err["mtu_changed_err"]
+    },
+    "gatt_conn_change": {
+        "evt": gatt_cb_strings["gatt_conn_change"],
+        "err": gatt_cb_err["gatt_conn_changed_err"]
+    },
+    "char_change": {
+        "evt": gatt_cb_strings["char_change"],
+        "err": gatt_cb_err["char_change_err"]
+    },
+    "phy_read": {
+        "evt": gatt_cb_strings["phy_read"],
+        "err": gatt_cb_err["phy_read_err"]
+    },
+    "phy_update": {
+        "evt": gatt_cb_strings["phy_update"],
+        "err": gatt_cb_err["phy_update_err"]
+    },
+    "serv_phy_read": {
+        "evt": gatt_cb_strings["serv_phy_read"],
+        "err": gatt_cb_err["phy_read_err"]
+    },
+    "serv_phy_update": {
+        "evt": gatt_cb_strings["serv_phy_update"],
+        "err": gatt_cb_err["phy_update_err"]
+    }
+}
+
+# Matches constants of connection states defined in BluetoothGatt.java
+gatt_connection_state = {
+    "disconnected": 0,
+    "connecting": 1,
+    "connected": 2,
+    "disconnecting": 3,
+    "closed": 4
+}
+
+# Matches constants of Bluetooth GATT Characteristic values as defined
+# in BluetoothGattCharacteristic.java
+gatt_characteristic = {
+    "property_broadcast": 0x01,
+    "property_read": 0x02,
+    "property_write_no_response": 0x04,
+    "property_write": 0x08,
+    "property_notify": 0x10,
+    "property_indicate": 0x20,
+    "property_signed_write": 0x40,
+    "property_extended_props": 0x80,
+    "permission_read": 0x01,
+    "permission_read_encrypted": 0x02,
+    "permission_read_encrypted_mitm": 0x04,
+    "permission_write": 0x10,
+    "permission_write_encrypted": 0x20,
+    "permission_write_encrypted_mitm": 0x40,
+    "permission_write_signed": 0x80,
+    "permission_write_signed_mitm": 0x100,
+    "write_type_default": 0x02,
+    "write_type_no_response": 0x01,
+    "write_type_signed": 0x04,
+}
+
+# Matches constants of Bluetooth GATT Characteristic values as defined
+# in BluetoothGattDescriptor.java
+gatt_descriptor = {
+    "enable_notification_value": [0x01, 0x00],
+    "enable_indication_value": [0x02, 0x00],
+    "disable_notification_value": [0x00, 0x00],
+    "permission_read": 0x01,
+    "permission_read_encrypted": 0x02,
+    "permission_read_encrypted_mitm": 0x04,
+    "permission_write": 0x10,
+    "permission_write_encrypted": 0x20,
+    "permission_write_encrypted_mitm": 0x40,
+    "permission_write_signed": 0x80,
+    "permission_write_signed_mitm": 0x100
+}
+
+# https://www.bluetooth.com/specifications/gatt/descriptors
+gatt_char_desc_uuids = {
+    "char_ext_props": '00002900-0000-1000-8000-00805f9b34fb',
+    "char_user_desc": '00002901-0000-1000-8000-00805f9b34fb',
+    "client_char_cfg": '00002902-0000-1000-8000-00805f9b34fb',
+    "server_char_cfg": '00002903-0000-1000-8000-00805f9b34fb',
+    "char_fmt_uuid": '00002904-0000-1000-8000-00805f9b34fb',
+    "char_agreg_fmt": '00002905-0000-1000-8000-00805f9b34fb',
+    "char_valid_range": '00002906-0000-1000-8000-00805f9b34fb',
+    "external_report_reference": '00002907-0000-1000-8000-00805f9b34fb',
+    "report_reference": '00002908-0000-1000-8000-00805f9b34fb'
+}
+
+# https://www.bluetooth.com/specifications/gatt/characteristics
+gatt_char_types = {
+    "device_name": '00002a00-0000-1000-8000-00805f9b34fb',
+    "appearance": '00002a01-0000-1000-8000-00805f9b34fb',
+    "peripheral_priv_flag": '00002a02-0000-1000-8000-00805f9b34fb',
+    "reconnection_address": '00002a03-0000-1000-8000-00805f9b34fb',
+    "peripheral_pref_conn": '00002a04-0000-1000-8000-00805f9b34fb',
+    "service_changed": '00002a05-0000-1000-8000-00805f9b34fb',
+    "system_id": '00002a23-0000-1000-8000-00805f9b34fb',
+    "model_number_string": '00002a24-0000-1000-8000-00805f9b34fb',
+    "serial_number_string": '00002a25-0000-1000-8000-00805f9b34fb',
+    "firmware_revision_string": '00002a26-0000-1000-8000-00805f9b34fb',
+    "hardware_revision_string": '00002a27-0000-1000-8000-00805f9b34fb',
+    "software_revision_string": '00002a28-0000-1000-8000-00805f9b34fb',
+    "manufacturer_name_string": '00002a29-0000-1000-8000-00805f9b34fb',
+    "pnp_id": '00002a50-0000-1000-8000-00805f9b34fb',
+}
+
+# Matches constants of Bluetooth GATT Characteristic values as defined
+# in BluetoothGattCharacteristic.java
+gatt_characteristic_value_format = {
+    "string": 0x1,
+    "byte": 0x2,
+    "sint8": 0x21,
+    "uint8": 0x11,
+    "sint16": 0x22,
+    "unit16": 0x12,
+    "sint32": 0x24,
+    "uint32": 0x14
+}
+
+# Matches constants of Bluetooth Gatt Service types as defined in
+# BluetoothGattService.java
+gatt_service_types = {"primary": 0, "secondary": 1}
+
+# Matches constants of Bluetooth Gatt Connection Priority values as defined in
+# BluetoothGatt.java
+gatt_connection_priority = {"balanced": 0, "high": 1, "low_power": 2}
+
+# Min and max MTU values
+gatt_mtu_size = {"min": 23, "max": 217}
+
+# Gatt Characteristic attribute lengths
+gatt_characteristic_attr_length = {"attr_1": 1, "attr_2": 3, "attr_3": 15}
+
+# Matches constants of Bluetooth Gatt operations status as defined in
+# BluetoothGatt.java
+gatt_status = {"success": 0, "failure": 0x101}
+
+# Matches constants of Bluetooth transport values as defined in
+# BluetoothDevice.java
+gatt_transport = {"auto": 0x00, "bredr": 0x01, "le": 0x02}
+
+# Matches constants of Bluetooth physical channeling values as defined in
+# BluetoothDevice.java
+gatt_phy = {"1m": 1, "2m": 2, "le_coded": 3}
+
+# Matches constants of Bluetooth physical channeling bitmask values as defined
+# in BluetoothDevice.java
+gatt_phy_mask = {"1m_mask": 1, "2m_mask": 2, "coded_mask": 4}
+
+# Values as defiend in the Bluetooth GATT specification
+gatt_server_responses = {
+    "GATT_SUCCESS": 0x0,
+    "GATT_FAILURE": 0x1,
+    "GATT_READ_NOT_PERMITTED": 0x2,
+    "GATT_WRITE_NOT_PERMITTED": 0x3,
+    "GATT_INVALID_PDU": 0x4,
+    "GATT_INSUFFICIENT_AUTHENTICATION": 0x5,
+    "GATT_REQUEST_NOT_SUPPORTED": 0x6,
+    "GATT_INVALID_OFFSET": 0x7,
+    "GATT_INSUFFICIENT_AUTHORIZATION": 0x8,
+    "GATT_INVALID_ATTRIBUTE_LENGTH": 0xd,
+    "GATT_INSUFFICIENT_ENCRYPTION": 0xf,
+    "GATT_CONNECTION_CONGESTED": 0x8f,
+    "GATT_13_ERR": 0x13,
+    "GATT_12_ERR": 0x12,
+    "GATT_0C_ERR": 0x0C,
+    "GATT_16": 0x16
+}
+
+### Bluetooth GATT Constants End ###
+
+### Chameleon Constants Begin ###
+
+# Chameleon audio bits per sample.
+audio_bits_per_sample_16 = 16
+audio_bits_per_sample_24 = 24
+audio_bits_per_sample_32 = 32
+
+# Chameleon audio sample rates.
+audio_sample_rate_44100 = 44100
+audio_sample_rate_48000 = 48000
+audio_sample_rate_88200 = 88200
+audio_sample_rate_96000 = 96000
+
+# Chameleon audio channel modes.
+audio_channel_mode_mono = 1
+audio_channel_mode_stereo = 2
+audio_channel_mode_8 = 8
+
+# Chameleon time delays.
+delay_after_binding_seconds = 0.5
+delay_before_record_seconds = 0.5
+silence_wait_seconds = 5
+
+# Chameleon bus endpoints.
+fpga_linein_bus_endpoint = 'Chameleon FPGA line-in'
+headphone_bus_endpoint = 'Cros device headphone'
+
+### Chameleon Constants End ###
+
+# Begin logcat strings dict"""
+logcat_strings = {
+    "media_playback_vol_changed": "onRouteVolumeChanged",
+}
+
+# End logcat strings dict"""
+
+### Begin Service Discovery UUIDS ###
+# Values match the Bluetooth SIG defined values: """
+""" https://www.bluetooth.com/specifications/assigned-numbers/service-discovery """
+sig_uuid_constants = {
+    "BASE_UUID": "0000{}-0000-1000-8000-00805F9B34FB",
+    "SDP": "0001",
+    "UDP": "0002",
+    "RFCOMM": "0003",
+    "TCP": "0004",
+    "TCS-BIN": "0005",
+    "TCS-AT": "0006",
+    "ATT": "0007",
+    "OBEX": "0008",
+    "IP": "0009",
+    "FTP": "000A",
+    "HTTP": "000C",
+    "WSP": "000E",
+    "BNEP": "000F",
+    "UPNP": "0010",
+    "HIDP": "0011",
+    "HardcopyControlChannel": "0012",
+    "HardcopyDataChannel": "0014",
+    "HardcopyNotification": "0016",
+    "AVCTP": "0017",
+    "AVDTP": "0019",
+    "CMTP": "001B",
+    "MCAPControlChannel": "001E",
+    "MCAPDataChannel": "001F",
+    "L2CAP": "0100",
+    "ServiceDiscoveryServerServiceClassID": "1000",
+    "BrowseGroupDescriptorServiceClassID": "1001",
+    "SerialPort": "1101",
+    "LANAccessUsingPPP": "1102",
+    "DialupNetworking": "1103",
+    "IrMCSync": "1104",
+    "OBEXObjectPush": "1105",
+    "OBEXFileTransfer": "1106",
+    "IrMCSyncCommand": "1107",
+    "Headset": "1108",
+    "CordlessTelephony": "1109",
+    "AudioSource": "110A",
+    "AudioSink": "110B",
+    "A/V_RemoteControlTarget": "110C",
+    "AdvancedAudioDistribution": "110D",
+    "A/V_RemoteControl": "110E",
+    "A/V_RemoteControlController": "110F",
+    "Intercom": "1110",
+    "Fax": "1111",
+    "Headset - Audio Gateway (AG)": "1112",
+    "WAP": "1113",
+    "WAP_CLIENT": "1114",
+    "PANU": "1115",
+    "NAP": "1116",
+    "GN": "1117",
+    "DirectPrinting": "1118",
+    "ReferencePrinting": "1119",
+    "ImagingResponder": "111B",
+    "ImagingAutomaticArchive": "111C",
+    "ImagingReferencedObjects": "111D",
+    "Handsfree": "111E",
+    "HandsfreeAudioGateway": "111F",
+    "DirectPrintingReferenceObjectsService": "1120",
+    "ReflectedUI": "1121",
+    "BasicPrinting": "1122",
+    "PrintingStatus": "1123",
+    "HumanInterfaceDeviceService": "1124",
+    "HardcopyCableReplacement": "1125",
+    "HCR_Print": "1126",
+    "HCR_Scan": "1127",
+    "Common_ISDN_Access": "1128",
+    "SIM_Access": "112D",
+    "Phonebook Access - PCE": "112E",
+    "Phonebook Access - PSE": "112F",
+    "Phonebook Access": "1130",
+    "Headset - HS": "1131",
+    "Message Access Server": "1132",
+    "Message Notification Server": "1133",
+    "Message Access Profile": "1134",
+    "GNSS": "1135",
+    "GNSS_Server": "1136",
+    "PnPInformation": "1200",
+    "GenericNetworking": "1201",
+    "GenericFileTransfer": "1202",
+    "GenericAudio": "1203",
+    "GenericTelephony": "1204",
+    "UPNP_Service": "1205",
+    "UPNP_IP_Service": "1206",
+    "ESDP_UPNP_IP_PAN": "1300",
+    "ESDP_UPNP_IP_LAP": "1301",
+    "ESDP_UPNP_L2CAP": "1302",
+    "VideoSource": "1303",
+    "VideoSink": "1304",
+    "VideoDistribution": "1305",
+    "HDP": "1400"
+}
+
+### End Service Discovery UUIDS ###
+
+### Begin Appearance Constants ###
+# https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.gap.appearance.xml
+sig_appearance_constants = {
+    "UNKNOWN": 0,
+    "PHONE": 64,
+    "COMPUTER": 128,
+    "WATCH": 192,
+    "WATCH_SPORTS": 193,
+    "CLOCK": 256,
+    "DISPLAY": 320,
+    "REMOTE_CONTROL": 384,
+    "EYE_GLASSES": 448,
+    "TAG": 512,
+    "KEYRING": 576,
+    "MEDIA_PLAYER": 640,
+    "BARCODE_SCANNER": 704,
+    "THERMOMETER": 768,
+    "THERMOMETER_EAR": 769,
+    "HEART_RATE_SENSOR": 832,
+    "HEART_RATE_SENSOR_BELT": 833,
+    "BLOOD_PRESSURE": 896,
+    "BLOOD_PRESSURE_ARM": 897,
+    "BLOOD_PRESSURE_WRIST": 898,
+    "HID": 960,
+    "HID_KEYBOARD": 961,
+    "HID_MOUSE": 962,
+    "HID_JOYSTICK": 963,
+    "HID_GAMEPAD": 964,
+    "HID_DIGITIZER_TABLET": 965,
+    "HID_CARD_READER": 966,
+    "HID_DIGITAL_PEN": 967,
+    "HID_BARCODE_SCANNER": 968,
+    "GLUCOSE_METER": 1024,
+    "RUNNING_WALKING_SENSOR": 1088,
+    "RUNNING_WALKING_SENSOR_IN_SHOE": 1089,
+    "RUNNING_WALKING_SENSOR_ON_SHOE": 1090,
+    "RUNNING_WALKING_SENSOR_ON_HIP": 1091,
+    "CYCLING": 1152,
+    "CYCLING_COMPUTER": 1153,
+    "CYCLING_SPEED_SENSOR": 1154,
+    "CYCLING_CADENCE_SENSOR": 1155,
+    "CYCLING_POWER_SENSOR": 1156,
+    "CYCLING_SPEED_AND_CADENCE_SENSOR": 1157,
+    "PULSE_OXIMETER": 3136,
+    "PULSE_OXIMETER_FINGERTIP": 3137,
+    "PULSE_OXIMETER_WRIST": 3138,
+    "WEIGHT_SCALE": 3200,
+    "PERSONAL_MOBILITY": 3264,
+    "PERSONAL_MOBILITY_WHEELCHAIR": 3265,
+    "PERSONAL_MOBILITY_SCOOTER": 3266,
+    "GLUCOSE_MONITOR": 3328,
+    "SPORTS_ACTIVITY": 5184,
+    "SPORTS_ACTIVITY_LOCATION_DISPLAY": 5185,
+    "SPORTS_ACTIVITY_LOCATION_AND_NAV_DISPLAY": 5186,
+    "SPORTS_ACTIVITY_LOCATION_POD": 5187,
+    "SPORTS_ACTIVITY_LOCATION_AND_NAV_POD": 5188,
+}
+
+### End Appearance Constants ###
+
+# Attribute Record values from the Bluetooth Specification
+# Version 5, Vol 3, Part B
+bt_attribute_values = {
+    'ATTR_SERVICE_RECORD_HANDLE': 0x0000,
+    'ATTR_SERVICE_CLASS_ID_LIST': 0x0001,
+    'ATTR_SERVICE_RECORD_STATE': 0x0002,
+    'ATTR_SERVICE_ID': 0x0003,
+    'ATTR_PROTOCOL_DESCRIPTOR_LIST': 0x0004,
+    'ATTR_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST': 0x000D,
+    'ATTR_BROWSE_GROUP_LIST': 0x0005,
+    'ATTR_LANGUAGE_BASE_ATTRIBUTE_ID_LIST': 0x0006,
+    'ATTR_SERVICE_INFO_TIME_TO_LIVE': 0x0007,
+    'ATTR_SERVICE_AVAILABILITY': 0x0008,
+    'ATTR_BLUETOOTH_PROFILE_DESCRIPTOR_LIST': 0x0009,
+    'ATTR_A2DP_SUPPORTED_FEATURES': 0x0311,
+}
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_contacts_utils.py b/acts_tests/acts_contrib/test_utils/bt/bt_contacts_utils.py
new file mode 100644
index 0000000..41de7c2
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_contacts_utils.py
@@ -0,0 +1,428 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Compare_contacts accepts 2 vcf files, extracts full name, email, and
+telephone numbers from each and reports how many unique cards it finds across
+the two files.
+"""
+
+from mmap import ACCESS_READ
+from mmap import mmap
+import logging
+import re
+import random
+import string
+import time
+from acts.utils import exe_cmd
+import queue
+
+# CallLog types
+INCOMMING_CALL_TYPE = "1"
+OUTGOING_CALL_TYPE = "2"
+MISSED_CALL_TYPE = "3"
+
+# Callback strings.
+CONTACTS_CHANGED_CALLBACK = "ContactsChanged"
+CALL_LOG_CHANGED = "CallLogChanged"
+CONTACTS_ERASED_CALLBACK = "ContactsErased"
+
+# URI for contacts database on Nexus.
+CONTACTS_URI = "content://com.android.contacts/data/phones"
+
+# Path for temporary file storage on device.
+STORAGE_PATH = "/storage/emulated/0/Download/"
+
+PBAP_SYNC_TIME = 30
+
+log = logging
+
+
+def parse_contacts(file_name):
+    """Read vcf file and generate a list of contacts.
+
+    Contacts full name, prefered email, and all phone numbers are extracted.
+    """
+
+    vcard_regex = re.compile(b"^BEGIN:VCARD((\n*?.*?)*?)END:VCARD",
+                             re.MULTILINE)
+    fullname_regex = re.compile(b"^FN:(.*)", re.MULTILINE)
+    email_regex = re.compile(b"^EMAIL;PREF:(.*)", re.MULTILINE)
+    tel_regex = re.compile(b"^TEL;(.*):(.*)", re.MULTILINE)
+
+    with open(file_name, "r") as contacts_file:
+        contacts = []
+        contacts_map = mmap(
+            contacts_file.fileno(), length=0, access=ACCESS_READ)
+        new_contact = None
+
+        # Find all VCARDs in the input file, then extract the first full name,
+        # first email address, and all phone numbers from it.  If there is at
+        # least a full name add it to the contact list.
+        for current_vcard in vcard_regex.findall(contacts_map):
+            new_contact = VCard()
+
+            fullname = fullname_regex.search(current_vcard[0])
+            if fullname is not None:
+                new_contact.name = fullname.group(1)
+
+            email = email_regex.search(current_vcard[0])
+            if email is not None:
+                new_contact.email = email.group(1)
+
+            for phone_number in tel_regex.findall(current_vcard[0]):
+                new_contact.add_phone_number(
+                    PhoneNumber(phone_number[0], phone_number[1]))
+
+            contacts.append(new_contact)
+
+        return contacts
+
+
+def phone_number_count(destination_path, file_name):
+    """Counts number of phone numbers in a VCF.
+    """
+    tel_regex = re.compile(b"^TEL;(.*):(.*)", re.MULTILINE)
+    with open("{}{}".format(destination_path, file_name),
+              "r") as contacts_file:
+        contacts_map = mmap(
+            contacts_file.fileno(), length=0, access=ACCESS_READ)
+        numbers = tel_regex.findall(contacts_map)
+        return len(numbers)
+
+
+def count_contacts_with_differences(destination_path,
+                                    pce_contacts_vcf_file_name,
+                                    pse_contacts_vcf_file_name):
+    """Compare two contact files and report the number of differences.
+
+    Difference count is returned, and the differences are logged, this is order
+    independent.
+    """
+
+    pce_contacts = parse_contacts("{}{}".format(destination_path,
+                                                pce_contacts_vcf_file_name))
+    pse_contacts = parse_contacts("{}{}".format(destination_path,
+                                                pse_contacts_vcf_file_name))
+
+    differences = set(pce_contacts).symmetric_difference(set(pse_contacts))
+    if not differences:
+        log.info("All {} contacts in the phonebooks match".format(
+            str(len(pce_contacts))))
+    else:
+        log.info("{} contacts match, but ".format(
+            str(len(set(pce_contacts).intersection(set(pse_contacts))))))
+        log.info("the following {} entries don't match:".format(
+            str(len(differences))))
+        for current_vcard in differences:
+            log.info(current_vcard)
+    return len(differences)
+
+
+class PhoneNumber(object):
+    """Simple class for maintaining a phone number entry and type with only the
+    digits.
+    """
+
+    def __init__(self, phone_type, phone_number):
+        self.phone_type = phone_type
+        # remove non digits from phone_number
+        self.phone_number = re.sub(r"\D", "", str(phone_number))
+
+    def __eq__(self, other):
+        return (self.phone_type == other.phone_type and
+                self.phone_number == other.phone_number)
+
+    def __hash__(self):
+        return hash(self.phone_type) ^ hash(self.phone_number)
+
+
+class VCard(object):
+    """Contains name, email, and phone numbers.
+    """
+
+    def __init__(self):
+        self.name = None
+        self.first_name = None
+        self.last_name = None
+        self.email = None
+        self.phone_numbers = []
+        self.photo = None
+
+    def __lt__(self, other):
+        return self.name < other.name
+
+    def __hash__(self):
+        result = hash(self.name) ^ hash(self.email) ^ hash(self.photo == None)
+        for number in self.phone_numbers:
+            result ^= hash(number)
+        return result
+
+    def __eq__(self, other):
+        return hash(self) == hash(other)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __str__(self):
+        vcard_strings = ["BEGIN:VCARD\n", "VERSION:2.1\n"]
+
+        if self.first_name or self.last_name:
+            vcard_strings.append("N:{};{};;;\nFN:{} {}\n".format(
+                self.last_name, self.first_name, self.first_name,
+                self.last_name))
+        elif self.name:
+            vcard_strings.append("FN:{}\n".format(self.name))
+
+        if self.phone_numbers:
+            for phone in self.phone_numbers:
+                vcard_strings.append("TEL;{}:{}\n".format(
+                    str(phone.phone_type), phone.phone_number))
+
+        if self.email:
+            vcard_strings.append("EMAIL;PREF:{}\n".format(self.email))
+
+        vcard_strings.append("END:VCARD\n")
+        return "".join(vcard_strings)
+
+    def add_phone_number(self, phone_number):
+        if phone_number not in self.phone_numbers:
+            self.phone_numbers.append(phone_number)
+
+
+def generate_random_phone_number():
+    """Generate a random phone number/type
+    """
+    return PhoneNumber("CELL",
+                       "+{0:010d}".format(random.randint(0, 9999999999)))
+
+
+def generate_random_string(length=8,
+                           charset="{}{}{}".format(string.digits,
+                                                   string.ascii_letters,
+                                                   string.punctuation)):
+    """Generate a random string of specified length from the characterset
+    """
+    # Remove ; since that would make 2 words.
+    charset = charset.replace(";", "")
+    name = []
+    for i in range(length):
+        name.append(random.choice(charset))
+    return "".join(name)
+
+
+def generate_contact_list(destination_path,
+                          file_name,
+                          contact_count,
+                          phone_number_count=1):
+    """Generate a simple VCF file for count contacts with basic content.
+
+    An example with count = 1 and local_number = 2]
+
+    BEGIN:VCARD
+    VERSION:2.1
+    N:Person;1;;;
+    FN:1 Person
+    TEL;CELL:+1-555-555-1234
+    TEL;CELL:+1-555-555-4321
+    EMAIL;PREF:person1@gmail.com
+    END:VCARD
+    """
+    vcards = []
+    for i in range(contact_count):
+        current_contact = VCard()
+        current_contact.first_name = generate_random_string(
+            random.randint(1, 19))
+        current_contact.last_name = generate_random_string(
+            random.randint(1, 19))
+        current_contact.email = "{}{}@{}.{}".format(
+            current_contact.last_name, current_contact.first_name,
+            generate_random_string(random.randint(1, 19)),
+            generate_random_string(random.randint(1, 4)))
+        for number in range(phone_number_count):
+            current_contact.add_phone_number(generate_random_phone_number())
+        vcards.append(current_contact)
+    create_new_contacts_vcf_from_vcards(destination_path, file_name, vcards)
+
+
+def create_new_contacts_vcf_from_vcards(destination_path, vcf_file_name,
+                                        vcards):
+    """Create a new file with filename
+    """
+    contact_file = open("{}{}".format(destination_path, vcf_file_name), "w+")
+    for card in vcards:
+        contact_file.write(str(card))
+    contact_file.close()
+
+
+def get_contact_count(device):
+    """Returns the number of name:phone number pairs.
+    """
+    contact_list = device.droid.contactsQueryContent(
+        CONTACTS_URI, ["display_name", "data1"], "", [], "display_name")
+    return len(contact_list)
+
+
+def import_device_contacts_from_vcf(device, destination_path, vcf_file, timeout=10):
+    """Uploads and import vcf file to device.
+    """
+    number_count = phone_number_count(destination_path, vcf_file)
+    device.log.info("Trying to add {} phone numbers.".format(number_count))
+    local_phonebook_path = "{}{}".format(destination_path, vcf_file)
+    phone_phonebook_path = "{}{}".format(STORAGE_PATH, vcf_file)
+    device.adb.push("{} {}".format(local_phonebook_path, phone_phonebook_path))
+    device.droid.importVcf("file://{}{}".format(STORAGE_PATH, vcf_file))
+    start_time = time.time()
+    while time.time() < start_time + timeout:
+        #TODO: use unattended way to bypass contact import module instead of keyevent
+        if "ImportVCardActivity" in device.get_my_current_focus_window():
+            # keyevent to allow contacts import from vcf file
+            for key in ["DPAD_RIGHT", "DPAD_RIGHT", "ENTER"]:
+                device.adb.shell("input keyevent KEYCODE_{}".format(key))
+            break
+        time.sleep(1)
+    if wait_for_phone_number_update_complete(device, number_count):
+        return number_count
+    else:
+        return 0
+
+
+def export_device_contacts_to_vcf(device, destination_path, vcf_file):
+    """Export and download vcf file from device.
+    """
+    path_on_phone = "{}{}".format(STORAGE_PATH, vcf_file)
+    device.droid.exportVcf("{}".format(path_on_phone))
+    # Download and then remove file from device
+    device.adb.pull("{} {}".format(path_on_phone, destination_path))
+    return True
+
+
+def delete_vcf_files(device):
+    """Deletes all files with .vcf extension
+    """
+    files = device.adb.shell("ls {}".format(STORAGE_PATH))
+    for file_name in files.split():
+        if ".vcf" in file_name:
+            device.adb.shell("rm -f {}{}".format(STORAGE_PATH, file_name))
+
+
+def erase_contacts(device):
+    """Erase all contacts out of devices contact database.
+    """
+    device.log.info("Erasing contacts.")
+    if get_contact_count(device) > 0:
+        device.droid.contactsEraseAll()
+        try:
+            device.ed.pop_event(CONTACTS_ERASED_CALLBACK, PBAP_SYNC_TIME)
+        except queue.Empty:
+            log.error("Phone book not empty.")
+            return False
+    return True
+
+
+def wait_for_phone_number_update_complete(device, expected_count):
+    """Check phone_number count on device and wait for updates until it has the
+    expected number of phone numbers in its contact database.
+    """
+    update_completed = True
+    try:
+        while (expected_count != get_contact_count(device) and
+               device.ed.pop_event(CONTACTS_CHANGED_CALLBACK, PBAP_SYNC_TIME)):
+            pass
+    except queue.Empty:
+        log.error("Contacts failed to update.")
+        update_completed = False
+    device.log.info("Found {} out of the expected {} contacts.".format(
+        get_contact_count(device), expected_count))
+    return update_completed
+
+
+def wait_for_call_log_update_complete(device, expected_count):
+    """Check call log count on device and wait for updates until it has the
+    expected number of calls in its call log database.
+    """
+    update_completed = True
+    try:
+        while (expected_count != device.droid.callLogGetCount() and
+               device.ed.pop_event(CALL_LOG_CHANGED, PBAP_SYNC_TIME)):
+            pass
+    except queue.Empty:
+        log.error("Call Log failed to update.")
+        update_completed = False
+    device.log.info("Found {} out of the expected {} call logs.".format(
+        device.droid.callLogGetCount(), expected_count))
+    return
+
+
+def add_call_log(device, call_log_type, phone_number, call_time):
+    """Add call number and time to specified log.
+    """
+    new_call_log = {}
+    new_call_log["type"] = str(call_log_type)
+    new_call_log["number"] = phone_number
+    new_call_log["time"] = str(call_time)
+    device.droid.callLogsPut(new_call_log)
+
+
+def get_and_compare_call_logs(pse, pce, call_log_type):
+    """Gather and compare call logs from PSE and PCE for the specified type.
+    """
+    pse_call_log = pse.droid.callLogsGet(call_log_type)
+    pce_call_log = pce.droid.callLogsGet(call_log_type)
+    return compare_call_logs(pse_call_log, pce_call_log)
+
+
+def normalize_phonenumber(phone_number):
+    """Remove all non-digits from phone_number
+    """
+    return re.sub(r"\D", "", phone_number)
+
+
+def compare_call_logs(pse_call_log, pce_call_log):
+    """Gather and compare call logs from PSE and PCE for the specified type.
+    """
+    call_logs_match = True
+    if len(pse_call_log) == len(pce_call_log):
+        for i in range(len(pse_call_log)):
+            # Compare the phone number
+            if normalize_phonenumber(pse_call_log[i][
+                    "number"]) != normalize_phonenumber(pce_call_log[i][
+                         "number"]):
+                log.warning("Call Log numbers differ")
+                call_logs_match = False
+
+            # Compare which log it was taken from (Incomming, Outgoing, Missed
+            if pse_call_log[i]["type"] != pce_call_log[i]["type"]:
+                log.warning("Call Log types differ")
+                call_logs_match = False
+
+            # Compare time to truncated second.
+            if int(pse_call_log[i]["date"]) // 1000 != int(pce_call_log[i][
+                    "date"]) // 1000:
+                log.warning("Call log times don't match, check timezone.")
+                call_logs_match = False
+
+    else:
+        log.warning("Call Log lengths differ {}:{}".format(
+            len(pse_call_log), len(pce_call_log)))
+        call_logs_match = False
+
+    if not call_logs_match:
+        log.info("PSE Call Log:")
+        log.info(pse_call_log)
+        log.info("PCE Call Log:")
+        log.info(pce_call_log)
+
+    return call_logs_match
+
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_factory.py b/acts_tests/acts_contrib/test_utils/bt/bt_factory.py
new file mode 100644
index 0000000..cbc4f09
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_factory.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import importlib
+
+
+def create(configs):
+    """Used to create instance of bt implementation.
+
+    A list of of configuration is extracted from configs.
+    The modules names are extracted and passed to import_module
+    to get the specific implementation, which gets appended to a
+    device list.
+    Args:
+        configs: A configurations dictionary that contains
+        a list of configs for each device in configs['user_params']['BtDevice'].
+
+    Returns:
+        A list of bt implementations.
+    """
+    bt_devices = []
+    for config in configs:
+        bt_name = config['bt_module']
+        bt = importlib.import_module('acts_contrib.test_utils.bt.bt_implementations.%s'
+                                      % bt_name)
+        bt_devices.append(bt.BluethoothDevice(config))
+    return bt_devices
+
+
+def destroy(bt_device_list):
+    for bt in bt_device_list:
+        bt.close()
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_gatt_utils.py b/acts_tests/acts_contrib/test_utils/bt/bt_gatt_utils.py
new file mode 100644
index 0000000..e902ec2
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_gatt_utils.py
@@ -0,0 +1,418 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import logging
+
+from acts_contrib.test_utils.bt.bt_test_utils import BtTestUtilsError
+from acts_contrib.test_utils.bt.bt_test_utils import get_mac_address_of_generic_advertisement
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_err
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import gatt_connection_state
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_phy_mask
+from acts_contrib.test_utils.bt.bt_constants import gatt_service_types
+from acts_contrib.test_utils.bt.bt_constants import gatt_transport
+import pprint
+from queue import Empty
+
+default_timeout = 10
+log = logging
+
+
+class GattTestUtilsError(Exception):
+    pass
+
+
+def setup_gatt_connection(cen_ad,
+                          mac_address,
+                          autoconnect,
+                          transport=gatt_transport['auto'],
+                          opportunistic=False):
+    gatt_callback = cen_ad.droid.gattCreateGattCallback()
+    log.info("Gatt Connect to mac address {}.".format(mac_address))
+    bluetooth_gatt = cen_ad.droid.gattClientConnectGatt(
+        gatt_callback, mac_address, autoconnect, transport, opportunistic,
+        gatt_phy_mask['1m_mask'])
+    expected_event = gatt_cb_strings['gatt_conn_change'].format(gatt_callback)
+    try:
+        event = cen_ad.ed.pop_event(expected_event, default_timeout)
+    except Empty:
+        close_gatt_client(cen_ad, bluetooth_gatt)
+        raise GattTestUtilsError(
+            "Could not establish a connection to "
+            "peripheral. Expected event: {}".format(expected_event))
+    if event['data']['State'] != gatt_connection_state['connected']:
+        close_gatt_client(cen_ad, bluetooth_gatt)
+        try:
+            cen_ad.droid.gattClientClose(bluetooth_gatt)
+        except Exception:
+            self.log.debug("Failed to close gatt client.")
+        raise GattTestUtilsError("Could not establish a connection to "
+                                 "peripheral. Event Details: {}".format(
+                                     pprint.pformat(event)))
+    return bluetooth_gatt, gatt_callback
+
+
+def close_gatt_client(cen_ad, bluetooth_gatt):
+    try:
+        cen_ad.droid.gattClientClose(bluetooth_gatt)
+    except Exception:
+        log.debug("Failed to close gatt client.")
+
+
+def disconnect_gatt_connection(cen_ad, bluetooth_gatt, gatt_callback):
+    cen_ad.droid.gattClientDisconnect(bluetooth_gatt)
+    wait_for_gatt_disconnect_event(cen_ad, gatt_callback)
+    return
+
+
+def wait_for_gatt_disconnect_event(cen_ad, gatt_callback):
+    expected_event = gatt_cb_strings['gatt_conn_change'].format(gatt_callback)
+    try:
+        event = cen_ad.ed.pop_event(expected_event, default_timeout)
+    except Empty:
+        raise GattTestUtilsError(
+            gatt_cb_err['gatt_conn_change_err'].format(expected_event))
+    found_state = event['data']['State']
+    expected_state = gatt_connection_state['disconnected']
+    if found_state != expected_state:
+        raise GattTestUtilsError(
+            "GATT connection state change expected {}, found {}".format(
+                expected_event, found_state))
+    return
+
+
+def orchestrate_gatt_connection(cen_ad,
+                                per_ad,
+                                transport=gatt_transport['le'],
+                                mac_address=None,
+                                autoconnect=False,
+                                opportunistic=False):
+    adv_callback = None
+    if mac_address is None:
+        if transport == gatt_transport['le']:
+            try:
+                mac_address, adv_callback, scan_callback = (
+                    get_mac_address_of_generic_advertisement(cen_ad, per_ad))
+            except BtTestUtilsError as err:
+                raise GattTestUtilsError(
+                    "Error in getting mac address: {}".format(err))
+        else:
+            mac_address = per_ad.droid.bluetoothGetLocalAddress()
+            adv_callback = None
+    bluetooth_gatt, gatt_callback = setup_gatt_connection(
+        cen_ad, mac_address, autoconnect, transport, opportunistic)
+    return bluetooth_gatt, gatt_callback, adv_callback
+
+
+def run_continuous_write_descriptor(cen_droid,
+                                    cen_ed,
+                                    per_droid,
+                                    per_ed,
+                                    gatt_server,
+                                    gatt_server_callback,
+                                    bluetooth_gatt,
+                                    services_count,
+                                    discovered_services_index,
+                                    number_of_iterations=100000):
+    log.info("Starting continuous write")
+    bt_device_id = 0
+    status = 1
+    offset = 1
+    test_value = [1, 2, 3, 4, 5, 6, 7]
+    test_value_return = [1, 2, 3]
+    for _ in range(number_of_iterations):
+        try:
+            for i in range(services_count):
+                characteristic_uuids = (
+                    cen_droid.gattClientGetDiscoveredCharacteristicUuids(
+                        discovered_services_index, i))
+                log.info(characteristic_uuids)
+                for characteristic in characteristic_uuids:
+                    descriptor_uuids = (
+                        cen_droid.gattClientGetDiscoveredDescriptorUuids(
+                            discovered_services_index, i, characteristic))
+                    log.info(descriptor_uuids)
+                    for descriptor in descriptor_uuids:
+                        cen_droid.gattClientDescriptorSetValue(
+                            bluetooth_gatt, discovered_services_index, i,
+                            characteristic, descriptor, test_value)
+                        cen_droid.gattClientWriteDescriptor(
+                            bluetooth_gatt, discovered_services_index, i,
+                            characteristic, descriptor)
+                        expected_event = gatt_cb_strings[
+                            'desc_write_req'].format(gatt_server_callback)
+                        try:
+                            event = per_ed.pop_event(expected_event,
+                                                     default_timeout)
+                        except Empty:
+                            log.error(gatt_cb_err['desc_write_req_err'].format(
+                                expected_event))
+                            return False
+                        request_id = event['data']['requestId']
+                        found_value = event['data']['value']
+                        if found_value != test_value:
+                            log.error(
+                                "Values didn't match. Found: {}, Expected: "
+                                "{}".format(found_value, test_value))
+                        per_droid.gattServerSendResponse(
+                            gatt_server, bt_device_id, request_id, status,
+                            offset, test_value_return)
+                        expected_event = gatt_cb_strings['desc_write'].format(
+                            bluetooth_gatt)
+                        try:
+                            cen_ed.pop_event(expected_event, default_timeout)
+                        except Empty:
+                            log.error(gatt_cb_strings['desc_write_err'].format(
+                                expected_event))
+                            raise Exception("Thread ended prematurely.")
+        except Exception as err:
+            log.error("Continuing but found exception: {}".format(err))
+
+
+def setup_characteristics_and_descriptors(droid):
+    characteristic_input = [
+        {
+            'uuid':
+            "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
+            'property':
+            gatt_characteristic['property_write']
+            | gatt_characteristic['property_write_no_response'],
+            'permission':
+            gatt_characteristic['permission_write']
+        },
+        {
+            'uuid':
+            "21c0a0bf-ad51-4a2d-8124-b74003e4e8c8",
+            'property':
+            gatt_characteristic['property_notify']
+            | gatt_characteristic['property_read'],
+            'permission':
+            gatt_characteristic['permission_read']
+        },
+        {
+            'uuid':
+            "6774191f-6ec3-4aa2-b8a8-cf830e41fda6",
+            'property':
+            gatt_characteristic['property_notify']
+            | gatt_characteristic['property_read'],
+            'permission':
+            gatt_characteristic['permission_read']
+        },
+    ]
+    descriptor_input = [{
+        'uuid':
+        "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
+        'property':
+        gatt_descriptor['permission_read']
+        | gatt_descriptor['permission_write'],
+    }, {
+        'uuid':
+        "76d5ed92-ca81-4edb-bb6b-9f019665fb32",
+        'property':
+        gatt_descriptor['permission_read']
+        | gatt_characteristic['permission_write'],
+    }]
+    characteristic_list = setup_gatt_characteristics(droid,
+                                                     characteristic_input)
+    descriptor_list = setup_gatt_descriptors(droid, descriptor_input)
+    return characteristic_list, descriptor_list
+
+
+def setup_multiple_services(per_ad):
+    per_droid, per_ed = per_ad.droid, per_ad.ed
+    gatt_server_callback = per_droid.gattServerCreateGattServerCallback()
+    gatt_server = per_droid.gattServerOpenGattServer(gatt_server_callback)
+    characteristic_list, descriptor_list = (
+        setup_characteristics_and_descriptors(per_droid))
+    per_droid.gattServerCharacteristicAddDescriptor(characteristic_list[1],
+                                                    descriptor_list[0])
+    per_droid.gattServerCharacteristicAddDescriptor(characteristic_list[2],
+                                                    descriptor_list[1])
+    gattService = per_droid.gattServerCreateService(
+        "00000000-0000-1000-8000-00805f9b34fb", gatt_service_types['primary'])
+    gattService2 = per_droid.gattServerCreateService(
+        "FFFFFFFF-0000-1000-8000-00805f9b34fb", gatt_service_types['primary'])
+    gattService3 = per_droid.gattServerCreateService(
+        "3846D7A0-69C8-11E4-BA00-0002A5D5C51B", gatt_service_types['primary'])
+    for characteristic in characteristic_list:
+        per_droid.gattServerAddCharacteristicToService(gattService,
+                                                       characteristic)
+    per_droid.gattServerAddService(gatt_server, gattService)
+    expected_event = gatt_cb_strings['serv_added'].format(gatt_server_callback)
+    try:
+        per_ed.pop_event(expected_event, default_timeout)
+    except Empty:
+        per_ad.droid.gattServerClose(gatt_server)
+        raise GattTestUtilsError(
+            gatt_cb_strings['serv_added_err'].format(expected_event))
+    for characteristic in characteristic_list:
+        per_droid.gattServerAddCharacteristicToService(gattService2,
+                                                       characteristic)
+    per_droid.gattServerAddService(gatt_server, gattService2)
+    try:
+        per_ed.pop_event(expected_event, default_timeout)
+    except Empty:
+        per_ad.droid.gattServerClose(gatt_server)
+        raise GattTestUtilsError(
+            gatt_cb_strings['serv_added_err'].format(expected_event))
+    for characteristic in characteristic_list:
+        per_droid.gattServerAddCharacteristicToService(gattService3,
+                                                       characteristic)
+    per_droid.gattServerAddService(gatt_server, gattService3)
+    try:
+        per_ed.pop_event(expected_event, default_timeout)
+    except Empty:
+        per_ad.droid.gattServerClose(gatt_server)
+        raise GattTestUtilsError(
+            gatt_cb_strings['serv_added_err'].format(expected_event))
+    return gatt_server_callback, gatt_server
+
+
+def setup_characteristics_and_descriptors(droid):
+    characteristic_input = [
+        {
+            'uuid':
+            "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
+            'property':
+            gatt_characteristic['property_write']
+            | gatt_characteristic['property_write_no_response'],
+            'permission':
+            gatt_characteristic['property_write']
+        },
+        {
+            'uuid':
+            "21c0a0bf-ad51-4a2d-8124-b74003e4e8c8",
+            'property':
+            gatt_characteristic['property_notify']
+            | gatt_characteristic['property_read'],
+            'permission':
+            gatt_characteristic['permission_read']
+        },
+        {
+            'uuid':
+            "6774191f-6ec3-4aa2-b8a8-cf830e41fda6",
+            'property':
+            gatt_characteristic['property_notify']
+            | gatt_characteristic['property_read'],
+            'permission':
+            gatt_characteristic['permission_read']
+        },
+    ]
+    descriptor_input = [{
+        'uuid':
+        "aa7edd5a-4d1d-4f0e-883a-d145616a1630",
+        'property':
+        gatt_descriptor['permission_read']
+        | gatt_descriptor['permission_write'],
+    }, {
+        'uuid':
+        "76d5ed92-ca81-4edb-bb6b-9f019665fb32",
+        'property':
+        gatt_descriptor['permission_read']
+        | gatt_characteristic['permission_write'],
+    }]
+    characteristic_list = setup_gatt_characteristics(droid,
+                                                     characteristic_input)
+    descriptor_list = setup_gatt_descriptors(droid, descriptor_input)
+    return characteristic_list, descriptor_list
+
+
+def setup_gatt_characteristics(droid, input):
+    characteristic_list = []
+    for item in input:
+        index = droid.gattServerCreateBluetoothGattCharacteristic(
+            item['uuid'], item['property'], item['permission'])
+        characteristic_list.append(index)
+    return characteristic_list
+
+
+def setup_gatt_descriptors(droid, input):
+    descriptor_list = []
+    for item in input:
+        index = droid.gattServerCreateBluetoothGattDescriptor(
+            item['uuid'],
+            item['property'],
+        )
+        descriptor_list.append(index)
+    log.info("setup descriptor list: {}".format(descriptor_list))
+    return descriptor_list
+
+
+def setup_gatt_mtu(cen_ad, bluetooth_gatt, gatt_callback, mtu):
+    """utility function to set mtu for GATT connection.
+
+    Steps:
+    1. Request mtu change.
+    2. Check if the mtu is changed to the new value
+
+    Args:
+        cen_ad: test device for client to scan.
+        bluetooth_gatt: GATT object
+        mtu: new mtu value to be set
+
+    Returns:
+        If success, return True.
+        if fail, return False
+    """
+    cen_ad.droid.gattClientRequestMtu(bluetooth_gatt, mtu)
+    expected_event = gatt_cb_strings['mtu_changed'].format(gatt_callback)
+    try:
+        mtu_event = cen_ad.ed.pop_event(expected_event, default_timeout)
+        mtu_size_found = mtu_event['data']['MTU']
+        if mtu_size_found != mtu:
+            log.error("MTU size found: {}, expected: {}".format(
+                mtu_size_found, mtu))
+            return False
+    except Empty:
+        log.error(gatt_cb_err['mtu_changed_err'].format(expected_event))
+        return False
+    return True
+
+
+def log_gatt_server_uuids(cen_ad,
+                          discovered_services_index,
+                          bluetooth_gatt=None):
+    services_count = cen_ad.droid.gattClientGetDiscoveredServicesCount(
+        discovered_services_index)
+    for i in range(services_count):
+        service = cen_ad.droid.gattClientGetDiscoveredServiceUuid(
+            discovered_services_index, i)
+        log.info("Discovered service uuid {}".format(service))
+        characteristic_uuids = (
+            cen_ad.droid.gattClientGetDiscoveredCharacteristicUuids(
+                discovered_services_index, i))
+        for j in range(len(characteristic_uuids)):
+            descriptor_uuids = (
+                cen_ad.droid.gattClientGetDiscoveredDescriptorUuidsByIndex(
+                    discovered_services_index, i, j))
+            if bluetooth_gatt:
+                char_inst_id = cen_ad.droid.gattClientGetCharacteristicInstanceId(
+                    bluetooth_gatt, discovered_services_index, i, j)
+                log.info("Discovered characteristic handle uuid: {} {}".format(
+                    hex(char_inst_id), characteristic_uuids[j]))
+                for k in range(len(descriptor_uuids)):
+                    desc_inst_id = cen_ad.droid.gattClientGetDescriptorInstanceId(
+                        bluetooth_gatt, discovered_services_index, i, j, k)
+                    log.info("Discovered descriptor handle uuid: {} {}".format(
+                        hex(desc_inst_id), descriptor_uuids[k]))
+            else:
+                log.info("Discovered characteristic uuid: {}".format(
+                    characteristic_uuids[j]))
+                for k in range(len(descriptor_uuids)):
+                    log.info("Discovered descriptor uuid {}".format(
+                        descriptor_uuids[k]))
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_implementations/bt_stub.py b/acts_tests/acts_contrib/test_utils/bt/bt_implementations/bt_stub.py
new file mode 100644
index 0000000..50c9393
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_implementations/bt_stub.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+"""A stub implementation of a DUT interface.
+
+This a stub interface which allows automated test to run
+without automating the hardware. This here for two reasons, first
+as an example of how to write a dut implementation, and second as
+an implementation that can be used to test case without writing
+out the full implementation.
+"""
+
+import logging
+
+class BluethoothDevice:
+    """The api interface used in the test for the stub.
+
+    This is interface which defines all the functions that can be
+    called by the bt test suite.
+    """
+
+    def __init__(self, config):
+        print('Init Stub with ', config)
+        logging.info('Init Stub with '+str(config))
+
+    def answer_phone(self):
+        input('Answer the phone and then press enter\n')
+
+    def hang_up(self):
+        input('Hang up the phone and then press enter\n')
+
+    def toggle_pause(self):
+        input('Press pause on device then press enter\n')
+
+    def volume(self, direction):
+        """Adjust the volume specified by the value of direction.
+
+        Args:
+            direction: A string that is either UP or DOWN
+            that indicates which way to adjust the volume.
+        """
+
+        return input('move volume '+direction+' and then press enter\n')
+
+    def connect(self, android):
+        input('Connect device and press enter\n')
+
+    def is_bt_connected(self):
+        con = input('Is device connected? y/n').lower()
+        while con not in ['y', 'n']:
+            con = input('Is device connected? y/n').lower()
+        return con == 'y'
+
+    def close(self):
+        """This where the hardware is released.
+        """
+        print('Close Stub')
+        logging.info('Close Stub')
+
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_metrics_utils.py b/acts_tests/acts_contrib/test_utils/bt/bt_metrics_utils.py
new file mode 100644
index 0000000..f30fe84
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_metrics_utils.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+import base64
+from acts_contrib.test_utils.bt.protos import bluetooth_pb2
+
+
+def get_bluetooth_metrics(ad):
+    """
+    Get metric proto from the Bluetooth stack
+
+    Parameter:
+      ad - Android device
+
+    Return:
+      a protobuf object representing Bluetooth metric
+
+    """
+    bluetooth_log = bluetooth_pb2.BluetoothLog()
+    proto_native_str_64 = \
+        ad.adb.shell("/system/bin/dumpsys bluetooth_manager --proto-bin")
+    proto_native_str = base64.b64decode(proto_native_str_64)
+    bluetooth_log.MergeFromString(proto_native_str)
+    return bluetooth_log
+
+def get_bluetooth_profile_connection_stats_map(bluetooth_log):
+    return project_pairs_list_to_map(bluetooth_log.profile_connection_stats,
+                                     lambda stats : stats.profile_id,
+                                     lambda stats : stats.num_times_connected,
+                                     lambda a, b : a + b)
+
+def get_bluetooth_headset_profile_connection_stats_map(bluetooth_log):
+    return project_pairs_list_to_map(bluetooth_log.headset_profile_connection_stats,
+                                     lambda stats : stats.profile_id,
+                                     lambda stats : stats.num_times_connected,
+                                     lambda a, b : a + b)
+
+def project_pairs_list_to_map(pairs_list, get_key, get_value, merge_value):
+    """
+    Project a list of pairs (A, B) into a map of [A] --> B
+    :param pairs_list:  list of pairs (A, B)
+    :param get_key: function used to get key from pair (A, B)
+    :param get_value: function used to get value from pair (A, B)
+    :param merge_value: function used to merge values of B
+    :return: a map of [A] --> B
+    """
+    result = {}
+    for item in pairs_list:
+        my_key = get_key(item)
+        if my_key in result:
+            result[my_key] = merge_value(result[my_key], get_value(item))
+        else:
+            result[my_key] = get_value(item)
+    return result
diff --git a/acts_tests/acts_contrib/test_utils/bt/bt_power_test_utils.py b/acts_tests/acts_contrib/test_utils/bt/bt_power_test_utils.py
new file mode 100644
index 0000000..899621f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_power_test_utils.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 Google, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import time
+import acts_contrib.test_utils.bt.BleEnum as bleenum
+import acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder as icb
+
+BLE_LOCATION_SCAN_ENABLE = 'settings put global ble_scan_always_enabled 1'
+BLE_LOCATION_SCAN_DISABLE = 'settings put global ble_scan_always_enabled 0'
+SCREEN_WAIT_TIME = 1
+
+
+class MediaControl(object):
+    """Media control using adb shell for power testing.
+
+    Object to control media play status using adb.
+    """
+
+    def __init__(self, android_device, music_file):
+        """Initialize the media_control class.
+
+        Args:
+            android_dut: android_device object
+            music_file: location of the music file
+        """
+        self.android_device = android_device
+        self.music_file = music_file
+
+    def player_on_foreground(self):
+        """Turn on screen and make sure media play is on foreground
+
+        All media control keycode only works when screen is on and media player
+        is on the foreground. Turn off screen first and turn it on to make sure
+        all operation is based on the same screen status. Otherwise, 'MENU' key
+        would block command to be sent.
+        """
+        self.android_device.droid.goToSleepNow()
+        time.sleep(SCREEN_WAIT_TIME)
+        self.android_device.droid.wakeUpNow()
+        time.sleep(SCREEN_WAIT_TIME)
+        self.android_device.send_keycode('MENU')
+        time.sleep(SCREEN_WAIT_TIME)
+
+    def play(self):
+        """Start playing music.
+
+        """
+        self.player_on_foreground()
+        PLAY = 'am start -a android.intent.action.VIEW -d file://{} -t audio/wav'.format(
+            self.music_file)
+        self.android_device.adb.shell(PLAY)
+
+    def pause(self):
+        """Pause music.
+
+        """
+        self.player_on_foreground()
+        self.android_device.send_keycode('MEDIA_PAUSE')
+
+    def resume(self):
+        """Pause music.
+
+        """
+        self.player_on_foreground()
+        self.android_device.send_keycode('MEDIA_PLAY')
+
+    def stop(self):
+        """Stop music and close media play.
+
+        """
+        self.player_on_foreground()
+        self.android_device.send_keycode('MEDIA_STOP')
+
+
+def start_apk_ble_adv(dut, adv_mode, adv_power_level, adv_duration):
+    """Trigger BLE advertisement from power-test.apk.
+
+    Args:
+        dut: Android device under test, type AndroidDevice obj
+        adv_mode: The BLE advertisement mode.
+            {0: 'LowPower', 1: 'Balanced', 2: 'LowLatency'}
+        adv_power_leve: The BLE advertisement TX power level.
+            {0: 'UltraLowTXPower', 1: 'LowTXPower', 2: 'MediumTXPower,
+            3: HighTXPower}
+        adv_duration: duration of advertisement in seconds, type int
+    """
+
+    adv_duration = str(adv_duration) + 's'
+    builder = icb.InstrumentationTestCommandBuilder.default()
+    builder.add_test_class(
+        "com.google.android.device.power.tests.ble.BleAdvertise")
+    builder.set_manifest_package("com.google.android.device.power")
+    builder.set_runner("androidx.test.runner.AndroidJUnitRunner")
+    builder.add_key_value_param("cool-off-duration", "0s")
+    builder.add_key_value_param("idle-duration", "0s")
+    builder.add_key_value_param(
+        "com.android.test.power.receiver.ADVERTISE_MODE", adv_mode)
+    builder.add_key_value_param("com.android.test.power.receiver.POWER_LEVEL",
+                                adv_power_level)
+    builder.add_key_value_param(
+        "com.android.test.power.receiver.ADVERTISING_DURATION", adv_duration)
+
+    adv_command = builder.build() + ' &'
+    logging.info('Start BLE {} at {} for {} seconds'.format(
+        bleenum.AdvertiseSettingsAdvertiseMode(adv_mode).name,
+        bleenum.AdvertiseSettingsAdvertiseTxPower(adv_power_level).name,
+        adv_duration))
+    dut.adb.shell_nb(adv_command)
+
+
+def start_apk_ble_scan(dut, scan_mode, scan_duration):
+    """Build the command to trigger BLE scan from power-test.apk.
+
+    Args:
+        dut: Android device under test, type AndroidDevice obj
+        scan_mode: The BLE scan mode.
+            {0: 'LowPower', 1: 'Balanced', 2: 'LowLatency', -1: 'Opportunistic'}
+        scan_duration: duration of scan in seconds, type int
+    Returns:
+        adv_command: the command for BLE scan
+    """
+    scan_duration = str(scan_duration) + 's'
+    builder = icb.InstrumentationTestCommandBuilder.default()
+    builder.set_proto_path()
+    builder.add_flag('--no-isolated-storage')
+    builder.add_test_class("com.google.android.device.power.tests.ble.BleScan")
+    builder.set_manifest_package("com.google.android.device.power")
+    builder.set_runner("androidx.test.runner.AndroidJUnitRunner")
+    builder.add_key_value_param("cool-off-duration", "0s")
+    builder.add_key_value_param("idle-duration", "0s")
+    builder.add_key_value_param("com.android.test.power.receiver.SCAN_MODE",
+                                scan_mode)
+    builder.add_key_value_param("com.android.test.power.receiver.MATCH_MODE",
+                                2)
+    builder.add_key_value_param(
+        "com.android.test.power.receiver.SCAN_DURATION", scan_duration)
+    builder.add_key_value_param(
+        "com.android.test.power.receiver.CALLBACK_TYPE", 1)
+    builder.add_key_value_param("com.android.test.power.receiver.FILTER",
+                                'true')
+
+    scan_command = builder.build() + ' &'
+    logging.info('Start BLE {} scans for {} seconds'.format(
+        bleenum.ScanSettingsScanMode(scan_mode).name, scan_duration))
+    dut.adb.shell_nb(scan_command)
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
new file mode 100644
index 0000000..653e4df
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bt_test_utils.py
@@ -0,0 +1,1863 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import logging
+import os
+import random
+import re
+import string
+import threading
+import time
+from queue import Empty
+from subprocess import call
+
+from acts import asserts
+from acts_contrib.test_utils.bt.bt_constants import adv_fail
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_constants import batch_scan_not_supported_list
+from acts_contrib.test_utils.bt.bt_constants import batch_scan_result
+from acts_contrib.test_utils.bt.bt_constants import bits_per_samples
+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 bluetooth_a2dp_codec_config_changed
+from acts_contrib.test_utils.bt.bt_constants import bluetooth_off
+from acts_contrib.test_utils.bt.bt_constants import bluetooth_on
+from acts_contrib.test_utils.bt.bt_constants import \
+    bluetooth_profile_connection_state_changed
+from acts_contrib.test_utils.bt.bt_constants import bluetooth_socket_conn_test_uuid
+from acts_contrib.test_utils.bt.bt_constants import bt_default_timeout
+from acts_contrib.test_utils.bt.bt_constants import bt_profile_constants
+from acts_contrib.test_utils.bt.bt_constants import bt_profile_states
+from acts_contrib.test_utils.bt.bt_constants import bt_rfcomm_uuids
+from acts_contrib.test_utils.bt.bt_constants import bt_scan_mode_types
+from acts_contrib.test_utils.bt.bt_constants import btsnoop_last_log_path_on_device
+from acts_contrib.test_utils.bt.bt_constants import btsnoop_log_path_on_device
+from acts_contrib.test_utils.bt.bt_constants import channel_modes
+from acts_contrib.test_utils.bt.bt_constants import codec_types
+from acts_contrib.test_utils.bt.bt_constants import default_bluetooth_socket_timeout_ms
+from acts_contrib.test_utils.bt.bt_constants import default_rfcomm_timeout_ms
+from acts_contrib.test_utils.bt.bt_constants import hid_id_keyboard
+from acts_contrib.test_utils.bt.bt_constants import pairing_variant_passkey_confirmation
+from acts_contrib.test_utils.bt.bt_constants import pan_connect_timeout
+from acts_contrib.test_utils.bt.bt_constants import sample_rates
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_constants import sig_uuid_constants
+from acts_contrib.test_utils.bt.bt_constants import small_timeout
+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_http_connection
+from acts.utils import exe_cmd
+
+from acts import utils
+
+log = logging
+
+advertisements_to_devices = {}
+
+
+class BtTestUtilsError(Exception):
+    pass
+
+
+def _add_android_device_to_dictionary(android_device, profile_list,
+                                      selector_dict):
+    """Adds the AndroidDevice and supported features to the selector dictionary
+
+    Args:
+        android_device: The Android device.
+        profile_list: The list of profiles the Android device supports.
+    """
+    for profile in profile_list:
+        if profile in selector_dict and android_device not in selector_dict[
+                profile]:
+            selector_dict[profile].append(android_device)
+        else:
+            selector_dict[profile] = [android_device]
+
+
+def bluetooth_enabled_check(ad, timeout_sec=5):
+    """Checks if the Bluetooth state is enabled, if not it will attempt to
+    enable it.
+
+    Args:
+        ad: The Android device list to enable Bluetooth on.
+        timeout_sec: number of seconds to wait for toggle to take effect.
+
+    Returns:
+        True if successful, false if unsuccessful.
+    """
+    if not ad.droid.bluetoothCheckState():
+        ad.droid.bluetoothToggleState(True)
+        expected_bluetooth_on_event_name = bluetooth_on
+        try:
+            ad.ed.pop_event(expected_bluetooth_on_event_name,
+                            bt_default_timeout)
+        except Empty:
+            ad.log.info(
+                "Failed to toggle Bluetooth on(no broadcast received).")
+            # Try one more time to poke at the actual state.
+            if ad.droid.bluetoothCheckState():
+                ad.log.info(".. actual state is ON")
+                return True
+            ad.log.error(".. actual state is OFF")
+            return False
+    end_time = time.time() + timeout_sec
+    while not ad.droid.bluetoothCheckState() and time.time() < end_time:
+        time.sleep(1)
+    return ad.droid.bluetoothCheckState()
+
+
+def check_device_supported_profiles(droid):
+    """Checks for Android device supported profiles.
+
+    Args:
+        droid: The droid object to query.
+
+    Returns:
+        A dictionary of supported profiles.
+    """
+    profile_dict = {}
+    profile_dict['hid'] = droid.bluetoothHidIsReady()
+    profile_dict['hsp'] = droid.bluetoothHspIsReady()
+    profile_dict['a2dp'] = droid.bluetoothA2dpIsReady()
+    profile_dict['avrcp'] = droid.bluetoothAvrcpIsReady()
+    profile_dict['a2dp_sink'] = droid.bluetoothA2dpSinkIsReady()
+    profile_dict['hfp_client'] = droid.bluetoothHfpClientIsReady()
+    profile_dict['pbap_client'] = droid.bluetoothPbapClientIsReady()
+    return profile_dict
+
+
+def cleanup_scanners_and_advertisers(scn_android_device, scn_callback_list,
+                                     adv_android_device, adv_callback_list):
+    """Try to gracefully stop all scanning and advertising instances.
+
+    Args:
+        scn_android_device: The Android device that is actively scanning.
+        scn_callback_list: The scan callback id list that needs to be stopped.
+        adv_android_device: The Android device that is actively advertising.
+        adv_callback_list: The advertise callback id list that needs to be
+            stopped.
+    """
+    scan_droid, scan_ed = scn_android_device.droid, scn_android_device.ed
+    adv_droid = adv_android_device.droid
+    try:
+        for scan_callback in scn_callback_list:
+            scan_droid.bleStopBleScan(scan_callback)
+    except Exception as err:
+        scn_android_device.log.debug(
+            "Failed to stop LE scan... reseting Bluetooth. Error {}".format(
+                err))
+        reset_bluetooth([scn_android_device])
+    try:
+        for adv_callback in adv_callback_list:
+            adv_droid.bleStopBleAdvertising(adv_callback)
+    except Exception as err:
+        adv_android_device.log.debug(
+            "Failed to stop LE advertisement... reseting Bluetooth. Error {}".
+            format(err))
+        reset_bluetooth([adv_android_device])
+
+
+def clear_bonded_devices(ad):
+    """Clear bonded devices from the input Android device.
+
+    Args:
+        ad: the Android device performing the connection.
+    Returns:
+        True if clearing bonded devices was successful, false if unsuccessful.
+    """
+    bonded_device_list = ad.droid.bluetoothGetBondedDevices()
+    while bonded_device_list:
+        device_address = bonded_device_list[0]['address']
+        if not ad.droid.bluetoothUnbond(device_address):
+            log.error("Failed to unbond {} from {}".format(
+                device_address, ad.serial))
+            return False
+        log.info("Successfully unbonded {} from {}".format(
+            device_address, ad.serial))
+        #TODO: wait for BOND_STATE_CHANGED intent instead of waiting
+        time.sleep(1)
+
+        # If device was first connected using LE transport, after bonding it is
+        # accessible through it's LE address, and through it classic address.
+        # Unbonding it will unbond two devices representing different
+        # "addresses". Attempt to unbond such already unbonded devices will
+        # result in bluetoothUnbond returning false.
+        bonded_device_list = ad.droid.bluetoothGetBondedDevices()
+    return True
+
+
+def connect_phone_to_headset(android,
+                             headset,
+                             timeout=bt_default_timeout,
+                             connection_check_period=10):
+    """Connects android phone to bluetooth headset.
+    Headset object must have methods power_on and enter_pairing_mode,
+    and attribute mac_address.
+
+    Args:
+        android: AndroidDevice object with SL4A installed.
+        headset: Object with attribute mac_address and methods power_on and
+            enter_pairing_mode.
+        timeout: Seconds to wait for devices to connect.
+        connection_check_period: how often to check for connection once the
+            SL4A connect RPC has been sent.
+    Returns:
+        connected (bool): True if devices are paired and connected by end of
+        method. False otherwise.
+    """
+    headset_mac_address = headset.mac_address
+    connected = is_a2dp_src_device_connected(android, headset_mac_address)
+    log.info('Devices connected before pair attempt: %s' % connected)
+    if not connected:
+        # Turn on headset and initiate pairing mode.
+        headset.enter_pairing_mode()
+        android.droid.bluetoothStartPairingHelper()
+    start_time = time.time()
+    # If already connected, skip pair and connect attempt.
+    while not connected and (time.time() - start_time < timeout):
+        bonded_info = android.droid.bluetoothGetBondedDevices()
+        connected_info = android.droid.bluetoothGetConnectedDevices()
+        if headset.mac_address not in [
+                info["address"] for info in bonded_info
+        ]:
+            # Use SL4A to pair and connect with headset.
+            headset.enter_pairing_mode()
+            android.droid.bluetoothDiscoverAndBond(headset_mac_address)
+        elif headset.mac_address not in [
+                info["address"] for info in connected_info
+        ]:
+            #Device is bonded but not connected
+            android.droid.bluetoothConnectBonded(headset_mac_address)
+        else:
+            #Headset is connected, but A2DP profile is not
+            android.droid.bluetoothA2dpConnect(headset_mac_address)
+        log.info('Waiting for connection...')
+        time.sleep(connection_check_period)
+        # Check for connection.
+        connected = is_a2dp_src_device_connected(android, headset_mac_address)
+    log.info('Devices connected after pair attempt: %s' % connected)
+    return connected
+
+def connect_pri_to_sec(pri_ad, sec_ad, profiles_set, attempts=2):
+    """Connects pri droid to secondary droid.
+
+    Args:
+        pri_ad: AndroidDroid initiating connection
+        sec_ad: AndroidDroid accepting connection
+        profiles_set: Set of profiles to be connected
+        attempts: Number of attempts to try until failure.
+
+    Returns:
+        Pass if True
+        Fail if False
+    """
+    device_addr = sec_ad.droid.bluetoothGetLocalAddress()
+    # Allows extra time for the SDP records to be updated.
+    time.sleep(2)
+    curr_attempts = 0
+    while curr_attempts < attempts:
+        log.info("connect_pri_to_sec curr attempt {} total {}".format(
+            curr_attempts, attempts))
+        if _connect_pri_to_sec(pri_ad, sec_ad, profiles_set):
+            return True
+        curr_attempts += 1
+    log.error("connect_pri_to_sec failed to connect after {} attempts".format(
+        attempts))
+    return False
+
+
+def _connect_pri_to_sec(pri_ad, sec_ad, profiles_set):
+    """Connects pri droid to secondary droid.
+
+    Args:
+        pri_ad: AndroidDroid initiating connection.
+        sec_ad: AndroidDroid accepting connection.
+        profiles_set: Set of profiles to be connected.
+
+    Returns:
+        True of connection is successful, false if unsuccessful.
+    """
+    # Check if we support all profiles.
+    supported_profiles = bt_profile_constants.values()
+    for profile in profiles_set:
+        if profile not in supported_profiles:
+            pri_ad.log.info("Profile {} is not supported list {}".format(
+                profile, supported_profiles))
+            return False
+
+    # First check that devices are bonded.
+    paired = False
+    for paired_device in pri_ad.droid.bluetoothGetBondedDevices():
+        if paired_device['address'] == \
+                sec_ad.droid.bluetoothGetLocalAddress():
+            paired = True
+            break
+
+    if not paired:
+        pri_ad.log.error("Not paired to {}".format(sec_ad.serial))
+        return False
+
+    # Now try to connect them, the following call will try to initiate all
+    # connections.
+    pri_ad.droid.bluetoothConnectBonded(
+        sec_ad.droid.bluetoothGetLocalAddress())
+
+    end_time = time.time() + 10
+    profile_connected = set()
+    sec_addr = sec_ad.droid.bluetoothGetLocalAddress()
+    pri_ad.log.info("Profiles to be connected {}".format(profiles_set))
+    # First use APIs to check profile connection state
+    while (time.time() < end_time
+           and not profile_connected.issuperset(profiles_set)):
+        if (bt_profile_constants['headset_client'] not in profile_connected
+                and bt_profile_constants['headset_client'] in profiles_set):
+            if is_hfp_client_device_connected(pri_ad, sec_addr):
+                profile_connected.add(bt_profile_constants['headset_client'])
+        if (bt_profile_constants['a2dp'] not in profile_connected
+                and bt_profile_constants['a2dp'] in profiles_set):
+            if is_a2dp_src_device_connected(pri_ad, sec_addr):
+                profile_connected.add(bt_profile_constants['a2dp'])
+        if (bt_profile_constants['a2dp_sink'] not in profile_connected
+                and bt_profile_constants['a2dp_sink'] in profiles_set):
+            if is_a2dp_snk_device_connected(pri_ad, sec_addr):
+                profile_connected.add(bt_profile_constants['a2dp_sink'])
+        if (bt_profile_constants['map_mce'] not in profile_connected
+                and bt_profile_constants['map_mce'] in profiles_set):
+            if is_map_mce_device_connected(pri_ad, sec_addr):
+                profile_connected.add(bt_profile_constants['map_mce'])
+        if (bt_profile_constants['map'] not in profile_connected
+                and bt_profile_constants['map'] in profiles_set):
+            if is_map_mse_device_connected(pri_ad, sec_addr):
+                profile_connected.add(bt_profile_constants['map'])
+        time.sleep(0.1)
+    # If APIs fail, try to find the connection broadcast receiver.
+    while not profile_connected.issuperset(profiles_set):
+        try:
+            profile_event = pri_ad.ed.pop_event(
+                bluetooth_profile_connection_state_changed,
+                bt_default_timeout + 10)
+            pri_ad.log.info("Got event {}".format(profile_event))
+        except Exception:
+            pri_ad.log.error("Did not get {} profiles left {}".format(
+                bluetooth_profile_connection_state_changed, profile_connected))
+            return False
+
+        profile = profile_event['data']['profile']
+        state = profile_event['data']['state']
+        device_addr = profile_event['data']['addr']
+        if state == bt_profile_states['connected'] and \
+                device_addr == sec_ad.droid.bluetoothGetLocalAddress():
+            profile_connected.add(profile)
+        pri_ad.log.info(
+            "Profiles connected until now {}".format(profile_connected))
+    # Failure happens inside the while loop. If we came here then we already
+    # connected.
+    return True
+
+
+def determine_max_advertisements(android_device):
+    """Determines programatically how many advertisements the Android device
+    supports.
+
+    Args:
+        android_device: The Android device to determine max advertisements of.
+
+    Returns:
+        The maximum advertisement count.
+    """
+    android_device.log.info(
+        "Determining number of maximum concurrent advertisements...")
+    advertisement_count = 0
+    bt_enabled = False
+    expected_bluetooth_on_event_name = bluetooth_on
+    if not android_device.droid.bluetoothCheckState():
+        android_device.droid.bluetoothToggleState(True)
+    try:
+        android_device.ed.pop_event(expected_bluetooth_on_event_name,
+                                    bt_default_timeout)
+    except Exception:
+        android_device.log.info(
+            "Failed to toggle Bluetooth on(no broadcast received).")
+        # Try one more time to poke at the actual state.
+        if android_device.droid.bluetoothCheckState() is True:
+            android_device.log.info(".. actual state is ON")
+        else:
+            android_device.log.error(
+                "Failed to turn Bluetooth on. Setting default advertisements to 1"
+            )
+            advertisement_count = -1
+            return advertisement_count
+    advertise_callback_list = []
+    advertise_data = android_device.droid.bleBuildAdvertiseData()
+    advertise_settings = android_device.droid.bleBuildAdvertiseSettings()
+    while (True):
+        advertise_callback = android_device.droid.bleGenBleAdvertiseCallback()
+        advertise_callback_list.append(advertise_callback)
+
+        android_device.droid.bleStartBleAdvertising(advertise_callback,
+                                                    advertise_data,
+                                                    advertise_settings)
+
+        regex = "(" + adv_succ.format(
+            advertise_callback) + "|" + adv_fail.format(
+                advertise_callback) + ")"
+        # wait for either success or failure event
+        evt = android_device.ed.pop_events(regex, bt_default_timeout,
+                                           small_timeout)
+        if evt[0]["name"] == adv_succ.format(advertise_callback):
+            advertisement_count += 1
+            android_device.log.info(
+                "Advertisement {} started.".format(advertisement_count))
+        else:
+            error = evt[0]["data"]["Error"]
+            if error == "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS":
+                android_device.log.info(
+                    "Advertisement failed to start. Reached max " +
+                    "advertisements at {}".format(advertisement_count))
+                break
+            else:
+                raise BtTestUtilsError(
+                    "Expected ADVERTISE_FAILED_TOO_MANY_ADVERTISERS," +
+                    " but received bad error code {}".format(error))
+    try:
+        for adv in advertise_callback_list:
+            android_device.droid.bleStopBleAdvertising(adv)
+    except Exception:
+        android_device.log.error(
+            "Failed to stop advertisingment, resetting Bluetooth.")
+        reset_bluetooth([android_device])
+    return advertisement_count
+
+
+def disable_bluetooth(droid):
+    """Disable Bluetooth on input Droid object.
+
+    Args:
+        droid: The droid object to disable Bluetooth on.
+
+    Returns:
+        True if successful, false if unsuccessful.
+    """
+    if droid.bluetoothCheckState() is True:
+        droid.bluetoothToggleState(False)
+        if droid.bluetoothCheckState() is True:
+            log.error("Failed to toggle Bluetooth off.")
+            return False
+    return True
+
+
+def disconnect_pri_from_sec(pri_ad, sec_ad, profiles_list):
+    """
+    Disconnect primary from secondary on a specific set of profiles
+    Args:
+        pri_ad - Primary android_device initiating disconnection
+        sec_ad - Secondary android droid (sl4a interface to keep the
+          method signature the same connect_pri_to_sec above)
+        profiles_list - List of profiles we want to disconnect from
+
+    Returns:
+        True on Success
+        False on Failure
+    """
+    # Sanity check to see if all the profiles in the given set is supported
+    supported_profiles = bt_profile_constants.values()
+    for profile in profiles_list:
+        if profile not in supported_profiles:
+            pri_ad.log.info("Profile {} is not in supported list {}".format(
+                profile, supported_profiles))
+            return False
+
+    pri_ad.log.info(pri_ad.droid.bluetoothGetBondedDevices())
+    # Disconnecting on a already disconnected profile is a nop,
+    # so not checking for the connection state
+    try:
+        pri_ad.droid.bluetoothDisconnectConnectedProfile(
+            sec_ad.droid.bluetoothGetLocalAddress(), profiles_list)
+    except Exception as err:
+        pri_ad.log.error(
+            "Exception while trying to disconnect profile(s) {}: {}".format(
+                profiles_list, err))
+        return False
+
+    profile_disconnected = set()
+    pri_ad.log.info("Disconnecting from profiles: {}".format(profiles_list))
+
+    while not profile_disconnected.issuperset(profiles_list):
+        try:
+            profile_event = pri_ad.ed.pop_event(
+                bluetooth_profile_connection_state_changed, bt_default_timeout)
+            pri_ad.log.info("Got event {}".format(profile_event))
+        except Exception as e:
+            pri_ad.log.error(
+                "Did not disconnect from Profiles. Reason {}".format(e))
+            return False
+
+        profile = profile_event['data']['profile']
+        state = profile_event['data']['state']
+        device_addr = profile_event['data']['addr']
+
+        if state == bt_profile_states['disconnected'] and \
+                device_addr == sec_ad.droid.bluetoothGetLocalAddress():
+            profile_disconnected.add(profile)
+        pri_ad.log.info(
+            "Profiles disconnected so far {}".format(profile_disconnected))
+
+    return True
+
+
+def enable_bluetooth(droid, ed):
+    if droid.bluetoothCheckState() is True:
+        return True
+
+    droid.bluetoothToggleState(True)
+    expected_bluetooth_on_event_name = bluetooth_on
+    try:
+        ed.pop_event(expected_bluetooth_on_event_name, bt_default_timeout)
+    except Exception:
+        log.info("Failed to toggle Bluetooth on (no broadcast received)")
+        if droid.bluetoothCheckState() is True:
+            log.info(".. actual state is ON")
+            return True
+        log.info(".. actual state is OFF")
+        return False
+
+    return True
+
+
+def factory_reset_bluetooth(android_devices):
+    """Clears Bluetooth stack of input Android device list.
+
+        Args:
+            android_devices: The Android device list to reset Bluetooth
+
+        Returns:
+            True if successful, false if unsuccessful.
+        """
+    for a in android_devices:
+        droid, ed = a.droid, a.ed
+        a.log.info("Reset state of bluetooth on device.")
+        if not bluetooth_enabled_check(a):
+            return False
+        # TODO: remove device unbond b/79418045
+        # Temporary solution to ensure all devices are unbonded
+        bonded_devices = droid.bluetoothGetBondedDevices()
+        for b in bonded_devices:
+            a.log.info("Removing bond for device {}".format(b['address']))
+            droid.bluetoothUnbond(b['address'])
+
+        droid.bluetoothFactoryReset()
+        wait_for_bluetooth_manager_state(droid)
+        if not enable_bluetooth(droid, ed):
+            return False
+    return True
+
+
+def generate_ble_advertise_objects(droid):
+    """Generate generic LE advertise objects.
+
+    Args:
+        droid: The droid object to generate advertise LE objects from.
+
+    Returns:
+        advertise_callback: The generated advertise callback id.
+        advertise_data: The generated advertise data id.
+        advertise_settings: The generated advertise settings id.
+    """
+    advertise_callback = droid.bleGenBleAdvertiseCallback()
+    advertise_data = droid.bleBuildAdvertiseData()
+    advertise_settings = droid.bleBuildAdvertiseSettings()
+    return advertise_callback, advertise_data, advertise_settings
+
+
+def generate_ble_scan_objects(droid):
+    """Generate generic LE scan objects.
+
+    Args:
+        droid: The droid object to generate LE scan objects from.
+
+    Returns:
+        filter_list: The generated scan filter list id.
+        scan_settings: The generated scan settings id.
+        scan_callback: The generated scan callback id.
+    """
+    filter_list = droid.bleGenFilterList()
+    scan_settings = droid.bleBuildScanSetting()
+    scan_callback = droid.bleGenScanCallback()
+    return filter_list, scan_settings, scan_callback
+
+
+def generate_id_by_size(size,
+                        chars=(string.ascii_lowercase +
+                               string.ascii_uppercase + string.digits)):
+    """Generate random ascii characters of input size and input char types
+
+    Args:
+        size: Input size of string.
+        chars: (Optional) Chars to use in generating a random string.
+
+    Returns:
+        String of random input chars at the input size.
+    """
+    return ''.join(random.choice(chars) for _ in range(size))
+
+
+def get_advanced_droid_list(android_devices):
+    """Add max_advertisement and batch_scan_supported attributes to input
+    Android devices
+
+    This will programatically determine maximum LE advertisements of each
+    input Android device.
+
+    Args:
+        android_devices: The Android devices to setup.
+
+    Returns:
+        List of Android devices with new attribtues.
+    """
+    droid_list = []
+    for a in android_devices:
+        d, e = a.droid, a.ed
+        model = d.getBuildModel()
+        max_advertisements = 1
+        batch_scan_supported = True
+        if model in advertisements_to_devices.keys():
+            max_advertisements = advertisements_to_devices[model]
+        else:
+            max_advertisements = determine_max_advertisements(a)
+            max_tries = 3
+            # Retry to calculate max advertisements
+            while max_advertisements == -1 and max_tries > 0:
+                a.log.info(
+                    "Attempts left to determine max advertisements: {}".format(
+                        max_tries))
+                max_advertisements = determine_max_advertisements(a)
+                max_tries -= 1
+            advertisements_to_devices[model] = max_advertisements
+        if model in batch_scan_not_supported_list:
+            batch_scan_supported = False
+        role = {
+            'droid': d,
+            'ed': e,
+            'max_advertisements': max_advertisements,
+            'batch_scan_supported': batch_scan_supported
+        }
+        droid_list.append(role)
+    return droid_list
+
+
+def get_bluetooth_crash_count(android_device):
+    out = android_device.adb.shell("dumpsys bluetooth_manager")
+    return int(re.search("crashed(.*\d)", out).group(1))
+
+
+def read_otp(ad):
+    """Reads and parses the OTP output to return TX power backoff
+
+    Reads the OTP registers from the phone, parses them to return a
+    dict of TX power backoffs for different power levels
+
+    Args:
+        ad : android device object
+
+    Returns :
+        otp_dict : power backoff dict
+    """
+
+    ad.adb.shell('svc bluetooth disable')
+    time.sleep(2)
+    otp_output = ad.adb.shell('bluetooth_sar_test -r')
+    ad.adb.shell('svc bluetooth enable')
+    time.sleep(2)
+    otp_dict = {
+        "BR": {
+            "10": 0,
+            "9": 0,
+            "8": 0
+        },
+        "EDR": {
+            "10": 0,
+            "9": 0,
+            "8": 0
+        },
+        "BLE": {
+            "10": 0,
+            "9": 0,
+            "8": 0
+        }
+    }
+
+    otp_regex = '\s+\[\s+PL10:\s+(\d+)\s+PL9:\s+(\d+)*\s+PL8:\s+(\d+)\s+\]'
+
+    for key in otp_dict:
+        bank_list = re.findall("{}{}".format(key, otp_regex), otp_output)
+        for bank_tuple in bank_list:
+            if ('0', '0', '0') != bank_tuple:
+                [otp_dict[key]["10"], otp_dict[key]["9"],
+                 otp_dict[key]["8"]] = bank_tuple
+    return otp_dict
+
+
+def get_bt_metric(ad_list, duration=1, tag="bt_metric", processed=True):
+    """ Function to get the bt metric from logcat.
+
+    Captures logcat for the specified duration and returns the bqr results.
+    Takes list of android objects as input. If a single android object is given,
+    converts it into a list.
+
+    Args:
+        ad_list: list of android_device objects
+        duration: time duration (seconds) for which the logcat is parsed.
+        tag: tag to be appended to the logcat dump.
+        processed: flag to process bqr output.
+
+    Returns:
+        metrics_dict: dict of metrics for each android device.
+    """
+
+    # Defining bqr quantitites and their regex to extract
+    regex_dict = {
+        "vsp_txpl": "VSP_TxPL:\s(\S+)",
+        "pwlv": "PwLv:\s(\S+)",
+        "rssi": "RSSI:\s[-](\d+)"
+    }
+    metrics_dict = {"rssi": {}, "pwlv": {}, "vsp_txpl": {}}
+
+    # Converting a single android device object to list
+    if not isinstance(ad_list, list):
+        ad_list = [ad_list]
+
+    #Time sync with the test machine
+    for ad in ad_list:
+        ad.droid.setTime(int(round(time.time() * 1000)))
+        time.sleep(0.5)
+
+    begin_time = utils.get_current_epoch_time()
+    time.sleep(duration)
+    end_time = utils.get_current_epoch_time()
+
+    for ad in ad_list:
+        bt_rssi_log = ad.cat_adb_log(tag, begin_time, end_time)
+        bqr_tag = "Handle:"
+
+        # Extracting supporting bqr quantities
+        for metric, regex in regex_dict.items():
+            bqr_metric = []
+            file_bt_log = open(bt_rssi_log, "r")
+            for line in file_bt_log:
+                if bqr_tag in line:
+                    if re.findall(regex, line):
+                        m = re.findall(regex, line)[0].strip(",")
+                        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]
+        ]
+
+        # Processing formatted data if processing is required
+        if processed:
+            # Computes the average RSSI
+            metrics_dict["rssi"][ad.serial] = round(
+                sum(metrics_dict["rssi"][ad.serial]) /
+                len(metrics_dict["rssi"][ad.serial]), 2)
+            # Returns last noted value for power level
+            metrics_dict["pwlv"][ad.serial] = float(
+                sum(metrics_dict["pwlv"][ad.serial]) /
+                len(metrics_dict["pwlv"][ad.serial]))
+
+    return metrics_dict
+
+
+def get_bt_rssi(ad, duration=1, processed=True):
+    """Function to get average bt rssi from logcat.
+
+    This function returns the average RSSI for the given duration. RSSI values are
+    extracted from BQR.
+
+    Args:
+        ad: (list of) android_device object.
+        duration: time duration(seconds) for which logcat is parsed.
+
+    Returns:
+        avg_rssi: average RSSI on each android device for the given duration.
+    """
+    function_tag = "get_bt_rssi"
+    bqr_results = get_bt_metric(ad,
+                                duration,
+                                tag=function_tag,
+                                processed=processed)
+    return bqr_results["rssi"]
+
+
+def enable_bqr(
+    ad_list,
+    bqr_interval=10,
+    bqr_event_mask=15,
+):
+    """Sets up BQR reporting.
+
+       Sets up BQR to report BT metrics at the requested frequency and toggles
+       airplane mode for the bqr settings to take effect.
+
+    Args:
+        ad_list: an android_device or list of android devices.
+    """
+    # Converting a single android device object to list
+    if not isinstance(ad_list, list):
+        ad_list = [ad_list]
+
+    for ad in ad_list:
+        #Setting BQR parameters
+        ad.adb.shell("setprop persist.bluetooth.bqr.event_mask {}".format(
+            bqr_event_mask))
+        ad.adb.shell("setprop persist.bluetooth.bqr.min_interval_ms {}".format(
+            bqr_interval))
+
+        ## Toggle airplane mode
+        ad.droid.connectivityToggleAirplaneMode(True)
+        ad.droid.connectivityToggleAirplaneMode(False)
+
+
+def disable_bqr(ad_list):
+    """Disables BQR reporting.
+
+    Args:
+        ad_list: an android_device or list of android devices.
+    """
+    # Converting a single android device object to list
+    if not isinstance(ad_list, list):
+        ad_list = [ad_list]
+
+    DISABLE_BQR_MASK = 0
+
+    for ad in ad_list:
+        #Disabling BQR
+        ad.adb.shell("setprop persist.bluetooth.bqr.event_mask {}".format(
+            DISABLE_BQR_MASK))
+
+        ## Toggle airplane mode
+        ad.droid.connectivityToggleAirplaneMode(True)
+        ad.droid.connectivityToggleAirplaneMode(False)
+
+
+def get_device_selector_dictionary(android_device_list):
+    """Create a dictionary of Bluetooth features vs Android devices.
+
+    Args:
+        android_device_list: The list of Android devices.
+    Returns:
+        A dictionary of profiles/features to Android devices.
+    """
+    selector_dict = {}
+    for ad in android_device_list:
+        uuids = ad.droid.bluetoothGetLocalUuids()
+
+        for profile, uuid_const in sig_uuid_constants.items():
+            uuid_check = sig_uuid_constants['BASE_UUID'].format(
+                uuid_const).lower()
+            if uuids and uuid_check in uuids:
+                if profile in selector_dict:
+                    selector_dict[profile].append(ad)
+                else:
+                    selector_dict[profile] = [ad]
+
+        # Various services may not be active during BT startup.
+        # If the device can be identified through adb shell pm list features
+        # then try to add them to the appropriate profiles / features.
+
+        # Android TV.
+        if "feature:com.google.android.tv.installed" in ad.features:
+            ad.log.info("Android TV device found.")
+            supported_profiles = ['AudioSink']
+            _add_android_device_to_dictionary(ad, supported_profiles,
+                                              selector_dict)
+
+        # Android Auto
+        elif "feature:android.hardware.type.automotive" in ad.features:
+            ad.log.info("Android Auto device found.")
+            # Add: AudioSink , A/V_RemoteControl,
+            supported_profiles = [
+                'AudioSink', 'A/V_RemoteControl', 'Message Notification Server'
+            ]
+            _add_android_device_to_dictionary(ad, supported_profiles,
+                                              selector_dict)
+        # Android Wear
+        elif "feature:android.hardware.type.watch" in ad.features:
+            ad.log.info("Android Wear device found.")
+            supported_profiles = []
+            _add_android_device_to_dictionary(ad, supported_profiles,
+                                              selector_dict)
+        # Android Phone
+        elif "feature:android.hardware.telephony" in ad.features:
+            ad.log.info("Android Phone device found.")
+            # Add: AudioSink
+            supported_profiles = [
+                'AudioSource', 'A/V_RemoteControlTarget',
+                'Message Access Server'
+            ]
+            _add_android_device_to_dictionary(ad, supported_profiles,
+                                              selector_dict)
+    return selector_dict
+
+
+def get_mac_address_of_generic_advertisement(scan_ad, adv_ad):
+    """Start generic advertisement and get it's mac address by LE scanning.
+
+    Args:
+        scan_ad: The Android device to use as the scanner.
+        adv_ad: The Android device to use as the advertiser.
+
+    Returns:
+        mac_address: The mac address of the advertisement.
+        advertise_callback: The advertise callback id of the active
+            advertisement.
+    """
+    adv_ad.droid.bleSetAdvertiseDataIncludeDeviceName(True)
+    adv_ad.droid.bleSetAdvertiseSettingsAdvertiseMode(
+        ble_advertise_settings_modes['low_latency'])
+    adv_ad.droid.bleSetAdvertiseSettingsIsConnectable(True)
+    adv_ad.droid.bleSetAdvertiseSettingsTxPowerLevel(
+        ble_advertise_settings_tx_powers['high'])
+    advertise_callback, advertise_data, advertise_settings = (
+        generate_ble_advertise_objects(adv_ad.droid))
+    adv_ad.droid.bleStartBleAdvertising(advertise_callback, advertise_data,
+                                        advertise_settings)
+    try:
+        adv_ad.ed.pop_event(adv_succ.format(advertise_callback),
+                            bt_default_timeout)
+    except Empty as err:
+        raise BtTestUtilsError(
+            "Advertiser did not start successfully {}".format(err))
+    filter_list = scan_ad.droid.bleGenFilterList()
+    scan_settings = scan_ad.droid.bleBuildScanSetting()
+    scan_callback = scan_ad.droid.bleGenScanCallback()
+    scan_ad.droid.bleSetScanFilterDeviceName(
+        adv_ad.droid.bluetoothGetLocalName())
+    scan_ad.droid.bleBuildScanFilter(filter_list)
+    scan_ad.droid.bleStartBleScan(filter_list, scan_settings, scan_callback)
+    try:
+        event = scan_ad.ed.pop_event(
+            "BleScan{}onScanResults".format(scan_callback), bt_default_timeout)
+    except Empty as err:
+        raise BtTestUtilsError(
+            "Scanner did not find advertisement {}".format(err))
+    mac_address = event['data']['Result']['deviceInfo']['address']
+    return mac_address, advertise_callback, scan_callback
+
+
+def hid_device_send_key_data_report(host_id, device_ad, key, interval=1):
+    """Send a HID report simulating a 1-second keyboard press from host_ad to
+    device_ad
+
+    Args:
+        host_id: the Bluetooth MAC address or name of the HID host
+        device_ad: HID device
+        key: the key we want to send
+        interval: the interval between key press and key release
+    """
+    device_ad.droid.bluetoothHidDeviceSendReport(host_id, hid_id_keyboard,
+                                                 hid_keyboard_report(key))
+    time.sleep(interval)
+    device_ad.droid.bluetoothHidDeviceSendReport(host_id, hid_id_keyboard,
+                                                 hid_keyboard_report("00"))
+
+
+def hid_keyboard_report(key, modifier="00"):
+    """Get the HID keyboard report for the given key
+
+    Args:
+        key: the key we want
+        modifier: HID keyboard modifier bytes
+    Returns:
+        The byte array for the HID report.
+    """
+    return str(
+        bytearray.fromhex(" ".join(
+            [modifier, "00", key, "00", "00", "00", "00", "00"])), "utf-8")
+
+
+def is_a2dp_connected(sink, source):
+    """
+    Convenience Function to see if the 2 devices are connected on
+    A2dp.
+    Args:
+        sink:       Audio Sink
+        source:     Audio Source
+    Returns:
+        True if Connected
+        False if Not connected
+    """
+
+    devices = sink.droid.bluetoothA2dpSinkGetConnectedDevices()
+    for device in devices:
+        sink.log.info("A2dp Connected device {}".format(device["name"]))
+        if (device["address"] == source.droid.bluetoothGetLocalAddress()):
+            return True
+    return False
+
+
+def is_a2dp_snk_device_connected(ad, addr):
+    """Determines if an AndroidDevice has A2DP snk connectivity to input address
+
+    Args:
+        ad: the Android device
+        addr: the address that's expected
+    Returns:
+        True if connection was successful, false if unsuccessful.
+    """
+    devices = ad.droid.bluetoothA2dpSinkGetConnectedDevices()
+    ad.log.info("Connected A2DP Sink devices: {}".format(devices))
+    if addr in {d['address'] for d in devices}:
+        return True
+    return False
+
+
+def is_a2dp_src_device_connected(ad, addr):
+    """Determines if an AndroidDevice has A2DP connectivity to input address
+
+    Args:
+        ad: the Android device
+        addr: the address that's expected
+    Returns:
+        True if connection was successful, false if unsuccessful.
+    """
+    devices = ad.droid.bluetoothA2dpGetConnectedDevices()
+    ad.log.info("Connected A2DP Source devices: {}".format(devices))
+    if addr in {d['address'] for d in devices}:
+        return True
+    return False
+
+
+def is_hfp_client_device_connected(ad, addr):
+    """Determines if an AndroidDevice has HFP connectivity to input address
+
+    Args:
+        ad: the Android device
+        addr: the address that's expected
+    Returns:
+        True if connection was successful, false if unsuccessful.
+    """
+    devices = ad.droid.bluetoothHfpClientGetConnectedDevices()
+    ad.log.info("Connected HFP Client devices: {}".format(devices))
+    if addr in {d['address'] for d in devices}:
+        return True
+    return False
+
+
+def is_map_mce_device_connected(ad, addr):
+    """Determines if an AndroidDevice has MAP MCE connectivity to input address
+
+    Args:
+        ad: the Android device
+        addr: the address that's expected
+    Returns:
+        True if connection was successful, false if unsuccessful.
+    """
+    devices = ad.droid.bluetoothMapClientGetConnectedDevices()
+    ad.log.info("Connected MAP MCE devices: {}".format(devices))
+    if addr in {d['address'] for d in devices}:
+        return True
+    return False
+
+
+def is_map_mse_device_connected(ad, addr):
+    """Determines if an AndroidDevice has MAP MSE connectivity to input address
+
+    Args:
+        ad: the Android device
+        addr: the address that's expected
+    Returns:
+        True if connection was successful, false if unsuccessful.
+    """
+    devices = ad.droid.bluetoothMapGetConnectedDevices()
+    ad.log.info("Connected MAP MSE devices: {}".format(devices))
+    if addr in {d['address'] for d in devices}:
+        return True
+    return False
+
+
+def kill_bluetooth_process(ad):
+    """Kill Bluetooth process on Android device.
+
+    Args:
+        ad: Android device to kill BT process on.
+    """
+    ad.log.info("Killing Bluetooth process.")
+    pid = ad.adb.shell(
+        "ps | grep com.android.bluetooth | awk '{print $2}'").decode('ascii')
+    call(["adb -s " + ad.serial + " shell kill " + pid], shell=True)
+
+
+def log_energy_info(android_devices, state):
+    """Logs energy info of input Android devices.
+
+    Args:
+        android_devices: input Android device list to log energy info from.
+        state: the input state to log. Usually 'Start' or 'Stop' for logging.
+
+    Returns:
+        A logging string of the Bluetooth energy info reported.
+    """
+    return_string = "{} Energy info collection:\n".format(state)
+    # Bug: b/31966929
+    return return_string
+
+
+def orchestrate_and_verify_pan_connection(pan_dut, panu_dut):
+    """Setups up a PAN conenction between two android devices.
+
+    Args:
+        pan_dut: the Android device providing tethering services
+        panu_dut: the Android device using the internet connection from the
+            pan_dut
+    Returns:
+        True if PAN connection and verification is successful,
+        false if unsuccessful.
+    """
+    if not toggle_airplane_mode_by_adb(log, panu_dut, True):
+        panu_dut.log.error("Failed to toggle airplane mode on")
+        return False
+    if not toggle_airplane_mode_by_adb(log, panu_dut, False):
+        pan_dut.log.error("Failed to toggle airplane mode off")
+        return False
+    pan_dut.droid.bluetoothStartConnectionStateChangeMonitor("")
+    panu_dut.droid.bluetoothStartConnectionStateChangeMonitor("")
+    if not bluetooth_enabled_check(panu_dut):
+        return False
+    if not bluetooth_enabled_check(pan_dut):
+        return False
+    pan_dut.droid.bluetoothPanSetBluetoothTethering(True)
+    if not (pair_pri_to_sec(pan_dut, panu_dut)):
+        return False
+    if not pan_dut.droid.bluetoothPanIsTetheringOn():
+        pan_dut.log.error("Failed to enable Bluetooth tethering.")
+        return False
+    # Magic sleep needed to give the stack time in between bonding and
+    # connecting the PAN profile.
+    time.sleep(pan_connect_timeout)
+    panu_dut.droid.bluetoothConnectBonded(
+        pan_dut.droid.bluetoothGetLocalAddress())
+    if not verify_http_connection(log, panu_dut):
+        panu_dut.log.error("Can't verify http connection on PANU device.")
+        if not verify_http_connection(log, pan_dut):
+            pan_dut.log.info(
+                "Can't verify http connection on PAN service device")
+        return False
+    return True
+
+
+def orchestrate_bluetooth_socket_connection(
+        client_ad,
+        server_ad,
+        accept_timeout_ms=default_bluetooth_socket_timeout_ms,
+        uuid=None):
+    """Sets up the Bluetooth Socket connection between two Android devices.
+
+    Args:
+        client_ad: the Android device performing the connection.
+        server_ad: the Android device accepting the connection.
+    Returns:
+        True if connection was successful, false if unsuccessful.
+    """
+    server_ad.droid.bluetoothStartPairingHelper()
+    client_ad.droid.bluetoothStartPairingHelper()
+
+    server_ad.droid.bluetoothSocketConnBeginAcceptThreadUuid(
+        (bluetooth_socket_conn_test_uuid if uuid is None else uuid),
+        accept_timeout_ms)
+    client_ad.droid.bluetoothSocketConnBeginConnectThreadUuid(
+        server_ad.droid.bluetoothGetLocalAddress(),
+        (bluetooth_socket_conn_test_uuid if uuid is None else uuid))
+
+    end_time = time.time() + bt_default_timeout
+    result = False
+    test_result = True
+    while time.time() < end_time:
+        if len(client_ad.droid.bluetoothSocketConnActiveConnections()) > 0:
+            test_result = True
+            client_ad.log.info("Bluetooth socket Client Connection Active")
+            break
+        else:
+            test_result = False
+        time.sleep(1)
+    if not test_result:
+        client_ad.log.error(
+            "Failed to establish a Bluetooth socket connection")
+        return False
+    return True
+
+
+def orchestrate_rfcomm_connection(client_ad,
+                                  server_ad,
+                                  accept_timeout_ms=default_rfcomm_timeout_ms,
+                                  uuid=None):
+    """Sets up the RFCOMM connection between two Android devices.
+
+    Args:
+        client_ad: the Android device performing the connection.
+        server_ad: the Android device accepting the connection.
+    Returns:
+        True if connection was successful, false if unsuccessful.
+    """
+    result = orchestrate_bluetooth_socket_connection(
+        client_ad, server_ad, accept_timeout_ms,
+        (bt_rfcomm_uuids['default_uuid'] if uuid is None else uuid))
+
+    return result
+
+
+def pair_pri_to_sec(pri_ad, sec_ad, attempts=2, auto_confirm=True):
+    """Pairs pri droid to secondary droid.
+
+    Args:
+        pri_ad: Android device initiating connection
+        sec_ad: Android device accepting connection
+        attempts: Number of attempts to try until failure.
+        auto_confirm: Auto confirm passkey match for both devices
+
+    Returns:
+        Pass if True
+        Fail if False
+    """
+    pri_ad.droid.bluetoothStartConnectionStateChangeMonitor(
+        sec_ad.droid.bluetoothGetLocalAddress())
+    curr_attempts = 0
+    while curr_attempts < attempts:
+        if _pair_pri_to_sec(pri_ad, sec_ad, auto_confirm):
+            return True
+        # Wait 2 seconds before unbound
+        time.sleep(2)
+        if not clear_bonded_devices(pri_ad):
+            log.error(
+                "Failed to clear bond for primary device at attempt {}".format(
+                    str(curr_attempts)))
+            return False
+        if not clear_bonded_devices(sec_ad):
+            log.error(
+                "Failed to clear bond for secondary device at attempt {}".
+                format(str(curr_attempts)))
+            return False
+        # Wait 2 seconds after unbound
+        time.sleep(2)
+        curr_attempts += 1
+    log.error("pair_pri_to_sec failed to connect after {} attempts".format(
+        str(attempts)))
+    return False
+
+
+def _pair_pri_to_sec(pri_ad, sec_ad, auto_confirm):
+    # Enable discovery on sec_ad so that pri_ad can find it.
+    # The timeout here is based on how much time it would take for two devices
+    # to pair with each other once pri_ad starts seeing devices.
+    pri_droid = pri_ad.droid
+    sec_droid = sec_ad.droid
+    pri_ad.ed.clear_all_events()
+    sec_ad.ed.clear_all_events()
+    log.info("Bonding device {} to {}".format(
+        pri_droid.bluetoothGetLocalAddress(),
+        sec_droid.bluetoothGetLocalAddress()))
+    sec_droid.bluetoothMakeDiscoverable(bt_default_timeout)
+    target_address = sec_droid.bluetoothGetLocalAddress()
+    log.debug("Starting paring helper on each device")
+    pri_droid.bluetoothStartPairingHelper(auto_confirm)
+    sec_droid.bluetoothStartPairingHelper(auto_confirm)
+    pri_ad.log.info("Primary device starting discovery and executing bond")
+    result = pri_droid.bluetoothDiscoverAndBond(target_address)
+    if not auto_confirm:
+        if not _wait_for_passkey_match(pri_ad, sec_ad):
+            return False
+    # Loop until we have bonded successfully or timeout.
+    end_time = time.time() + bt_default_timeout
+    pri_ad.log.info("Verifying devices are bonded")
+    while time.time() < end_time:
+        bonded_devices = pri_droid.bluetoothGetBondedDevices()
+        bonded = False
+        for d in bonded_devices:
+            if d['address'] == target_address:
+                pri_ad.log.info("Successfully bonded to device")
+                return True
+        time.sleep(0.1)
+    # Timed out trying to bond.
+    pri_ad.log.info("Failed to bond devices.")
+    return False
+
+
+def reset_bluetooth(android_devices):
+    """Resets Bluetooth state of input Android device list.
+
+    Args:
+        android_devices: The Android device list to reset Bluetooth state on.
+
+    Returns:
+        True if successful, false if unsuccessful.
+    """
+    for a in android_devices:
+        droid, ed = a.droid, a.ed
+        a.log.info("Reset state of bluetooth on device.")
+        if droid.bluetoothCheckState() is True:
+            droid.bluetoothToggleState(False)
+            expected_bluetooth_off_event_name = bluetooth_off
+            try:
+                ed.pop_event(expected_bluetooth_off_event_name,
+                             bt_default_timeout)
+            except Exception:
+                a.log.error("Failed to toggle Bluetooth off.")
+                return False
+        # temp sleep for b/17723234
+        time.sleep(3)
+        if not bluetooth_enabled_check(a):
+            return False
+    return True
+
+
+def scan_and_verify_n_advertisements(scn_ad, max_advertisements):
+    """Verify that input number of advertisements can be found from the scanning
+    Android device.
+
+    Args:
+        scn_ad: The Android device to start LE scanning on.
+        max_advertisements: The number of advertisements the scanner expects to
+        find.
+
+    Returns:
+        True if successful, false if unsuccessful.
+    """
+    test_result = False
+    address_list = []
+    filter_list = scn_ad.droid.bleGenFilterList()
+    scn_ad.droid.bleBuildScanFilter(filter_list)
+    scan_settings = scn_ad.droid.bleBuildScanSetting()
+    scan_callback = scn_ad.droid.bleGenScanCallback()
+    scn_ad.droid.bleStartBleScan(filter_list, scan_settings, scan_callback)
+    start_time = time.time()
+    while (start_time + bt_default_timeout) > time.time():
+        event = None
+        try:
+            event = scn_ad.ed.pop_event(scan_result.format(scan_callback),
+                                        bt_default_timeout)
+        except Empty as error:
+            raise BtTestUtilsError(
+                "Failed to find scan event: {}".format(error))
+        address = event['data']['Result']['deviceInfo']['address']
+        if address not in address_list:
+            address_list.append(address)
+        if len(address_list) == max_advertisements:
+            test_result = True
+            break
+    scn_ad.droid.bleStopBleScan(scan_callback)
+    return test_result
+
+
+def set_bluetooth_codec(android_device,
+                        codec_type,
+                        sample_rate,
+                        bits_per_sample,
+                        channel_mode,
+                        codec_specific_1=0):
+    """Sets the A2DP codec configuration on the AndroidDevice.
+
+    Args:
+        android_device (acts.controllers.android_device.AndroidDevice): the
+            android device for which to switch the codec.
+        codec_type (str): the desired codec type. Must be a key in
+            bt_constants.codec_types.
+        sample_rate (str): the desired sample rate. Must be a key in
+            bt_constants.sample_rates.
+        bits_per_sample (str): the desired bits per sample. Must be a key in
+            bt_constants.bits_per_samples.
+        channel_mode (str): the desired channel mode. Must be a key in
+            bt_constants.channel_modes.
+        codec_specific_1 (int): the desired bit rate (quality) for LDAC codec.
+    Returns:
+        bool: True if the codec config was successfully changed to the desired
+            values. Else False.
+    """
+    message = ("Set Android Device A2DP Bluetooth codec configuration:\n"
+               "\tCodec: {codec_type}\n"
+               "\tSample Rate: {sample_rate}\n"
+               "\tBits per Sample: {bits_per_sample}\n"
+               "\tChannel Mode: {channel_mode}".format(
+                   codec_type=codec_type,
+                   sample_rate=sample_rate,
+                   bits_per_sample=bits_per_sample,
+                   channel_mode=channel_mode))
+    android_device.log.info(message)
+
+    # Send SL4A command
+    droid, ed = android_device.droid, android_device.ed
+    if not droid.bluetoothA2dpSetCodecConfigPreference(
+            codec_types[codec_type], sample_rates[str(sample_rate)],
+            bits_per_samples[str(bits_per_sample)],
+            channel_modes[channel_mode], codec_specific_1):
+        android_device.log.warning(
+            "SL4A command returned False. Codec was not "
+            "changed.")
+    else:
+        try:
+            ed.pop_event(bluetooth_a2dp_codec_config_changed,
+                         bt_default_timeout)
+        except Exception:
+            android_device.log.warning("SL4A event not registered. Codec "
+                                       "may not have been changed.")
+
+    # Validate codec value through ADB
+    # TODO (aidanhb): validate codec more robustly using SL4A
+    command = "dumpsys bluetooth_manager | grep -i 'current codec'"
+    out = android_device.adb.shell(command)
+    split_out = out.split(": ")
+    if len(split_out) != 2:
+        android_device.log.warning("Could not verify codec config change "
+                                   "through ADB.")
+    elif split_out[1].strip().upper() != codec_type:
+        android_device.log.error("Codec config was not changed.\n"
+                                 "\tExpected codec: {exp}\n"
+                                 "\tActual codec: {act}".format(
+                                     exp=codec_type, act=split_out[1].strip()))
+        return False
+    android_device.log.info("Bluetooth codec successfully changed.")
+    return True
+
+
+def set_bt_scan_mode(ad, scan_mode_value):
+    """Set Android device's Bluetooth scan mode.
+
+    Args:
+        ad: The Android device to set the scan mode on.
+        scan_mode_value: The value to set the scan mode to.
+
+    Returns:
+        True if successful, false if unsuccessful.
+    """
+    droid, ed = ad.droid, ad.ed
+    if scan_mode_value == bt_scan_mode_types['state_off']:
+        disable_bluetooth(droid)
+        scan_mode = droid.bluetoothGetScanMode()
+        reset_bluetooth([ad])
+        if scan_mode != scan_mode_value:
+            return False
+    elif scan_mode_value == bt_scan_mode_types['none']:
+        droid.bluetoothMakeUndiscoverable()
+        scan_mode = droid.bluetoothGetScanMode()
+        if scan_mode != scan_mode_value:
+            return False
+    elif scan_mode_value == bt_scan_mode_types['connectable']:
+        droid.bluetoothMakeUndiscoverable()
+        droid.bluetoothMakeConnectable()
+        scan_mode = droid.bluetoothGetScanMode()
+        if scan_mode != scan_mode_value:
+            return False
+    elif (scan_mode_value == bt_scan_mode_types['connectable_discoverable']):
+        droid.bluetoothMakeDiscoverable()
+        scan_mode = droid.bluetoothGetScanMode()
+        if scan_mode != scan_mode_value:
+            return False
+    else:
+        # invalid scan mode
+        return False
+    return True
+
+
+def set_device_name(droid, name):
+    """Set and check Bluetooth local name on input droid object.
+
+    Args:
+        droid: Droid object to set local name on.
+        name: the Bluetooth local name to set.
+
+    Returns:
+        True if successful, false if unsuccessful.
+    """
+    droid.bluetoothSetLocalName(name)
+    time.sleep(2)
+    droid_name = droid.bluetoothGetLocalName()
+    if droid_name != name:
+        return False
+    return True
+
+
+def set_profile_priority(host_ad, client_ad, profiles, priority):
+    """Sets the priority of said profile(s) on host_ad for client_ad"""
+    for profile in profiles:
+        host_ad.log.info("Profile {} on {} for {} set to priority {}".format(
+            profile, host_ad.droid.bluetoothGetLocalName(),
+            client_ad.droid.bluetoothGetLocalAddress(), priority.value))
+        if bt_profile_constants['a2dp_sink'] == profile:
+            host_ad.droid.bluetoothA2dpSinkSetPriority(
+                client_ad.droid.bluetoothGetLocalAddress(), priority.value)
+        elif bt_profile_constants['headset_client'] == profile:
+            host_ad.droid.bluetoothHfpClientSetPriority(
+                client_ad.droid.bluetoothGetLocalAddress(), priority.value)
+        elif bt_profile_constants['pbap_client'] == profile:
+            host_ad.droid.bluetoothPbapClientSetPriority(
+                client_ad.droid.bluetoothGetLocalAddress(), priority.value)
+        else:
+            host_ad.log.error(
+                "Profile {} not yet supported for priority settings".format(
+                    profile))
+
+
+def setup_multiple_devices_for_bt_test(android_devices):
+    """A common setup routine for Bluetooth on input Android device list.
+
+    Things this function sets up:
+    1. Resets Bluetooth
+    2. Set Bluetooth local name to random string of size 4
+    3. Disable BLE background scanning.
+    4. Enable Bluetooth snoop logging.
+
+    Args:
+        android_devices: Android device list to setup Bluetooth on.
+
+    Returns:
+        True if successful, false if unsuccessful.
+    """
+    log.info("Setting up Android Devices")
+    # TODO: Temp fix for an selinux error.
+    for ad in android_devices:
+        ad.adb.shell("setenforce 0")
+    threads = []
+    try:
+        for a in android_devices:
+            thread = threading.Thread(target=factory_reset_bluetooth,
+                                      args=([[a]]))
+            threads.append(thread)
+            thread.start()
+        for t in threads:
+            t.join()
+
+        for a in android_devices:
+            d = a.droid
+            # TODO: Create specific RPC command to instantiate
+            # BluetoothConnectionFacade. This is just a workaround.
+            d.bluetoothStartConnectionStateChangeMonitor("")
+            setup_result = d.bluetoothSetLocalName(generate_id_by_size(4))
+            if not setup_result:
+                a.log.error("Failed to set device name.")
+                return setup_result
+            d.bluetoothDisableBLE()
+            utils.set_location_service(a, True)
+            bonded_devices = d.bluetoothGetBondedDevices()
+            for b in bonded_devices:
+                a.log.info("Removing bond for device {}".format(b['address']))
+                d.bluetoothUnbond(b['address'])
+        for a in android_devices:
+            a.adb.shell("setprop persist.bluetooth.btsnooplogmode full")
+            getprop_result = a.adb.shell(
+                "getprop persist.bluetooth.btsnooplogmode") == "full"
+            if not getprop_result:
+                a.log.warning("Failed to enable Bluetooth Hci Snoop Logging.")
+    except Exception as err:
+        log.error("Something went wrong in multi device setup: {}".format(err))
+        return False
+    return setup_result
+
+
+def setup_n_advertisements(adv_ad, num_advertisements):
+    """Setup input number of advertisements on input Android device.
+
+    Args:
+        adv_ad: The Android device to start LE advertisements on.
+        num_advertisements: The number of advertisements to start.
+
+    Returns:
+        advertise_callback_list: List of advertisement callback ids.
+    """
+    adv_ad.droid.bleSetAdvertiseSettingsAdvertiseMode(
+        ble_advertise_settings_modes['low_latency'])
+    advertise_data = adv_ad.droid.bleBuildAdvertiseData()
+    advertise_settings = adv_ad.droid.bleBuildAdvertiseSettings()
+    advertise_callback_list = []
+    for i in range(num_advertisements):
+        advertise_callback = adv_ad.droid.bleGenBleAdvertiseCallback()
+        advertise_callback_list.append(advertise_callback)
+        adv_ad.droid.bleStartBleAdvertising(advertise_callback, advertise_data,
+                                            advertise_settings)
+        try:
+            adv_ad.ed.pop_event(adv_succ.format(advertise_callback),
+                                bt_default_timeout)
+            adv_ad.log.info("Advertisement {} started.".format(i + 1))
+        except Empty as error:
+            adv_ad.log.error("Advertisement {} failed to start.".format(i + 1))
+            raise BtTestUtilsError(
+                "Test failed with Empty error: {}".format(error))
+    return advertise_callback_list
+
+
+def take_btsnoop_log(ad, testcase, testname):
+    """Grabs the btsnoop_hci log on a device and stores it in the log directory
+    of the test class.
+
+    If you want grab the btsnoop_hci log, call this function with android_device
+    objects in on_fail. Bug report takes a relative long time to take, so use
+    this cautiously.
+
+    Args:
+        ad: The android_device instance to take bugreport on.
+        testcase: Name of the test calss that triggered this snoop log.
+        testname: Name of the test case that triggered this bug report.
+    """
+    testname = "".join(x for x in testname if x.isalnum())
+    serial = ad.serial
+    device_model = ad.droid.getBuildModel()
+    device_model = device_model.replace(" ", "")
+    out_name = ','.join((testname, device_model, serial))
+    snoop_path = os.path.join(ad.device_log_path, 'BluetoothSnoopLogs')
+    os.makedirs(snoop_path, exist_ok=True)
+    cmd = ''.join(("adb -s ", serial, " pull ", btsnoop_log_path_on_device,
+                   " ", snoop_path + '/' + out_name, ".btsnoop_hci.log"))
+    exe_cmd(cmd)
+    try:
+        cmd = ''.join(
+            ("adb -s ", serial, " pull ", btsnoop_last_log_path_on_device, " ",
+             snoop_path + '/' + out_name, ".btsnoop_hci.log.last"))
+        exe_cmd(cmd)
+    except Exception as err:
+        testcase.log.info(
+            "File does not exist {}".format(btsnoop_last_log_path_on_device))
+
+
+def take_btsnoop_logs(android_devices, testcase, testname):
+    """Pull btsnoop logs from an input list of android devices.
+
+    Args:
+        android_devices: the list of Android devices to pull btsnoop logs from.
+        testcase: Name of the test calss that triggered this snoop log.
+        testname: Name of the test case that triggered this bug report.
+    """
+    for a in android_devices:
+        take_btsnoop_log(a, testcase, testname)
+
+
+def teardown_n_advertisements(adv_ad, num_advertisements,
+                              advertise_callback_list):
+    """Stop input number of advertisements on input Android device.
+
+    Args:
+        adv_ad: The Android device to stop LE advertisements on.
+        num_advertisements: The number of advertisements to stop.
+        advertise_callback_list: The list of advertisement callbacks to stop.
+
+    Returns:
+        True if successful, false if unsuccessful.
+    """
+    for n in range(num_advertisements):
+        adv_ad.droid.bleStopBleAdvertising(advertise_callback_list[n])
+    return True
+
+
+def verify_server_and_client_connected(client_ad, server_ad, log=True):
+    """Verify that input server and client Android devices are connected.
+
+    This code is under the assumption that there will only be
+    a single connection.
+
+    Args:
+        client_ad: the Android device to check number of active connections.
+        server_ad: the Android device to check number of active connections.
+
+    Returns:
+        True both server and client have at least 1 active connection,
+        false if unsuccessful.
+    """
+    test_result = True
+    if len(server_ad.droid.bluetoothSocketConnActiveConnections()) == 0:
+        if log:
+            server_ad.log.error("No socket connections found on server.")
+        test_result = False
+    if len(client_ad.droid.bluetoothSocketConnActiveConnections()) == 0:
+        if log:
+            client_ad.log.error("No socket connections found on client.")
+        test_result = False
+    return test_result
+
+
+def wait_for_bluetooth_manager_state(droid,
+                                     state=None,
+                                     timeout=10,
+                                     threshold=5):
+    """ Waits for BlueTooth normalized state or normalized explicit state
+    args:
+        droid: droid device object
+        state: expected BlueTooth state
+        timeout: max timeout threshold
+        threshold: list len of bt state
+    Returns:
+        True if successful, false if unsuccessful.
+    """
+    all_states = []
+    get_state = lambda: droid.bluetoothGetLeState()
+    start_time = time.time()
+    while time.time() < start_time + timeout:
+        all_states.append(get_state())
+        if len(all_states) >= threshold:
+            # for any normalized state
+            if state is None:
+                if len(set(all_states[-threshold:])) == 1:
+                    log.info("State normalized {}".format(
+                        set(all_states[-threshold:])))
+                    return True
+            else:
+                # explicit check against normalized state
+                if set([state]).issubset(all_states[-threshold:]):
+                    return True
+        time.sleep(0.5)
+    log.error(
+        "Bluetooth state fails to normalize" if state is None else
+        "Failed to match bluetooth state, current state {} expected state {}".
+        format(get_state(), state))
+    return False
+
+
+def _wait_for_passkey_match(pri_ad, sec_ad):
+    pri_pin, sec_pin = -1, 1
+    pri_variant, sec_variant = -1, 1
+    pri_pairing_req, sec_pairing_req = None, None
+    try:
+        pri_pairing_req = pri_ad.ed.pop_event(
+            event_name="BluetoothActionPairingRequest",
+            timeout=bt_default_timeout)
+        pri_variant = pri_pairing_req["data"]["PairingVariant"]
+        pri_pin = pri_pairing_req["data"]["Pin"]
+        pri_ad.log.info("Primary device received Pin: {}, Variant: {}".format(
+            pri_pin, pri_variant))
+        sec_pairing_req = sec_ad.ed.pop_event(
+            event_name="BluetoothActionPairingRequest",
+            timeout=bt_default_timeout)
+        sec_variant = sec_pairing_req["data"]["PairingVariant"]
+        sec_pin = sec_pairing_req["data"]["Pin"]
+        sec_ad.log.info(
+            "Secondary device received Pin: {}, Variant: {}".format(
+                sec_pin, sec_variant))
+    except Empty as err:
+        log.error("Wait for pin error: {}".format(err))
+        log.error("Pairing request state, Primary: {}, Secondary: {}".format(
+            pri_pairing_req, sec_pairing_req))
+        return False
+    if pri_variant == sec_variant == pairing_variant_passkey_confirmation:
+        confirmation = pri_pin == sec_pin
+        if confirmation:
+            log.info("Pairing code matched, accepting connection")
+        else:
+            log.info("Pairing code mismatched, rejecting connection")
+        pri_ad.droid.eventPost("BluetoothActionPairingRequestUserConfirm",
+                               str(confirmation))
+        sec_ad.droid.eventPost("BluetoothActionPairingRequestUserConfirm",
+                               str(confirmation))
+        if not confirmation:
+            return False
+    elif pri_variant != sec_variant:
+        log.error("Pairing variant mismatched, abort connection")
+        return False
+    return True
+
+
+def write_read_verify_data(client_ad, server_ad, msg, binary=False):
+    """Verify that the client wrote data to the server Android device correctly.
+
+    Args:
+        client_ad: the Android device to perform the write.
+        server_ad: the Android device to read the data written.
+        msg: the message to write.
+        binary: if the msg arg is binary or not.
+
+    Returns:
+        True if the data written matches the data read, false if not.
+    """
+    client_ad.log.info("Write message.")
+    try:
+        if binary:
+            client_ad.droid.bluetoothSocketConnWriteBinary(msg)
+        else:
+            client_ad.droid.bluetoothSocketConnWrite(msg)
+    except Exception as err:
+        client_ad.log.error("Failed to write data: {}".format(err))
+        return False
+    server_ad.log.info("Read message.")
+    try:
+        if binary:
+            read_msg = server_ad.droid.bluetoothSocketConnReadBinary().rstrip(
+                "\r\n")
+        else:
+            read_msg = server_ad.droid.bluetoothSocketConnRead()
+    except Exception as err:
+        server_ad.log.error("Failed to read data: {}".format(err))
+        return False
+    log.info("Verify message.")
+    if msg != read_msg:
+        log.error("Mismatch! Read: {}, Expected: {}".format(read_msg, msg))
+        return False
+    return True
+
+
+class MediaControlOverSl4a(object):
+    """Media control using sl4a facade for general purpose.
+
+    """
+    def __init__(self, android_device, music_file):
+        """Initialize the media_control class.
+
+        Args:
+            android_dut: android_device object
+            music_file: location of the music file
+        """
+        self.android_device = android_device
+        self.music_file = music_file
+
+    def play(self):
+        """Play media.
+
+        """
+        self.android_device.droid.mediaPlayOpen('file://%s' % self.music_file,
+                                                'default', True)
+        playing = self.android_device.droid.mediaIsPlaying()
+        asserts.assert_true(playing,
+                            'Failed to play music %s' % self.music_file)
+
+    def pause(self):
+        """Pause media.
+
+        """
+        self.android_device.droid.mediaPlayPause('default')
+        paused = not self.android_device.droid.mediaIsPlaying()
+        asserts.assert_true(paused,
+                            'Failed to pause music %s' % self.music_file)
+
+    def resume(self):
+        """Resume media.
+
+        """
+        self.android_device.droid.mediaPlayStart('default')
+        playing = self.android_device.droid.mediaIsPlaying()
+        asserts.assert_true(playing,
+                            'Failed to play music %s' % self.music_file)
+
+    def stop(self):
+        """Stop media.
+
+        """
+        self.android_device.droid.mediaPlayStop('default')
+        stopped = not self.android_device.droid.mediaIsPlaying()
+        asserts.assert_true(stopped,
+                            'Failed to stop music %s' % self.music_file)
diff --git a/acts_tests/acts_contrib/test_utils/bt/bta_lib.py b/acts_tests/acts_contrib/test_utils/bt/bta_lib.py
new file mode 100644
index 0000000..6c3be78
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/bta_lib.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""
+Bluetooth adapter libraries
+"""
+
+from acts_contrib.test_utils.bt.bt_constants import bt_scan_mode_types
+from acts_contrib.test_utils.bt.bt_test_utils import set_bt_scan_mode
+
+import pprint
+
+
+class BtaLib():
+    def __init__(self, log, dut, target_mac_address=None):
+        self.advertisement_list = []
+        self.dut = dut
+        self.log = log
+        self.target_mac_addr = target_mac_address
+
+    def set_target_mac_addr(self, mac_addr):
+        self.target_mac_addr = mac_addr
+
+    def set_scan_mode(self, scan_mode):
+        """Set the Scan mode of the Bluetooth Adapter"""
+        set_bt_scan_mode(self.dut, bt_scan_mode_types[scan_mode])
+
+    def set_device_name(self, line):
+        """Set Bluetooth Adapter Name"""
+        self.dut.droid.bluetoothSetLocalName(line)
+
+    def enable(self):
+        """Enable Bluetooth Adapter"""
+        self.dut.droid.bluetoothToggleState(True)
+
+    def disable(self):
+        """Disable Bluetooth Adapter"""
+        self.dut.droid.bluetoothToggleState(False)
+
+    def init_bond(self):
+        """Initiate bond to PTS device"""
+        self.dut.droid.bluetoothDiscoverAndBond(self.target_mac_addr)
+
+    def start_discovery(self):
+        """Start BR/EDR Discovery"""
+        self.dut.droid.bluetoothStartDiscovery()
+
+    def stop_discovery(self):
+        """Stop BR/EDR Discovery"""
+        self.dut.droid.bluetoothCancelDiscovery()
+
+    def get_discovered_devices(self):
+        """Get Discovered Br/EDR Devices"""
+        if self.dut.droid.bluetoothIsDiscovering():
+            self.dut.droid.bluetoothCancelDiscovery()
+        self.log.info(
+            pprint.pformat(self.dut.droid.bluetoothGetDiscoveredDevices()))
+
+    def bond(self):
+        """Bond to PTS device"""
+        self.dut.droid.bluetoothBond(self.target_mac_addr)
+
+    def disconnect(self):
+        """BTA disconnect"""
+        self.dut.droid.bluetoothDisconnectConnected(self.target_mac_addr)
+
+    def unbond(self):
+        """Unbond from PTS device"""
+        self.dut.droid.bluetoothUnbond(self.target_mac_addr)
+
+    def start_pairing_helper(self, line):
+        """Start or stop Bluetooth Pairing Helper"""
+        if line:
+            self.dut.droid.bluetoothStartPairingHelper(bool(line))
+        else:
+            self.dut.droid.bluetoothStartPairingHelper()
+
+    def push_pairing_pin(self, line):
+        """Push pairing pin to the Android Device"""
+        self.dut.droid.eventPost("BluetoothActionPairingRequestUserConfirm",
+                                 line)
+
+    def get_pairing_pin(self):
+        """Get pairing PIN"""
+        self.log.info(
+            self.dut.ed.pop_event("BluetoothActionPairingRequest", 1))
+
+    def fetch_uuids_with_sdp(self):
+        """BTA fetch UUIDS with SDP"""
+        self.log.info(
+            self.dut.droid.bluetoothFetchUuidsWithSdp(self.target_mac_addr))
+
+    def connect_profiles(self):
+        """Connect available profiles"""
+        self.dut.droid.bluetoothConnectBonded(self.target_mac_addr)
+
+    def tts_speak(self):
+        """Open audio channel by speaking characters"""
+        self.dut.droid.ttsSpeak(self.target_mac_addr)
diff --git a/acts_tests/acts_contrib/test_utils/bt/config_lib.py b/acts_tests/acts_contrib/test_utils/bt/config_lib.py
new file mode 100644
index 0000000..2926243
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/config_lib.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""
+Bluetooth Config Pusher
+"""
+
+from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_mtu
+from acts_contrib.test_utils.bt.bt_gatt_utils import log_gatt_server_uuids
+
+import time
+import os
+
+
+class ConfigLib():
+    bluetooth_config_path = "/system/etc/bluetooth/bt_stack.conf"
+    conf_path = "{}/configs".format(
+        os.path.dirname(os.path.realpath(__file__)))
+    reset_config_path = "{}/bt_stack.conf".format(conf_path)
+    non_bond_config_path = "{}/non_bond_bt_stack.conf".format(conf_path)
+    disable_mitm_config_path = "{}/dis_mitm_bt_stack.conf".format(conf_path)
+
+    def __init__(self, log, dut):
+        self.dut = dut
+        self.log = log
+
+    def _reset_bluetooth(self):
+        self.dut.droid.bluetoothToggleState(False)
+        self.dut.droid.bluetoothToggleState(True)
+
+    def reset(self):
+        self.dut.adb.push("{} {}".format(self.reset_config_path,
+                                         self.bluetooth_config_path))
+        self._reset_bluetooth()
+
+    def set_nonbond(self):
+        self.dut.adb.push("{} {}".format(self.non_bond_config_path,
+                                         self.bluetooth_config_path))
+        self._reset_bluetooth()
+
+    def set_disable_mitm(self):
+        self.dut.adb.push("{} {}".format(self.disable_mitm_config_path,
+                                         self.bluetooth_config_path))
+        self._reset_bluetooth()
diff --git a/acts_tests/acts_contrib/test_utils/bt/configs/bt_stack.conf b/acts_tests/acts_contrib/test_utils/bt/configs/bt_stack.conf
new file mode 100644
index 0000000..4bcf15a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/configs/bt_stack.conf
@@ -0,0 +1,29 @@
+# Enable trace level reconfiguration function
+# Must be present before any TRC_ trace level settings
+TraceConf=true
+
+# Trace level configuration
+#   BT_TRACE_LEVEL_NONE    0    ( No trace messages to be generated )
+#   BT_TRACE_LEVEL_ERROR   1    ( Error condition trace messages )
+#   BT_TRACE_LEVEL_WARNING 2    ( Warning condition trace messages )
+#   BT_TRACE_LEVEL_API     3    ( API traces )
+#   BT_TRACE_LEVEL_EVENT   4    ( Debug messages for events )
+#   BT_TRACE_LEVEL_DEBUG   5    ( Full debug messages )
+#   BT_TRACE_LEVEL_VERBOSE 6    ( Verbose messages ) - Currently supported for TRC_BTAPP only.
+TRC_BTM=5
+TRC_HCI=5
+TRC_L2CAP=5
+TRC_RFCOMM=5
+TRC_OBEX=5
+TRC_AVCT=5
+TRC_AVDT=5
+TRC_AVRC=5
+TRC_AVDT_SCB=5
+TRC_AVDT_CCB=5
+TRC_A2D=2
+TRC_SDP=5
+TRC_GATT=5
+TRC_SMP=5
+TRC_BTAPP=5
+TRC_BTIF=5
+
diff --git a/acts_tests/acts_contrib/test_utils/bt/configs/dis_mitm_bt_stack.conf b/acts_tests/acts_contrib/test_utils/bt/configs/dis_mitm_bt_stack.conf
new file mode 100644
index 0000000..120fc1e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/configs/dis_mitm_bt_stack.conf
@@ -0,0 +1,30 @@
+# Enable trace level reconfiguration function
+# Must be present before any TRC_ trace level settings
+TraceConf=true
+
+# Trace level configuration
+#   BT_TRACE_LEVEL_NONE    0    ( No trace messages to be generated )
+#   BT_TRACE_LEVEL_ERROR   1    ( Error condition trace messages )
+#   BT_TRACE_LEVEL_WARNING 2    ( Warning condition trace messages )
+#   BT_TRACE_LEVEL_API     3    ( API traces )
+#   BT_TRACE_LEVEL_EVENT   4    ( Debug messages for events )
+#   BT_TRACE_LEVEL_DEBUG   5    ( Full debug messages )
+#   BT_TRACE_LEVEL_VERBOSE 6    ( Verbose messages ) - Currently supported for TRC_BTAPP only.
+TRC_BTM=5
+TRC_HCI=5
+TRC_L2CAP=5
+TRC_RFCOMM=5
+TRC_OBEX=5
+TRC_AVCT=5
+TRC_AVDT=5
+TRC_AVRC=5
+TRC_AVDT_SCB=5
+TRC_AVDT_CCB=5
+TRC_A2D=2
+TRC_SDP=5
+TRC_GATT=5
+TRC_SMP=5
+TRC_BTAPP=5
+TRC_BTIF=5
+
+PTS_SmpOptions=0x9,0x4,0xf,0xf,0x10
diff --git a/acts_tests/acts_contrib/test_utils/bt/configs/non_bond_bt_stack.conf b/acts_tests/acts_contrib/test_utils/bt/configs/non_bond_bt_stack.conf
new file mode 100644
index 0000000..3dedf7e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/configs/non_bond_bt_stack.conf
@@ -0,0 +1,30 @@
+# Enable trace level reconfiguration function
+# Must be present before any TRC_ trace level settings
+TraceConf=true
+
+# Trace level configuration
+#   BT_TRACE_LEVEL_NONE    0    ( No trace messages to be generated )
+#   BT_TRACE_LEVEL_ERROR   1    ( Error condition trace messages )
+#   BT_TRACE_LEVEL_WARNING 2    ( Warning condition trace messages )
+#   BT_TRACE_LEVEL_API     3    ( API traces )
+#   BT_TRACE_LEVEL_EVENT   4    ( Debug messages for events )
+#   BT_TRACE_LEVEL_DEBUG   5    ( Full debug messages )
+#   BT_TRACE_LEVEL_VERBOSE 6    ( Verbose messages ) - Currently supported for TRC_BTAPP only.
+TRC_BTM=5
+TRC_HCI=5
+TRC_L2CAP=5
+TRC_RFCOMM=5
+TRC_OBEX=5
+TRC_AVCT=5
+TRC_AVDT=5
+TRC_AVRC=5
+TRC_AVDT_SCB=5
+TRC_AVDT_CCB=5
+TRC_A2D=2
+TRC_SDP=5
+TRC_GATT=5
+TRC_SMP=5
+TRC_BTAPP=5
+TRC_BTIF=5
+
+PTS_SmpOptions=0xC,0x4,0xf,0xf,0x10
diff --git a/acts_tests/acts_contrib/test_utils/bt/gatt_test_database.py b/acts_tests/acts_contrib/test_utils/bt/gatt_test_database.py
new file mode 100644
index 0000000..8c4f54c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/gatt_test_database.py
@@ -0,0 +1,1705 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_service_types
+from acts_contrib.test_utils.bt.bt_constants import gatt_char_types
+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
+
+STRING_512BYTES = '''
+11111222223333344444555556666677777888889999900000
+11111222223333344444555556666677777888889999900000
+11111222223333344444555556666677777888889999900000
+11111222223333344444555556666677777888889999900000
+11111222223333344444555556666677777888889999900000
+11111222223333344444555556666677777888889999900000
+11111222223333344444555556666677777888889999900000
+11111222223333344444555556666677777888889999900000
+11111222223333344444555556666677777888889999900000
+11111222223333344444555556666677777888889999900000
+111112222233
+'''
+STRING_50BYTES = '''
+11111222223333344444555556666677777888889999900000
+'''
+STRING_25BYTES = '''
+1111122222333334444455555
+'''
+
+INVALID_SMALL_DATABASE = {
+    'services': [{
+        'uuid': '00001800-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'characteristics': [{
+            'uuid': gatt_char_types['device_name'],
+            'properties': gatt_characteristic['property_read'],
+            'permissions': gatt_characteristic['permission_read'],
+            'instance_id': 0x0003,
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': 'Test Database'
+        }, {
+            'uuid': gatt_char_types['appearance'],
+            'properties': gatt_characteristic['property_read'],
+            'permissions': gatt_characteristic['permission_read'],
+            'instance_id': 0x0005,
+            'value_type': gatt_characteristic_value_format['sint32'],
+            'offset': 0,
+            'value': 17
+        }, {
+            'uuid': gatt_char_types['peripheral_pref_conn'],
+            'properties': gatt_characteristic['property_read'],
+            'permissions': gatt_characteristic['permission_read'],
+            'instance_id': 0x0007
+        }]
+    }, {
+        'uuid': '00001801-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'characteristics': [{
+            'uuid': gatt_char_types['service_changed'],
+            'properties': gatt_characteristic['property_indicate'],
+            'permissions': gatt_characteristic['permission_read'] |
+            gatt_characteristic['permission_write'],
+            'instance_id': 0x0012,
+            'value_type': gatt_characteristic_value_format['byte'],
+            'value': [0x0000],
+            'descriptors': [{
+                'uuid': gatt_char_desc_uuids['client_char_cfg'],
+                'permissions': gatt_descriptor['permission_read'] |
+                gatt_descriptor['permission_write'],
+            }]
+        }, {
+            'uuid': '0000b004-0000-1000-8000-00805f9b34fb',
+            'properties': gatt_characteristic['property_read'],
+            'permissions': gatt_characteristic['permission_read'],
+            'instance_id': 0x0015,
+            'value_type': gatt_characteristic_value_format['byte'],
+            'value': [0x04]
+        }]
+    }]
+}
+
+# Corresponds to the PTS defined LARGE_DB_1
+LARGE_DB_1 = {
+    'services': [
+        {
+            'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'handles': 7,
+            'characteristics': [{
+                'uuid': '0000b008-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'] |
+                gatt_characteristic['property_extended_props'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x08],
+                'descriptors': [{
+                    'uuid': '0000b015-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                }, {
+                    'uuid': '0000b016-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                }, {
+                    'uuid': '0000b017-0000-1000-8000-00805f9b34fb',
+                    'permissions':
+                    gatt_characteristic['permission_read_encrypted_mitm'],
+                }]
+            }]
+        },
+        {
+            'uuid': '0000a00d-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['secondary'],
+            'handles': 6,
+            'characteristics': [{
+                'uuid': '0000b00c-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_extended_props'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x0C],
+            }, {
+                'uuid': '0000b00b-0000-0000-0123-456789abcdef',
+                'properties': gatt_characteristic['property_extended_props'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x0B],
+            }]
+        },
+        {
+            'uuid': '0000a00a-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'handles': 10,
+            'characteristics': [{
+                'uuid': '0000b001-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x01],
+            }, {
+                'uuid': '0000b002-0000-0000-0123-456789abcdef',
+                'properties': gatt_characteristic['property_extended_props'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            }, {
+                'uuid': '0000b004-0000-0000-0123-456789abcdef',
+                'properties': gatt_characteristic['property_read'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            }, {
+                'uuid': '0000b002-0000-0000-0123-456789abcdef',
+                'properties': gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': '11111222223333344444555556666677777888889999900000',
+            }, {
+                'uuid': '0000b003-0000-0000-0123-456789abcdef',
+                'properties': gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x03],
+            }]
+        },
+        {
+            'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'handles': 3,
+            'characteristics': [{
+                'uuid': '0000b007-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x07],
+            }]
+        },
+        {
+            'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'handles': 3,
+            'characteristics': [{
+                'uuid': '0000b006-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'] |
+                gatt_characteristic['property_write_no_response'] |
+                gatt_characteristic['property_notify'] |
+                gatt_characteristic['property_indicate'],
+                'permissions': gatt_characteristic['permission_write'] |
+                gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x06],
+            }]
+        },
+        {
+            'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'handles': 12,
+            'characteristics': [
+                {
+                    'uuid': '0000b004-0000-1000-8000-00805f9b34fb',
+                    'properties': gatt_characteristic['property_read'] |
+                    gatt_characteristic['property_write'],
+                    'permissions': gatt_characteristic['permission_write'] |
+                    gatt_characteristic['permission_read'],
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x04],
+                },
+                {
+                    'uuid': '0000b004-0000-1000-8000-00805f9b34fb',
+                    'properties': gatt_characteristic['property_read'] |
+                    gatt_characteristic['property_write'],
+                    'permissions': gatt_characteristic['permission_write'] |
+                    gatt_characteristic['permission_read'],
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x04],
+                    'descriptors': [{
+                        'uuid': gatt_char_desc_uuids['server_char_cfg'],
+                        'permissions': gatt_descriptor['permission_read'] |
+                        gatt_descriptor['permission_write'],
+                        'value': gatt_descriptor['disable_notification_value']
+                    }]
+                },
+                {
+                    'uuid': '0000b004-0000-1000-8000-00805f9b34fb',
+                    'properties': 0x0,
+                    'permissions': 0x0,
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x04],
+                    'descriptors': [{
+                        'uuid': '0000b012-0000-1000-8000-00805f9b34fb',
+                        'permissions': gatt_descriptor['permission_read'] |
+                        gatt_descriptor['permission_write'],
+                        'value': [
+                            0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
+                            0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                            0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44,
+                            0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22,
+                            0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                            0x11, 0x22, 0x33
+                        ]
+                    }]
+                },
+                {
+                    'uuid': '0000b004-0000-1000-8000-00805f9b34fb',
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'],
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x04],
+                    'descriptors': [{
+                        'uuid': '0000b012-0000-1000-8000-00805f9b34fb',
+                        'permissions': gatt_descriptor['permission_read'],
+                        'value': [
+                            0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
+                            0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                            0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44,
+                            0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22,
+                            0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                            0x11, 0x22, 0x33
+                        ]
+                    }]
+                },
+            ]
+        },
+        {
+            'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'handles': 7,
+            'characteristics': [{
+                'uuid': '0000b005-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_write'] |
+                gatt_characteristic['property_extended_props'],
+                'permissions': gatt_characteristic['permission_write'] |
+                gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x05],
+                'descriptors': [{
+                    'uuid': gatt_char_desc_uuids['char_ext_props'],
+                    'permissions': gatt_descriptor['permission_read'],
+                    'value': [0x03, 0x00]
+                }, {
+                    'uuid': gatt_char_desc_uuids['char_user_desc'],
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [
+                        0x65, 0x66, 0x67, 0x68, 0x69, 0x70, 0x71, 0x72, 0x73,
+                        0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x80, 0x81, 0x82,
+                        0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x90
+                    ]
+                }, {
+                    'uuid': gatt_char_desc_uuids['char_fmt_uuid'],
+                    'permissions':
+                    gatt_descriptor['permission_read_encrypted_mitm'],
+                    'value': [0x00, 0x01, 0x30, 0x01, 0x11, 0x31]
+                }, {
+                    'uuid': '0000d5d4-0000-0000-0123-456789abcdef',
+                    'permissions': gatt_descriptor['permission_read'],
+                    'value': [0x44]
+                }]
+            }]
+        },
+        {
+            'uuid': '0000a00c-0000-0000-0123-456789abcdef',
+            'type': gatt_service_types['primary'],
+            'handles': 7,
+            'characteristics': [{
+                'uuid': '0000b009-0000-0000-0123-456789abcdef',
+                'enforce_initial_attribute_length': True,
+                'properties': gatt_characteristic['property_write'] |
+                gatt_characteristic['property_extended_props'] |
+                gatt_characteristic['property_read'],
+                'permissions': gatt_characteristic['permission_write'] |
+                gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x09],
+                'descriptors': [{
+                    'uuid': gatt_char_desc_uuids['char_ext_props'],
+                    'permissions': gatt_descriptor['permission_read'],
+                    'value': gatt_descriptor['enable_notification_value']
+                }, {
+                    'uuid': '0000d9d2-0000-0000-0123-456789abcdef',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [0x22]
+                }, {
+                    'uuid': '0000d9d3-0000-0000-0123-456789abcdef',
+                    'permissions': gatt_descriptor['permission_write'],
+                    'value': [0x33]
+                }]
+            }]
+        },
+        {
+            'uuid': '0000a00f-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'handles': 18,
+            'characteristics': [
+                {
+                    'uuid': '0000b00e-0000-1000-8000-00805f9b34fb',
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': "Length is ",
+                    'descriptors': [{
+                        'uuid': gatt_char_desc_uuids['char_fmt_uuid'],
+                        'permissions': gatt_descriptor['permission_read'],
+                        'value': [0x19, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00]
+                    }]
+                },
+                {
+                    'uuid': '0000b00f-0000-1000-8000-00805f9b34fb',
+                    'enforce_initial_attribute_length': True,
+                    'properties': gatt_characteristic['property_read'] |
+                    gatt_characteristic['property_write'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x65],
+                    'descriptors': [{
+                        'uuid': gatt_char_desc_uuids['char_fmt_uuid'],
+                        'permissions': gatt_descriptor['permission_read'],
+                        'value': [0x04, 0x00, 0x01, 0x27, 0x01, 0x01, 0x00]
+                    }]
+                },
+                {
+                    'uuid': '0000b006-0000-1000-8000-00805f9b34fb',
+                    'properties': gatt_characteristic['property_read'] |
+                    gatt_characteristic['property_write'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x34, 0x12],
+                    'descriptors': [{
+                        'uuid': gatt_char_desc_uuids['char_fmt_uuid'],
+                        'permissions': gatt_descriptor['permission_read'],
+                        'value': [0x06, 0x00, 0x10, 0x27, 0x01, 0x02, 0x00]
+                    }]
+                },
+                {
+                    'uuid': '0000b007-0000-1000-8000-00805f9b34fb',
+                    'enforce_initial_attribute_length': True,
+                    'properties': gatt_characteristic['property_read'] |
+                    gatt_characteristic['property_write'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x04, 0x03, 0x02, 0x01],
+                    'descriptors': [{
+                        'uuid': gatt_char_desc_uuids['char_fmt_uuid'],
+                        'permissions': gatt_descriptor['permission_read'],
+                        'value': [0x08, 0x00, 0x17, 0x27, 0x01, 0x03, 0x00]
+                    }]
+                },
+                {
+                    'uuid': '0000b010-0000-1000-8000-00805f9b34fb',
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'],
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x65, 0x34, 0x12, 0x04, 0x03, 0x02, 0x01],
+                    'descriptors': [{
+                        'uuid': gatt_char_desc_uuids['char_agreg_fmt'],
+                        'permissions': gatt_descriptor['permission_read'],
+                        'value': [0xa6, 0x00, 0xa9, 0x00, 0xac, 0x00]
+                    }]
+                },
+                {
+                    'uuid': '0000b011-0000-1000-8000-00805f9b34fb',
+                    'properties': gatt_characteristic['write_type_signed']
+                    |  #for some reason 0x40 is not working...
+                    gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x12]
+                }
+            ]
+        },
+        {
+            'uuid': '0000a00c-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'handles': 30,
+            'characteristics': [{
+                'uuid': '0000b00a-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x0a],
+            }, {
+                'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': "111112222233333444445",
+                'descriptors': [{
+                    'uuid': '0000b012-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [
+                        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
+                        0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x11
+                    ]
+                }]
+            }, {
+                'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': "2222233333444445555566",
+                'descriptors': [{
+                    'uuid': '0000b013-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [
+                        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
+                        0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x11, 0x22
+                    ]
+                }]
+            }, {
+                'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': "33333444445555566666777",
+                'descriptors': [{
+                    'uuid': '0000b014-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [
+                        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
+                        0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x11, 0x22, 0x33
+                    ]
+                }]
+            }, {
+                'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33
+                ],
+                'descriptors': [{
+                    'uuid': '0000b012-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [
+                        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
+                        0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+                        0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34,
+                        0x56, 0x78, 0x90, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                        0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33
+                    ]
+                }]
+            }, {
+                'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44
+                ],
+                'descriptors': [{
+                    'uuid': '0000b013-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [
+                        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
+                        0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+                        0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34,
+                        0x56, 0x78, 0x90, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                        0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44
+                    ]
+                }]
+            }, {
+                'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x11, 0x22, 0x33, 0x44, 0x55
+                ],
+                'descriptors': [{
+                    'uuid': '0000b014-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [
+                        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
+                        0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+                        0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34,
+                        0x56, 0x78, 0x90, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                        0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55
+                    ]
+                }]
+            }, {
+                'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': "1111122222333334444455555666667777788888999",
+                'descriptors': [{
+                    'uuid': '0000b012-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [
+                        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
+                        0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+                        0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34,
+                        0x56, 0x78, 0x90, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                        0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33
+                    ]
+                }]
+            }, {
+                'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': "22222333334444455555666667777788888999990000",
+                'descriptors': [{
+                    'uuid': '0000b013-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [
+                        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
+                        0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+                        0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34,
+                        0x56, 0x78, 0x90, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                        0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44
+                    ]
+                }]
+            }, {
+                'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                'properties': gatt_characteristic['property_read'] |
+                gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': "333334444455555666667777788888999990000011111",
+                'descriptors': [{
+                    'uuid': '0000b014-0000-1000-8000-00805f9b34fb',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [
+                        0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99,
+                        0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56,
+                        0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34,
+                        0x56, 0x78, 0x90, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66,
+                        0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55
+                    ]
+                }]
+            }]
+        },
+    ]
+}
+
+# Corresponds to the PTS defined LARGE_DB_2
+LARGE_DB_2 = {
+    'services': [
+        {
+            'uuid': '0000a00c-0000-0000-0123-456789abdcef',
+            'type': gatt_service_types['primary'],
+            'characteristics': [{
+                'uuid': '0000b00a-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0003,
+                'properties': gatt_characteristic['property_read'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x04],
+            }, {
+                'uuid': '0000b0002-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0005,
+                'properties': 0x0a,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': '111112222233333444445',
+            }, {
+                'uuid': '0000b0002-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0007,
+                'properties': 0x0a,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': '2222233333444445555566',
+            }, {
+                'uuid': '0000b0002-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0009,
+                'properties': 0x0a,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': '33333444445555566666777',
+            }, {
+                'uuid': '0000b0002-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x000b,
+                'properties': 0x0a0,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': '1111122222333334444455555666667777788888999',
+            }, {
+                'uuid': '0000b0002-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x000d,
+                'properties': 0x0a,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': '22222333334444455555666667777788888999990000',
+            }, {
+                'uuid': '0000b0002-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x000f,
+                'properties': 0x0a,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': '333334444455555666667777788888999990000011111',
+            }]
+        },
+        {
+            'uuid': '0000a00c-0000-0000-0123-456789abcdef',
+            'handles': 5,
+            'type': gatt_service_types['primary'],
+            'characteristics': [{
+                'uuid': '0000b009-0000-0000-0123-456789abcdef',
+                'instance_id': 0x0023,
+                'properties': 0x8a,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x09],
+                'descriptors': [{
+                    'uuid': '0000d9d2-0000-0000-0123-456789abcdef',
+                    'permissions': gatt_descriptor['permission_read'] |
+                    gatt_descriptor['permission_write'],
+                    'value': [0x22]
+                }, {
+                    'uuid': '0000d9d3-0000-0000-0123-456789abcdef',
+                    'permissions': gatt_descriptor['permission_write'],
+                    'value': [0x33]
+                }, {
+                    'uuid': gatt_char_desc_uuids['char_ext_props'],
+                    'permissions': gatt_descriptor['permission_write'],
+                    'value': gatt_descriptor['enable_notification_value']
+                }]
+            }]
+        },
+        {
+            'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'characteristics': [{
+                'uuid': '0000b007-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0012,
+                'properties': 0x0a,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x04],
+            }]
+        },
+    ]
+}
+
+DB_TEST = {
+    'services': [{
+        'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'characteristics': [{
+            'uuid': '0000b004-0000-1000-8000-00805f9b34fb',
+            'properties': 0x02 | 0x08,
+            'permissions': 0x10 | 0x01,
+            'value_type': gatt_characteristic_value_format['byte'],
+            'value': [0x01],
+            'enforce_initial_attribute_length': True,
+            'descriptors': [{
+                'uuid': '0000b004-0000-1000-8000-00805f9b34fb',
+                'permissions': gatt_descriptor['permission_read'] |
+                gatt_descriptor['permission_write'],
+                'value': [0x01] * 30
+            }]
+        }, ]
+    }]
+}
+
+PTS_TEST2 = {
+    'services': [{
+        'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'characteristics': [
+            {
+                'uuid': '000018ba-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000060aa-0000-0000-0123-456789abcdef',
+                'properties': 0x02,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '00000af2-0000-1000-8000-00805f9b34fb',
+                'properties': 0x20,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000000af2-0000-1000-8000-00805f9b34fb',
+                'properties': 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000004d5e-0000-1000-8000-00805f9b34fb',
+                'properties': 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000001b44-0000-1000-8000-00805f9b34fb',
+                'properties': 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000006b98-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08 | 0x10 | 0x04,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '00000247f-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '00000247f-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '00000247f-0000-1000-8000-00805f9b34fb',
+                'properties': 0x00,
+                'permissions': 0x00,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '00000247f-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000000d62-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08 | 0x80,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000002e85-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000004a64-0000-0000-0123-456789abcdef',
+                'properties': 0x02 | 0x08 | 0x80,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000005b4a-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000001c81-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000006b98-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000001b44-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000000c55-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '0000014dd-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000000c55-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000000c55-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000000c55-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000000c55-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '00000008f-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02,
+                'permissions': 0x10,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000000af2-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x32
+                ],
+            },
+            {
+                'uuid': '000000af2-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x32
+                ],
+            },
+            {
+                'uuid': '000000af2-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x32
+                ],
+            },
+            {
+                'uuid': '000000af2-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x32
+                ],
+            },
+            {
+                'uuid': '000000af2-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x32
+                ],
+            },
+            {
+                'uuid': '000000af2-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x32
+                ],
+            },
+            {
+                'uuid': '000000af2-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [
+                    0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00,
+                    0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x32
+                ],
+            },
+            {
+                'uuid': '000002aad-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000002ab0-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+            {
+                'uuid': '000002ab3-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_512BYTES,
+            },
+        ]
+    }]
+}
+
+PTS_TEST = {
+    'services': [{
+        'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'characteristics': [
+            {
+                'uuid': '000018ba-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_25BYTES,
+            },
+            {
+                'uuid': '000060aa-0000-1000-8000-00805f9b34fb',
+                'properties': 0x02 | 0x08,
+                'permissions': 0x10 | 0x01,
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': STRING_25BYTES,
+            },
+        ]
+    }]
+}
+
+# Corresponds to the PTS defined LARGE_DB_3
+LARGE_DB_3 = {
+    'services': [
+        {
+            'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'characteristics': [
+                {
+                    'uuid': '0000b004-0000-1000-8000-00805f9b34fb',
+                    'instance_id': 0x0003,
+                    'properties': 0x0a,
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x04],
+                },
+                {
+                    'uuid': '0000b004-0000-1000-8000-00805f9b34fb',
+                    'instance_id': 0x0013,
+                    'properties': 0x10,
+                    'permissions': 0x17,
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x04],
+                    'descriptors': [
+                        {
+                            'uuid': gatt_char_desc_uuids['char_ext_props'],
+                            'permissions': gatt_descriptor['permission_read'] |
+                            gatt_descriptor['permission_write'],
+                            'value': [0x09]
+                        },
+                        {
+                            'uuid': gatt_char_desc_uuids['char_user_desc'],
+                            'permissions': gatt_descriptor['permission_read'] |
+                            gatt_descriptor['permission_write'],
+                            'value': [0x22]
+                        },
+                        {
+                            'uuid': gatt_char_desc_uuids['client_char_cfg'],
+                            'permissions': gatt_descriptor['permission_read'] |
+                            gatt_descriptor['permission_write'],
+                            'value': [0x01, 0x00]
+                        },
+                        {
+                            'uuid': gatt_char_desc_uuids['server_char_cfg'],
+                            'permissions': gatt_descriptor['permission_read'] |
+                            gatt_descriptor['permission_write'],
+                            'value': [0x22]
+                        },
+                        {
+                            'uuid': gatt_char_desc_uuids['char_fmt_uuid'],
+                            'permissions': gatt_descriptor['permission_read'] |
+                            gatt_descriptor['permission_write'],
+                            'value': [0x22]
+                        },
+                        {
+                            'uuid': gatt_char_desc_uuids['char_agreg_fmt'],
+                            'permissions': gatt_descriptor['permission_read'] |
+                            gatt_descriptor['permission_write'],
+                            'value': [0x22]
+                        },
+                        {
+                            'uuid': gatt_char_desc_uuids['char_valid_range'],
+                            'permissions': gatt_descriptor['permission_read'] |
+                            gatt_descriptor['permission_write'],
+                            'value': [0x22]
+                        },
+                        {
+                            'uuid':
+                            gatt_char_desc_uuids['external_report_reference'],
+                            'permissions': gatt_descriptor['permission_read'] |
+                            gatt_descriptor['permission_write'],
+                            'value': [0x22]
+                        },
+                        {
+                            'uuid': gatt_char_desc_uuids['report_reference'],
+                            'permissions': gatt_descriptor['permission_read'] |
+                            gatt_descriptor['permission_write'],
+                            'value': [0x22]
+                        },
+                    ]
+                },
+                {
+                    'uuid': gatt_char_types['service_changed'],
+                    'instance_id': 0x0023,
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['appearance'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['peripheral_priv_flag'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['reconnection_address'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['system_id'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['model_number_string'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['serial_number_string'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['firmware_revision_string'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['hardware_revision_string'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['software_revision_string'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['manufacturer_name_string'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+                {
+                    'uuid': gatt_char_types['pnp_id'],
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+            ]
+        },
+        {
+            'uuid': '0000a00d-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['secondary'],
+            'handles': 5,
+            'characteristics': [{
+                'uuid': '0000b00c-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0023,
+                'properties': gatt_characteristic['property_read'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x0c],
+            }, {
+                'uuid': '0000b00b-0000-0000-0123-456789abcdef',
+                'instance_id': 0x0025,
+                'properties': gatt_characteristic['property_read'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x0b],
+            }]
+        },
+        {
+            'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'characteristics': [{
+                'uuid': '0000b008-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0032,
+                'properties': gatt_characteristic['property_read'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x08],
+            }]
+        },
+        {
+            'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'characteristics': [{
+                'uuid': '0000b007-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0042,
+                'properties': gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x07],
+            }]
+        },
+        {
+            'uuid': '0000a00b-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'characteristics': [{
+                'uuid': '0000b006-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0052,
+                'properties': 0x3e,
+                'permissions': gatt_characteristic['permission_write_encrypted_mitm'] |
+                gatt_characteristic['permission_read_encrypted_mitm'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x06],
+            }]
+        },
+        {
+            'uuid': '0000a00a-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'handles': 10,
+            'characteristics': [{
+                'uuid': '0000b001-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0074,
+                'properties': gatt_characteristic['property_read'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x01],
+            }, {
+                'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                'enforce_initial_attribute_length': True,
+                'instance_id': 0x0076,
+                'properties': 0x0a,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['string'],
+                'value': '11111222223333344444555556666677777888889999900000',
+            }, {
+                'uuid': '0000b003-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0x0078,
+                'properties': gatt_characteristic['property_write'],
+                'permissions': gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x03],
+            }]
+        },
+        {
+            'uuid': '0000a00c-0000-0000-0123-456789abcdef',
+            'type': gatt_service_types['primary'],
+            'handles': 10,
+            'characteristics': [{
+                'uuid': '0000b009-0000-0000-0123-456789abcdef',
+                'instance_id': 0x0082,
+                'properties': 0x8a,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x09],
+                'descriptors': [
+                    {
+                        'uuid': '0000b009-0000-0000-0123-456789abcdef',
+                        'permissions': gatt_descriptor['permission_read'] |
+                        gatt_descriptor['permission_write'],
+                        'value': [0x09]
+                    },
+                    {
+                        'uuid': '0000d9d2-0000-0000-0123-456789abcdef',
+                        'permissions': gatt_descriptor['permission_read'] |
+                        gatt_descriptor['permission_write'],
+                        'value': [0x22]
+                    },
+                    {
+                        'uuid': gatt_char_desc_uuids['char_ext_props'],
+                        'permissions': gatt_descriptor['permission_read'],
+                        'value': [0x01, 0x00]
+                    },
+                    {
+                        'uuid': '0000d9d3-0000-0000-0123-456789abcdef',
+                        'permissions': gatt_descriptor['permission_write'],
+                        'value': [0x22]
+                    },
+                ]
+            }]
+        },
+        {
+            'uuid': '0000a00b-0000-0000-0123-456789abcdef',
+            'type': gatt_service_types['primary'],
+            'characteristics': [{
+                'uuid': '0000b009-0000-0000-0123-456789abcdef',
+                'instance_id': 0x0092,
+                'properties': 0x8a,
+                'permissions': gatt_characteristic['permission_read'] |
+                gatt_characteristic['permission_write'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x05],
+                'descriptors': [
+                    {
+                        'uuid': gatt_char_desc_uuids['char_user_desc'],
+                        'permissions': gatt_descriptor['permission_read'] |
+                        gatt_descriptor['permission_write'],
+                        'value': [0] * 26
+                    },
+                    {
+                        'uuid': gatt_char_desc_uuids['char_ext_props'],
+                        'permissions': gatt_descriptor['permission_read'],
+                        'value': [0x03, 0x00]
+                    },
+                    {
+                        'uuid': '0000d5d4-0000-0000-0123-456789abcdef',
+                        'permissions': gatt_descriptor['permission_read'],
+                        'value': [0x44]
+                    },
+                    {
+                        'uuid': gatt_char_desc_uuids['char_fmt_uuid'],
+                        'permissions': gatt_descriptor['permission_read'],
+                        'value': [0x04, 0x00, 0x01, 0x30, 0x01, 0x11, 0x31]
+                    },
+                ]
+            }]
+        },
+        {
+            'uuid': '0000a00c-0000-0000-0123-456789abcdef',
+            'type': gatt_service_types['primary'],
+            'characteristics': [
+                {
+                    'uuid': '0000b00a-0000-1000-8000-00805f9b34fb',
+                    'instance_id': 0x00a2,
+                    'properties': gatt_characteristic['property_read'],
+                    'permissions': gatt_characteristic['permission_read'],
+                    'value_type': gatt_characteristic_value_format['byte'],
+                    'value': [0x0a],
+                },
+                {
+                    'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                    'instance_id': 0x00a4,
+                    'enforce_initial_attribute_length': True,
+                    'properties': 0x0a,
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '111112222233333444445',
+                },
+                {
+                    'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                    'instance_id': 0x00a6,
+                    'enforce_initial_attribute_length': True,
+                    'properties': 0x0a,
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '2222233333444445555566',
+                },
+                {
+                    'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                    'instance_id': 0x00a8,
+                    'enforce_initial_attribute_length': True,
+                    'properties': 0x0a,
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '33333444445555566666777',
+                },
+                {
+                    'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                    'instance_id': 0x00aa,
+                    'enforce_initial_attribute_length': True,
+                    'properties': 0x0a,
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '1111122222333334444455555666667777788888999',
+                },
+                {
+                    'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                    'instance_id': 0x00ac,
+                    'enforce_initial_attribute_length': True,
+                    'properties': 0x0a,
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '22222333334444455555666667777788888999990000',
+                },
+                {
+                    'uuid': '0000b002-0000-1000-8000-00805f9b34fb',
+                    'instance_id': 0x00ae,
+                    'enforce_initial_attribute_length': True,
+                    'properties': 0x0a,
+                    'permissions': gatt_characteristic['permission_read'] |
+                    gatt_characteristic['permission_write'],
+                    'value_type': gatt_characteristic_value_format['string'],
+                    'value': '333334444455555666667777788888999990000011111',
+                },
+            ]
+        },
+        {
+            'uuid': '0000a00e-0000-1000-8000-00805f9b34fb',
+            'type': gatt_service_types['primary'],
+            'characteristics': [{
+                'uuid': '0000b00d-0000-1000-8000-00805f9b34fb',
+                'instance_id': 0xffff,
+                'properties': gatt_characteristic['property_read'],
+                'permissions': gatt_characteristic['permission_read'],
+                'value_type': gatt_characteristic_value_format['byte'],
+                'value': [0x0d],
+            }]
+        },
+    ]
+}
+
+TEST_DB_1 = {
+    'services': [{
+        'uuid': '0000180d-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'handles': 4,
+        'characteristics': [{
+            'uuid': '00002a29-0000-1000-8000-00805f9b34fb',
+            'properties': gatt_characteristic['property_read'] |
+            gatt_characteristic['property_write'],
+            'permissions': gatt_characteristic['permission_read'] |
+            gatt_characteristic['permission_write'],
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': 'test',
+            'instance_id': 0x002a,
+            'descriptors': [{
+                'uuid': gatt_char_desc_uuids['char_user_desc'],
+                'permissions': gatt_descriptor['permission_read'],
+                'value': [0x01]
+            }]
+        }]
+    }]
+}
+
+TEST_DB_2 = {
+    'services': [{
+        'uuid': '0000180d-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'handles': 4,
+        'characteristics': [{
+            'uuid': '00002a29-0000-1000-8000-00805f9b34fb',
+            'properties': gatt_characteristic['property_read'],
+            'permissions':
+            gatt_characteristic['permission_read_encrypted_mitm'],
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': 'test',
+            'instance_id': 0x002a,
+        }, {
+            'uuid': '00002a30-0000-1000-8000-00805f9b34fb',
+            'properties': gatt_characteristic['property_read'],
+            'permissions':
+            gatt_characteristic['permission_read_encrypted_mitm'],
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': 'test',
+            'instance_id': 0x002b,
+        }]
+    }]
+}
+
+TEST_DB_3 = {
+    'services': [{
+        'uuid': '0000180d-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'handles': 4,
+        'characteristics': [{
+            'uuid': '00002a29-0000-1000-8000-00805f9b34fb',
+            'properties': gatt_characteristic['property_read'] |
+            gatt_characteristic['property_write'],
+            'permissions': gatt_characteristic['permission_read'] |
+            gatt_characteristic['permission_write'],
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': 'test',
+            'instance_id': 0x002a,
+            'descriptors': [{
+                'uuid': gatt_char_desc_uuids['char_user_desc'],
+                'permissions': gatt_descriptor['permission_read'],
+                'value': [0x01]
+            }, {
+                'uuid': '00002a20-0000-1000-8000-00805f9b34fb',
+                'permissions': gatt_descriptor['permission_read'] |
+                gatt_descriptor['permission_write'],
+                'instance_id': 0x002c,
+                'value': [0x01]
+            }]
+        }, {
+            'uuid': '00002a30-0000-1000-8000-00805f9b34fb',
+            'properties': gatt_characteristic['property_read'] |
+            gatt_characteristic['property_write'],
+            'permissions': gatt_characteristic['permission_read'] |
+            gatt_characteristic['permission_write'],
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': 'test',
+            'instance_id': 0x002b,
+        }]
+    }]
+}
+
+TEST_DB_4 = {
+    'services': [{
+        'uuid': '0000180d-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'handles': 4,
+        'characteristics': [{
+            'uuid': '00002a29-0000-1000-8000-00805f9b34fb',
+            'properties': gatt_characteristic['property_write_no_response'],
+            'permissions': gatt_characteristic['permission_read'],
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': "test",
+            'instance_id': 0x002a,
+            'descriptors': [{
+                'uuid': gatt_char_desc_uuids['char_user_desc'],
+                'permissions':
+                gatt_descriptor['permission_read_encrypted_mitm'],
+                'value': [0] * 512
+            }]
+        }]
+    }]
+}
+
+TEST_DB_5 = {
+    'services': [{
+        'uuid': '0000180d-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'characteristics': [{
+            'uuid': 'b2c83efa-34ca-11e6-ac61-9e71128cae77',
+            'properties': gatt_characteristic['property_write'] |
+            gatt_characteristic['property_read'] |
+            gatt_characteristic['property_notify'],
+            'permissions': gatt_characteristic['permission_read'] |
+            gatt_characteristic['permission_write'],
+            'value_type': gatt_characteristic_value_format['byte'],
+            'value': [0x1],
+            'instance_id': 0x002c,
+            'descriptors': [{
+                'uuid': '00002902-0000-1000-8000-00805f9b34fb',
+                'permissions': gatt_descriptor['permission_read'] |
+                gatt_descriptor['permission_write'],
+            }]
+        }]
+    }]
+}
+
+TEST_DB_6 = {
+    'services': [{
+        'uuid': '0000180d-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'handles': 4,
+        'characteristics': [{
+            'uuid': '00002a29-0000-1000-8000-00805f9b34fb',
+            'properties': gatt_characteristic['property_read'] | gatt_characteristic['property_notify'],
+            'permissions': gatt_characteristic['permission_read_encrypted_mitm'],
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': 'test',
+            'instance_id': 0x002a,
+            'descriptors': [{
+                'uuid': '00002a19-0000-1000-8000-00805f9b34fb',
+                'permissions': gatt_descriptor['permission_read'],
+                'value': [0x01] * 30
+            }]
+        }]
+    }]
+}
+
+SIMPLE_READ_DESCRIPTOR = {
+    'services': [{
+        'uuid': '0000a00a-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'characteristics': [{
+            'uuid': 'aa7edd5a-4d1d-4f0e-883a-d145616a1630',
+            'properties': gatt_characteristic['property_read'],
+            'permissions': gatt_characteristic['permission_read'],
+            'instance_id': 0x002a,
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': 'Test Database',
+            'descriptors': [{
+                'uuid': gatt_char_desc_uuids['client_char_cfg'],
+                'permissions': gatt_descriptor['permission_read'],
+            }]
+        }]
+    }]
+}
+
+CHARACTERISTIC_PROPERTY_WRITE_NO_RESPONSE = {
+    'services': [{
+        'uuid': '0000a00a-0000-1000-8000-00805f9b34fb',
+        'type': gatt_service_types['primary'],
+        'characteristics': [{
+            'uuid': 'aa7edd5a-4d1d-4f0e-883a-d145616a1630',
+            'properties': gatt_characteristic['property_write_no_response'],
+            'permissions': gatt_characteristic['permission_write'] |
+            gatt_characteristic['permission_read'],
+            'instance_id': 0x0042,
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': 'Test Database'
+        }, {
+            'uuid': 'aa7edd6a-4d1d-4f0e-883a-d145616a1630',
+            'properties': gatt_characteristic['property_write_no_response'],
+            'permissions': gatt_characteristic['permission_write'] |
+            gatt_characteristic['permission_read'],
+            'instance_id': 0x004d,
+            'value_type': gatt_characteristic_value_format['string'],
+            'value': 'Test Database'
+        }]
+    }]
+}
+
+GATT_SERVER_DB_MAPPING = {
+    'LARGE_DB_1': LARGE_DB_1,
+    'LARGE_DB_3': LARGE_DB_3,
+    'INVALID_SMALL_DATABASE': INVALID_SMALL_DATABASE,
+    'SIMPLE_READ_DESCRIPTOR': SIMPLE_READ_DESCRIPTOR,
+    'CHARACTERISTIC_PROPERTY_WRITE_NO_RESPONSE':
+    CHARACTERISTIC_PROPERTY_WRITE_NO_RESPONSE,
+    'TEST_DB_1': TEST_DB_1,
+    'TEST_DB_2': TEST_DB_2,
+    'TEST_DB_3': TEST_DB_3,
+    'TEST_DB_4': TEST_DB_4,
+    'TEST_DB_5': TEST_DB_5,
+    'LARGE_DB_3_PLUS': LARGE_DB_3,
+    'DB_TEST': DB_TEST,
+    'PTS_TEST': PTS_TEST,
+    'PTS_TEST2': PTS_TEST2,
+    'TEST_DB_6': TEST_DB_6,
+}
diff --git a/acts_tests/acts_contrib/test_utils/bt/gattc_lib.py b/acts_tests/acts_contrib/test_utils/bt/gattc_lib.py
new file mode 100644
index 0000000..86f0950
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/gattc_lib.py
@@ -0,0 +1,576 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""
+GATT Client Libraries
+"""
+
+from acts_contrib.test_utils.bt.bt_constants import default_le_connection_interval_ms
+from acts_contrib.test_utils.bt.bt_constants import default_bluetooth_socket_timeout_ms
+from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_mtu
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import gatt_char_desc_uuids
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_transport
+from acts_contrib.test_utils.bt.bt_constants import le_default_supervision_timeout
+from acts_contrib.test_utils.bt.bt_constants import le_connection_interval_time_step_ms
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_gatt_utils import log_gatt_server_uuids
+
+import time
+import os
+
+
+class GattClientLib():
+    def __init__(self, log, dut, target_mac_addr=None):
+        self.dut = dut
+        self.log = log
+        self.gatt_callback = None
+        self.bluetooth_gatt = None
+        self.discovered_services_index = None
+        self.target_mac_addr = target_mac_addr
+        self.generic_uuid = "0000{}-0000-1000-8000-00805f9b34fb"
+
+    def set_target_mac_addr(self, mac_addr):
+        self.target_mac_addr = mac_addr
+
+    def connect_over_le_based_off_name(self, autoconnect, name):
+        """Perform GATT connection over LE"""
+        self.dut.droid.bleSetScanSettingsScanMode(
+            ble_scan_settings_modes['low_latency'])
+        filter_list = self.dut.droid.bleGenFilterList()
+        scan_settings = self.dut.droid.bleBuildScanSetting()
+        scan_callback = self.dut.droid.bleGenScanCallback()
+        event_name = scan_result.format(scan_callback)
+        self.dut.droid.bleSetScanFilterDeviceName("BLE Rect")
+        self.dut.droid.bleBuildScanFilter(filter_list)
+        self.dut.droid.bleStartBleScan(filter_list, scan_settings,
+                                       scan_callback)
+
+        try:
+            event = self.dut.ed.pop_event(event_name, 10)
+            self.log.info("Found scan result: {}".format(event))
+        except Exception:
+            self.log.info("Didn't find any scan results.")
+        mac_addr = event['data']['Result']['deviceInfo']['address']
+        self.bluetooth_gatt, self.gatt_callback = setup_gatt_connection(
+            self.dut, mac_addr, autoconnect, transport=gatt_transport['le'])
+        self.dut.droid.bleStopBleScan(scan_callback)
+        self.discovered_services_index = None
+
+    def connect_over_le(self, autoconnect):
+        """Perform GATT connection over LE"""
+        self.bluetooth_gatt, self.gatt_callback = setup_gatt_connection(
+            self.dut,
+            self.target_mac_addr,
+            autoconnect,
+            transport=gatt_transport['le'])
+        self.discovered_services_index = None
+
+    def connect_over_bredr(self):
+        """Perform GATT connection over BREDR"""
+        self.bluetooth_gatt, self.gatt_callback = setup_gatt_connection(
+            self.dut,
+            self.target_mac_addr,
+            False,
+            transport=gatt_transport['bredr'])
+
+    def disconnect(self):
+        """Perform GATT disconnect"""
+        cmd = "Disconnect GATT connection"
+        try:
+            disconnect_gatt_connection(self.dut, self.bluetooth_gatt,
+                                       self.gatt_callback)
+        except Exception as err:
+            self.log.info("Cmd {} failed with {}".format(cmd, err))
+        try:
+            self.dut.droid.gattClientClose(self.bluetooth_gatt)
+        except Exception as err:
+            self.log.info("Cmd failed with {}".format(err))
+
+    def _setup_discovered_services_index(self):
+        if not self.discovered_services_index:
+            self.dut.droid.gattClientDiscoverServices(self.bluetooth_gatt)
+            expected_event = gatt_cb_strings['gatt_serv_disc'].format(
+                self.gatt_callback)
+            event = self.dut.ed.pop_event(expected_event, 10)
+            self.discovered_services_index = event['data']['ServicesIndex']
+
+    def read_char_by_uuid(self, line):
+        """GATT client read Characteristic by UUID."""
+        uuid = line
+        if len(line) == 4:
+            uuid = self.generic_uuid.format(line)
+        self.dut.droid.gattClientReadUsingCharacteristicUuid(
+            self.bluetooth_gatt, uuid, 0x0001, 0xFFFF)
+
+    def request_mtu(self, mtu):
+        """Request MTU Change of input value"""
+        setup_gatt_mtu(self.dut, self.bluetooth_gatt, self.gatt_callback,
+                       int(mtu))
+
+    def list_all_uuids(self):
+        """From the GATT Client, discover services and list all services,
+        chars and descriptors
+        """
+        self._setup_discovered_services_index()
+        log_gatt_server_uuids(self.dut, self.discovered_services_index,
+                              self.bluetooth_gatt)
+
+    def discover_services(self):
+        """GATT Client discover services of GATT Server"""
+        self.dut.droid.gattClientDiscoverServices(self.bluetooth_gatt)
+
+    def refresh(self):
+        """Perform Gatt Client Refresh"""
+        self.dut.droid.gattClientRefresh(self.bluetooth_gatt)
+
+    def read_char_by_instance_id(self, id):
+        """From the GATT Client, discover services and list all services,
+        chars and descriptors
+        """
+        if not id:
+            self.log.info("Invalid id")
+            return
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientReadCharacteristicByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index, int(id, 16))
+
+    def write_char_by_instance_id(self, line):
+        """GATT Client Write to Characteristic by instance ID"""
+        args = line.split()
+        if len(args) != 2:
+            self.log.info("2 Arguments required: [InstanceId] [Size]")
+            return
+        instance_id = args[0]
+        size = args[1]
+        write_value = []
+        for i in range(int(size)):
+            write_value.append(i % 256)
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientWriteCharacteristicByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16), write_value)
+
+    def write_char_by_instance_id_value(self, line):
+        """GATT Client Write to Characteristic by instance ID"""
+        args = line.split()
+        if len(args) != 2:
+            self.log.info("2 Arguments required: [InstanceId] [Size]")
+            return
+        instance_id = args[0]
+        write_value = args[1]
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientWriteCharacteristicByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16), [int(write_value)])
+
+    def mod_write_char_by_instance_id(self, line):
+        """GATT Client Write to Char that doesn't have write permission"""
+        args = line.split()
+        if len(args) != 2:
+            self.log.info("2 Arguments required: [InstanceId] [Size]")
+            return
+        instance_id = args[0]
+        size = args[1]
+        write_value = []
+        for i in range(int(size)):
+            write_value.append(i % 256)
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientModifyAccessAndWriteCharacteristicByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16), write_value)
+
+    def write_invalid_char_by_instance_id(self, line):
+        """GATT Client Write to Char that doesn't exists"""
+        args = line.split()
+        if len(args) != 2:
+            self.log.info("2 Arguments required: [InstanceId] [Size]")
+            return
+        instance_id = args[0]
+        size = args[1]
+        write_value = []
+        for i in range(int(size)):
+            write_value.append(i % 256)
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientWriteInvalidCharacteristicByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16), write_value)
+
+    def mod_read_char_by_instance_id(self, line):
+        """GATT Client Read Char that doesn't have write permission"""
+        instance_id = line
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientModifyAccessAndReadCharacteristicByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16))
+
+    def read_invalid_char_by_instance_id(self, line):
+        """GATT Client Read Char that doesn't exists"""
+        instance_id = line
+        self.dut.droid.gattClientReadInvalidCharacteristicByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16))
+
+    def mod_write_desc_by_instance_id(self, line):
+        """GATT Client Write to Desc that doesn't have write permission"""
+        cmd = ""
+        args = line.split()
+        if len(args) != 2:
+            self.log.info("2 Arguments required: [InstanceId] [Size]")
+            return
+        instance_id = args[0]
+        size = args[1]
+        write_value = []
+        for i in range(int(size)):
+            write_value.append(i % 256)
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientModifyAccessAndWriteDescriptorByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16), write_value)
+
+    def write_invalid_desc_by_instance_id(self, line):
+        """GATT Client Write to Desc that doesn't exists"""
+        args = line.split()
+        if len(args) != 2:
+            self.log.info("2 Arguments required: [InstanceId] [Size]")
+            return
+        instance_id = args[0]
+        size = args[1]
+        write_value = []
+        for i in range(int(size)):
+            write_value.append(i % 256)
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientWriteInvalidDescriptorByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16), write_value)
+
+    def mod_read_desc_by_instance_id(self, line):
+        """GATT Client Read Desc that doesn't have write permission"""
+        cmd = ""
+        instance_id = line
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientModifyAccessAndReadDescriptorByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16))
+
+    def read_invalid_desc_by_instance_id(self, line):
+        """GATT Client Read Desc that doesn't exists"""
+        instance_id = line
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientReadInvalidDescriptorByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16))
+
+    def mod_read_char_by_uuid_and_instance_id(self, line):
+        """GATT Client Read Char that doesn't have write permission"""
+        args = line.split()
+        if len(args) != 2:
+            self.log.info("2 Arguments required: [uuid] [instance_id]")
+            return
+        uuid = args[0]
+        instance_id = args[1]
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientModifyAccessAndReadCharacteristicByUuidAndInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16), self.generic_uuid.format(uuid))
+
+    def read_invalid_char_by_uuid(self, line):
+        """GATT Client Read Char that doesn't exists"""
+        uuid = line
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientReadInvalidCharacteristicByUuid(
+            self.bluetooth_gatt, self.discovered_services_index,
+            self.generic_uuid.format(uuid))
+
+    def write_desc_by_instance_id(self, line):
+        """GATT Client Write to Descriptor by instance ID"""
+        args = line.split()
+        if len(args) != 2:
+            self.log.info("2 Arguments required: [instanceID] [size]")
+            return
+        instance_id = args[0]
+        size = args[1]
+        write_value = []
+        for i in range(int(size)):
+            write_value.append(i % 256)
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientWriteDescriptorByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16), write_value)
+
+    def write_desc_notification_by_instance_id(self, line):
+        """GATT Client Write to Descriptor by instance ID"""
+        args = line.split()
+        instance_id = args[0]
+        switch = int(args[1])
+        write_value = [0x00, 0x00]
+        if switch == 2:
+            write_value = [0x02, 0x00]
+        self._setup_discovered_services_index()
+        self.dut.droid.gattClientWriteDescriptorByInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index,
+            int(instance_id, 16), write_value)
+
+    def enable_notification_desc_by_instance_id(self, line):
+        """GATT Client Enable Notification on Descriptor by instance ID"""
+        instance_id = line
+        self._setup_discovered_services_index()
+        services_count = self.dut.droid.gattClientGetDiscoveredServicesCount(
+            self.discovered_services_index)
+        for i in range(services_count):
+            characteristic_uuids = (
+                self.dut.droid.gattClientGetDiscoveredCharacteristicUuids(
+                    self.discovered_services_index, i))
+            for j in range(len(characteristic_uuids)):
+                descriptor_uuids = (
+                    self.dut.droid.
+                    gattClientGetDiscoveredDescriptorUuidsByIndex(
+                        self.discovered_services_index, i, j))
+                for k in range(len(descriptor_uuids)):
+                    desc_inst_id = self.dut.droid.gattClientGetDescriptorInstanceId(
+                        self.bluetooth_gatt, self.discovered_services_index, i,
+                        j, k)
+                    if desc_inst_id == int(instance_id, 16):
+                        self.dut.droid.gattClientDescriptorSetValueByIndex(
+                            self.bluetooth_gatt,
+                            self.discovered_services_index, i, j, k,
+                            gatt_descriptor['enable_notification_value'])
+                        time.sleep(2)  #Necessary for PTS
+                        self.dut.droid.gattClientWriteDescriptorByIndex(
+                            self.bluetooth_gatt,
+                            self.discovered_services_index, i, j, k)
+                        time.sleep(2)  #Necessary for PTS
+                        self.dut.droid.gattClientSetCharacteristicNotificationByIndex(
+                            self.bluetooth_gatt,
+                            self.discovered_services_index, i, j, True)
+
+    def enable_indication_desc_by_instance_id(self, line):
+        """GATT Client Enable indication on Descriptor by instance ID"""
+        instance_id = line
+        self._setup_discovered_services_index()
+        services_count = self.dut.droid.gattClientGetDiscoveredServicesCount(
+            self.discovered_services_index)
+        for i in range(services_count):
+            characteristic_uuids = (
+                self.dut.droid.gattClientGetDiscoveredCharacteristicUuids(
+                    self.discovered_services_index, i))
+            for j in range(len(characteristic_uuids)):
+                descriptor_uuids = (
+                    self.dut.droid.
+                    gattClientGetDiscoveredDescriptorUuidsByIndex(
+                        self.discovered_services_index, i, j))
+                for k in range(len(descriptor_uuids)):
+                    desc_inst_id = self.dut.droid.gattClientGetDescriptorInstanceId(
+                        self.bluetooth_gatt, self.discovered_services_index, i,
+                        j, k)
+                    if desc_inst_id == int(instance_id, 16):
+                        self.dut.droid.gattClientDescriptorSetValueByIndex(
+                            self.bluetooth_gatt,
+                            self.discovered_services_index, i, j, k,
+                            gatt_descriptor['enable_indication_value'])
+                        time.sleep(2)  #Necessary for PTS
+                        self.dut.droid.gattClientWriteDescriptorByIndex(
+                            self.bluetooth_gatt,
+                            self.discovered_services_index, i, j, k)
+                        time.sleep(2)  #Necessary for PTS
+                        self.dut.droid.gattClientSetCharacteristicNotificationByIndex(
+                            self.bluetooth_gatt,
+                            self.discovered_services_index, i, j, True)
+
+    def char_enable_all_notifications(self):
+        self._setup_discovered_services_index()
+        services_count = self.dut.droid.gattClientGetDiscoveredServicesCount(
+            self.discovered_services_index)
+        for i in range(services_count):
+            characteristic_uuids = (
+                self.dut.droid.gattClientGetDiscoveredCharacteristicUuids(
+                    self.discovered_services_index, i))
+            for j in range(len(characteristic_uuids)):
+                self.dut.droid.gattClientSetCharacteristicNotificationByIndex(
+                    self.bluetooth_gatt, self.discovered_services_index, i, j,
+                    True)
+
+    def read_char_by_invalid_instance_id(self, line):
+        self._setup_discovered_services_index()
+        services_count = self.dut.droid.gattClientGetDiscoveredServicesCount(
+            self.discovered_services_index)
+        self.dut.droid.gattClientReadInvalidCharacteristicInstanceId(
+            self.bluetooth_gatt, self.discovered_services_index, 0,
+            int(line, 16))
+
+    def begin_reliable_write(self):
+        """Begin a reliable write on the Bluetooth Gatt Client"""
+        self.dut.droid.gattClientBeginReliableWrite(self.bluetooth_gatt)
+
+    def abort_reliable_write(self):
+        """Abort a reliable write on the Bluetooth Gatt Client"""
+        self.dut.droid.gattClientAbortReliableWrite(self.bluetooth_gatt)
+
+    def execute_reliable_write(self):
+        """Execute a reliable write on the Bluetooth Gatt Client"""
+        self.dut.droid.gattExecuteReliableWrite(self.bluetooth_gatt)
+
+    def read_all_char(self):
+        """GATT Client read all Characteristic values"""
+        self._setup_discovered_services_index()
+        services_count = self.dut.droid.gattClientGetDiscoveredServicesCount(
+            self.discovered_services_index)
+        for i in range(services_count):
+            characteristic_uuids = (
+                self.dut.droid.gattClientGetDiscoveredCharacteristicUuids(
+                    self.discovered_services_index, i))
+            for j in range(len(characteristic_uuids)):
+                char_inst_id = self.dut.droid.gattClientGetCharacteristicInstanceId(
+                    self.bluetooth_gatt, self.discovered_services_index, i, j)
+                self.log.info("Reading characteristic {} {}".format(
+                    hex(char_inst_id), characteristic_uuids[j]))
+                self.dut.droid.gattClientReadCharacteristicByIndex(
+                    self.bluetooth_gatt, self.discovered_services_index, i, j)
+                time.sleep(1)  # Necessary for PTS
+
+    def read_all_desc(self):
+        """GATT Client read all Descriptor values"""
+        self._setup_discovered_services_index()
+        services_count = self.dut.droid.gattClientGetDiscoveredServicesCount(
+            self.discovered_services_index)
+        for i in range(services_count):
+            characteristic_uuids = (
+                self.dut.droid.gattClientGetDiscoveredCharacteristicUuids(
+                    self.discovered_services_index, i))
+            for j in range(len(characteristic_uuids)):
+                descriptor_uuids = (
+                    self.dut.droid.
+                    gattClientGetDiscoveredDescriptorUuidsByIndex(
+                        self.discovered_services_index, i, j))
+                for k in range(len(descriptor_uuids)):
+                    time.sleep(1)
+                    try:
+                        self.log.info("Reading descriptor {}".format(
+                            descriptor_uuids[k]))
+                        self.dut.droid.gattClientReadDescriptorByIndex(
+                            self.bluetooth_gatt,
+                            self.discovered_services_index, i, j, k)
+                    except Exception as err:
+                        self.log.info(
+                            "Failed to read to descriptor: {}".format(
+                                descriptor_uuids[k]))
+
+    def write_all_char(self, line):
+        """Write to every Characteristic on the GATT server"""
+        args = line.split()
+        write_value = []
+        for i in range(int(line)):
+            write_value.append(i % 256)
+        self._setup_discovered_services_index()
+        services_count = self.dut.droid.gattClientGetDiscoveredServicesCount(
+            self.discovered_services_index)
+        for i in range(services_count):
+            characteristic_uuids = (
+                self.dut.droid.gattClientGetDiscoveredCharacteristicUuids(
+                    self.discovered_services_index, i))
+            for j in range(len(characteristic_uuids)):
+                char_inst_id = self.dut.droid.gattClientGetCharacteristicInstanceId(
+                    self.bluetooth_gatt, self.discovered_services_index, i, j)
+                self.log.info("Writing to {} {}".format(
+                    hex(char_inst_id), characteristic_uuids[j]))
+                try:
+                    self.dut.droid.gattClientCharacteristicSetValueByIndex(
+                        self.bluetooth_gatt, self.discovered_services_index, i,
+                        j, write_value)
+                    self.dut.droid.gattClientWriteCharacteristicByIndex(
+                        self.bluetooth_gatt, self.discovered_services_index, i,
+                        j)
+                    time.sleep(1)
+                except Exception as err:
+                    self.log.info(
+                        "Failed to write to characteristic: {}".format(
+                            characteristic_uuids[j]))
+
+    def write_all_desc(self, line):
+        """ Write to every Descriptor on the GATT server """
+        args = line.split()
+        write_value = []
+        for i in range(int(line)):
+            write_value.append(i % 256)
+        self._setup_discovered_services_index()
+        services_count = self.dut.droid.gattClientGetDiscoveredServicesCount(
+            self.discovered_services_index)
+        for i in range(services_count):
+            characteristic_uuids = (
+                self.dut.droid.gattClientGetDiscoveredCharacteristicUuids(
+                    self.discovered_services_index, i))
+            for j in range(len(characteristic_uuids)):
+                descriptor_uuids = (
+                    self.dut.droid.
+                    gattClientGetDiscoveredDescriptorUuidsByIndex(
+                        self.discovered_services_index, i, j))
+                for k in range(len(descriptor_uuids)):
+                    time.sleep(1)
+                    desc_inst_id = self.dut.droid.gattClientGetDescriptorInstanceId(
+                        self.bluetooth_gatt, self.discovered_services_index, i,
+                        j, k)
+                    self.log.info("Writing to {} {}".format(
+                        hex(desc_inst_id), descriptor_uuids[k]))
+                    try:
+                        self.dut.droid.gattClientDescriptorSetValueByIndex(
+                            self.bluetooth_gatt,
+                            self.discovered_services_index, i, j, k,
+                            write_value)
+                        self.dut.droid.gattClientWriteDescriptorByIndex(
+                            self.bluetooth_gatt,
+                            self.discovered_services_index, i, j, k)
+                    except Exception as err:
+                        self.log.info(
+                            "Failed to write to descriptor: {}".format(
+                                descriptor_uuids[k]))
+
+    def discover_service_by_uuid(self, line):
+        """ Discover service by UUID """
+        uuid = line
+        if len(line) == 4:
+            uuid = self.generic_uuid.format(line)
+        self.dut.droid.gattClientDiscoverServiceByUuid(self.bluetooth_gatt,
+                                                       uuid)
+
+    def request_le_connection_parameters(self):
+        le_min_ce_len = 0
+        le_max_ce_len = 0
+        le_connection_interval = 0
+        minInterval = default_le_connection_interval_ms / le_connection_interval_time_step_ms
+        maxInterval = default_le_connection_interval_ms / le_connection_interval_time_step_ms
+        return_status = self.dut.droid.gattClientRequestLeConnectionParameters(
+            self.bluetooth_gatt, minInterval, maxInterval, 0,
+            le_default_supervision_timeout, le_min_ce_len, le_max_ce_len)
+        self.log.info(
+            "Result of request le connection param: {}".format(return_status))
+
+    def socket_conn_begin_connect_thread_psm(self, line):
+        args = line.split()
+        is_ble = bool(int(args[0]))
+        secured_conn = bool(int(args[1]))
+        psm_value = int(args[2])  # 1
+        self.dut.droid.bluetoothSocketConnBeginConnectThreadPsm(
+            self.target_mac_addr, is_ble, psm_value, secured_conn)
+
+    def socket_conn_begin_accept_thread_psm(self, line):
+        accept_timeout_ms = default_bluetooth_socket_timeout_ms
+        is_ble = True
+        secured_conn = False
+        self.dut.droid.bluetoothSocketConnBeginAcceptThreadPsm(
+            accept_timeout_ms, is_ble, secured_conn)
diff --git a/acts_tests/acts_contrib/test_utils/bt/gatts_lib.py b/acts_tests/acts_contrib/test_utils/bt/gatts_lib.py
new file mode 100644
index 0000000..ebfe1bd
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/gatts_lib.py
@@ -0,0 +1,377 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import time
+import os
+
+from acts.keys import Config
+from acts.utils import rand_ascii_str
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic_value_format
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_err
+from acts_contrib.test_utils.bt.bt_constants import gatt_transport
+from acts_contrib.test_utils.bt.bt_constants import gatt_event
+from acts_contrib.test_utils.bt.bt_constants import gatt_server_responses
+from acts_contrib.test_utils.bt.bt_constants import gatt_service_types
+from acts_contrib.test_utils.bt.bt_constants import small_timeout
+from acts_contrib.test_utils.bt.gatt_test_database import STRING_512BYTES
+
+from acts.utils import exe_cmd
+from math import ceil
+
+
+class GattServerLib():
+
+    characteristic_list = []
+    default_timeout = 10
+    descriptor_list = []
+    dut = None
+    gatt_server = None
+    gatt_server_callback = None
+    gatt_server_list = []
+    log = None
+    service_list = []
+    write_mapping = {}
+
+    def __init__(self, log, dut):
+        self.dut = dut
+        self.log = log
+
+    def list_all_uuids(self):
+        """From the GATT Client, discover services and list all services,
+        chars and descriptors.
+        """
+        self.log.info("Service List:")
+        for service in self.dut.droid.gattGetServiceUuidList(self.gatt_server):
+            self.dut.log.info("GATT Server service uuid: {}".format(service))
+        self.log.info("Characteristics List:")
+        for characteristic in self.characteristic_list:
+            instance_id = self.dut.droid.gattServerGetCharacteristicInstanceId(
+                characteristic)
+            uuid = self.dut.droid.gattServerGetCharacteristicUuid(
+                characteristic)
+            self.dut.log.info(
+                "GATT Server characteristic handle uuid: {} {}".format(
+                    hex(instance_id), uuid))
+        # TODO: add getting insance ids and uuids from each descriptor.
+
+    def open(self):
+        """Open an empty GATT Server instance"""
+        self.gatt_server_callback = self.dut.droid.gattServerCreateGattServerCallback(
+        )
+        self.gatt_server = self.dut.droid.gattServerOpenGattServer(
+            self.gatt_server_callback)
+        self.gatt_server_list.append(self.gatt_server)
+
+    def clear_services(self):
+        """Clear BluetoothGattServices from BluetoothGattServer"""
+        self.dut.droid.gattServerClearServices(self.gatt_server)
+
+    def close_bluetooth_gatt_servers(self):
+        """Close Bluetooth Gatt Servers"""
+        try:
+            for btgs in self.gatt_server_list:
+                self.dut.droid.gattServerClose(btgs)
+        except Exception as err:
+            self.log.error(
+                "Failed to close Bluetooth GATT Servers: {}".format(err))
+        self.characteristic_list = []
+        self.descriptor_list = []
+        self.gatt_server_list = []
+        self.service_list = []
+
+    def characteristic_set_value_by_instance_id(self, instance_id, value):
+        """Set Characteristic value by instance id"""
+        self.dut.droid.gattServerCharacteristicSetValueByInstanceId(
+            int(instance_id, 16), value)
+
+    def notify_characteristic_changed(self, instance_id, confirm):
+        """ Notify characteristic changed """
+        self.dut.droid.gattServerNotifyCharacteristicChangedByInstanceId(
+            self.gatt_server, 0, int(instance_id, 16), confirm)
+
+    def send_response(self, user_input):
+        """Send a single response to the GATT Client"""
+        args = user_input.split()
+        mtu = 23
+        if len(args) == 2:
+            user_input = args[0]
+            mtu = int(args[1])
+        desc_read = gatt_event['desc_read_req']['evt'].format(
+            self.gatt_server_callback)
+        desc_write = gatt_event['desc_write_req']['evt'].format(
+            self.gatt_server_callback)
+        char_read = gatt_event['char_read_req']['evt'].format(
+            self.gatt_server_callback)
+        char_write_req = gatt_event['char_write_req']['evt'].format(
+            self.gatt_server_callback)
+        char_write = gatt_event['char_write']['evt'].format(
+            self.gatt_server_callback)
+        execute_write = gatt_event['exec_write']['evt'].format(
+            self.gatt_server_callback)
+        regex = "({}|{}|{}|{}|{}|{})".format(desc_read, desc_write, char_read,
+                                             char_write, execute_write,
+                                             char_write_req)
+        events = self.dut.ed.pop_events(regex, 5, small_timeout)
+        status = 0
+        if user_input:
+            status = gatt_server_responses.get(user_input)
+        for event in events:
+            self.log.debug("Found event: {}.".format(event))
+            request_id = event['data']['requestId']
+            if event['name'] == execute_write:
+                if ('execute' in event['data']
+                        and event['data']['execute'] == True):
+                    for key in self.write_mapping:
+                        value = self.write_mapping[key]
+                        self.log.info("Writing key, value: {}, {}".format(
+                            key, value))
+                        self.dut.droid.gattServerSetByteArrayValueByInstanceId(
+                            key, value)
+                else:
+                    self.log.info("Execute result is false")
+                self.write_mapping = {}
+                self.dut.droid.gattServerSendResponse(
+                    self.gatt_server, 0, request_id, status, 0, [])
+                continue
+            offset = event['data']['offset']
+            instance_id = event['data']['instanceId']
+            if (event['name'] == desc_write or event['name'] == char_write
+                    or event['name'] == char_write_req):
+                if ('preparedWrite' in event['data']
+                        and event['data']['preparedWrite'] == True):
+                    value = event['data']['value']
+                    if instance_id in self.write_mapping.keys():
+                        self.write_mapping[
+                            instance_id] = self.write_mapping[instance_id] + value
+                        self.log.info(
+                            "New Prepared Write Value for {}: {}".format(
+                                instance_id, self.write_mapping[instance_id]))
+                    else:
+                        self.log.info("write mapping key, value {}, {}".format(
+                            instance_id, value))
+                        self.write_mapping[instance_id] = value
+                        self.log.info("current value {}, {}".format(
+                            instance_id, value))
+                    self.dut.droid.gattServerSendResponse(
+                        self.gatt_server, 0, request_id, status, 0, value)
+                    continue
+                else:
+                    self.dut.droid.gattServerSetByteArrayValueByInstanceId(
+                        event['data']['instanceId'], event['data']['value'])
+
+            try:
+                data = self.dut.droid.gattServerGetReadValueByInstanceId(
+                    int(event['data']['instanceId']))
+            except Exception as err:
+                self.log.error(err)
+            if not data:
+                data = [1]
+            self.log.info(
+                "GATT Server Send Response [request_id, status, offset, data]" \
+                " [{}, {}, {}, {}]".
+                format(request_id, status, offset, data))
+            data = data[offset:offset + mtu - 1]
+            self.dut.droid.gattServerSendResponse(
+                self.gatt_server, 0, request_id, status, offset, data)
+
+    def _setup_service(self, serv):
+        service = self.dut.droid.gattServerCreateService(
+            serv['uuid'], serv['type'])
+        if 'handles' in serv:
+            self.dut.droid.gattServerServiceSetHandlesToReserve(
+                service, serv['handles'])
+        return service
+
+    def _setup_characteristic(self, char):
+        characteristic = \
+            self.dut.droid.gattServerCreateBluetoothGattCharacteristic(
+                char['uuid'], char['properties'], char['permissions'])
+        if 'instance_id' in char:
+            self.dut.droid.gattServerCharacteristicSetInstanceId(
+                characteristic, char['instance_id'])
+            set_id = self.dut.droid.gattServerCharacteristicGetInstanceId(
+                characteristic)
+            if set_id != char['instance_id']:
+                self.log.error(
+                    "Instance ID did not match up. Found {} Expected {}".
+                    format(set_id, char['instance_id']))
+        if 'value_type' in char:
+            value_type = char['value_type']
+            value = char['value']
+            if value_type == gatt_characteristic_value_format['string']:
+                self.log.info("Set String value result: {}".format(
+                    self.dut.droid.gattServerCharacteristicSetStringValue(
+                        characteristic, value)))
+            elif value_type == gatt_characteristic_value_format['byte']:
+                self.log.info("Set Byte Array value result: {}".format(
+                    self.dut.droid.gattServerCharacteristicSetByteValue(
+                        characteristic, value)))
+            else:
+                self.log.info("Set Int value result: {}".format(
+                    self.dut.droid.gattServerCharacteristicSetIntValue(
+                        characteristic, value, value_type, char['offset'])))
+        return characteristic
+
+    def _setup_descriptor(self, desc):
+        descriptor = self.dut.droid.gattServerCreateBluetoothGattDescriptor(
+            desc['uuid'], desc['permissions'])
+        if 'value' in desc:
+            self.dut.droid.gattServerDescriptorSetByteValue(
+                descriptor, desc['value'])
+        if 'instance_id' in desc:
+            self.dut.droid.gattServerDescriptorSetInstanceId(
+                descriptor, desc['instance_id'])
+        self.descriptor_list.append(descriptor)
+        return descriptor
+
+    def setup_gatts_db(self, database):
+        """Setup GATT Server database"""
+        self.gatt_server_callback = \
+            self.dut.droid.gattServerCreateGattServerCallback()
+        self.gatt_server = self.dut.droid.gattServerOpenGattServer(
+            self.gatt_server_callback)
+        self.gatt_server_list.append(self.gatt_server)
+        for serv in database['services']:
+            service = self._setup_service(serv)
+            self.service_list.append(service)
+            if 'characteristics' in serv:
+                for char in serv['characteristics']:
+                    characteristic = self._setup_characteristic(char)
+                    if 'descriptors' in char:
+                        for desc in char['descriptors']:
+                            descriptor = self._setup_descriptor(desc)
+                            self.dut.droid.gattServerCharacteristicAddDescriptor(
+                                characteristic, descriptor)
+                    self.characteristic_list.append(characteristic)
+                    self.dut.droid.gattServerAddCharacteristicToService(
+                        service, characteristic)
+            self.dut.droid.gattServerAddService(self.gatt_server, service)
+            expected_event = gatt_cb_strings['serv_added'].format(
+                self.gatt_server_callback)
+            self.dut.ed.pop_event(expected_event, 10)
+        return self.gatt_server, self.gatt_server_callback
+
+    def send_continuous_response(self, user_input):
+        """Send the same response"""
+        desc_read = gatt_event['desc_read_req']['evt'].format(
+            self.gatt_server_callback)
+        desc_write = gatt_event['desc_write_req']['evt'].format(
+            self.gatt_server_callback)
+        char_read = gatt_event['char_read_req']['evt'].format(
+            self.gatt_server_callback)
+        char_write = gatt_event['char_write']['evt'].format(
+            self.gatt_server_callback)
+        execute_write = gatt_event['exec_write']['evt'].format(
+            self.gatt_server_callback)
+        regex = "({}|{}|{}|{}|{})".format(desc_read, desc_write, char_read,
+                                          char_write, execute_write)
+        offset = 0
+        status = 0
+        mtu = 23
+        char_value = []
+        for i in range(512):
+            char_value.append(i % 256)
+        len_min = 470
+        end_time = time.time() + 180
+        i = 0
+        num_packets = ceil((len(char_value) + 1) / (mtu - 1))
+        while time.time() < end_time:
+            events = self.dut.ed.pop_events(regex, 10, small_timeout)
+            for event in events:
+                start_offset = i * (mtu - 1)
+                i += 1
+                self.log.debug("Found event: {}.".format(event))
+                request_id = event['data']['requestId']
+                data = char_value[start_offset:start_offset + mtu - 1]
+                if not data:
+                    data = [1]
+                self.log.debug(
+                    "GATT Server Send Response [request_id, status, offset, " \
+                    "data] [{}, {}, {}, {}]".format(request_id, status, offset,
+                        data))
+                self.dut.droid.gattServerSendResponse(
+                    self.gatt_server, 0, request_id, status, offset, data)
+
+    def send_continuous_response_data(self, user_input):
+        """Send the same response with data"""
+        desc_read = gatt_event['desc_read_req']['evt'].format(
+            self.gatt_server_callback)
+        desc_write = gatt_event['desc_write_req']['evt'].format(
+            self.gatt_server_callback)
+        char_read = gatt_event['char_read_req']['evt'].format(
+            self.gatt_server_callback)
+        char_write = gatt_event['char_write']['evt'].format(
+            self.gatt_server_callback)
+        execute_write = gatt_event['exec_write']['evt'].format(
+            self.gatt_server_callback)
+        regex = "({}|{}|{}|{}|{})".format(desc_read, desc_write, char_read,
+                                          char_write, execute_write)
+        offset = 0
+        status = 0
+        mtu = 11
+        char_value = []
+        len_min = 470
+        end_time = time.time() + 180
+        i = 0
+        num_packets = ceil((len(char_value) + 1) / (mtu - 1))
+        while time.time() < end_time:
+            events = self.dut.ed.pop_events(regex, 10, small_timeout)
+            for event in events:
+                self.log.info(event)
+                request_id = event['data']['requestId']
+                if event['name'] == execute_write:
+                    if ('execute' in event['data']
+                            and event['data']['execute'] == True):
+                        for key in self.write_mapping:
+                            value = self.write_mapping[key]
+                            self.log.debug("Writing key, value: {}, {}".format(
+                                key, value))
+                            self.dut.droid.gattServerSetByteArrayValueByInstanceId(
+                                key, value)
+                        self.write_mapping = {}
+                    self.dut.droid.gattServerSendResponse(
+                        self.gatt_server, 0, request_id, status, 0, [1])
+                    continue
+                offset = event['data']['offset']
+                instance_id = event['data']['instanceId']
+                if (event['name'] == desc_write
+                        or event['name'] == char_write):
+                    if ('preparedWrite' in event['data']
+                            and event['data']['preparedWrite'] == True):
+                        value = event['data']['value']
+                        if instance_id in self.write_mapping:
+                            self.write_mapping[
+                                instance_id] = self.write_mapping[instance_id] + value
+                        else:
+                            self.write_mapping[instance_id] = value
+                    else:
+                        self.dut.droid.gattServerSetByteArrayValueByInstanceId(
+                            event['data']['instanceId'],
+                            event['data']['value'])
+                try:
+                    data = self.dut.droid.gattServerGetReadValueByInstanceId(
+                        int(event['data']['instanceId']))
+                except Exception as err:
+                    self.log.error(err)
+                if not data:
+                    self.dut.droid.gattServerSendResponse(
+                        self.gatt_server, 0, request_id, status, offset, [1])
+                else:
+                    self.dut.droid.gattServerSendResponse(
+                        self.gatt_server, 0, request_id, status, offset,
+                        data[offset:offset + 17])
diff --git a/acts_tests/acts_contrib/test_utils/bt/loggers/__init__.py b/acts_tests/acts_contrib/test_utils/bt/loggers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/loggers/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/bt/loggers/bluetooth_metric_logger.py b/acts_tests/acts_contrib/test_utils/bt/loggers/bluetooth_metric_logger.py
new file mode 100644
index 0000000..98c925e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/loggers/bluetooth_metric_logger.py
@@ -0,0 +1,162 @@
+# /usr/bin/env python3
+#
+# Copyright (C) 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 base64
+from google.protobuf import message
+import os
+import time
+
+from acts.metrics.core import ProtoMetric
+from acts.metrics.logger import MetricLogger
+from acts_contrib.test_utils.bt.loggers.protos import bluetooth_metric_pb2
+
+
+def recursive_assign(proto, dct):
+    """Assign values in dct to proto recursively."""
+    for metric in dir(proto):
+        if metric in dct:
+            if (isinstance(dct[metric], dict) and
+                    isinstance(getattr(proto, metric), message.Message)):
+                recursive_assign(getattr(proto, metric), dct[metric])
+            else:
+                setattr(proto, metric, dct[metric])
+
+
+class BluetoothMetricLogger(MetricLogger):
+    """A logger for gathering Bluetooth test metrics
+
+    Attributes:
+        proto_module: Module used to store Bluetooth metrics in a proto
+        results: Stores ProtoMetrics to be published for each logger context
+        proto_map: Maps test case names to the appropriate protos for each case
+    """
+
+    def __init__(self, event):
+        super().__init__(event=event)
+        self.proto_module = bluetooth_metric_pb2
+        self.results = []
+        self.start_time = int(time.time())
+
+        self.proto_map = {'BluetoothPairAndConnectTest': self.proto_module
+                              .BluetoothPairAndConnectTestResult(),
+                          'BluetoothReconnectTest': self.proto_module
+                              .BluetoothReconnectTestResult(),
+                          'BluetoothThroughputTest': self.proto_module
+                              .BluetoothDataTestResult(),
+                          'BluetoothLatencyTest': self.proto_module
+                              .BluetoothDataTestResult(),
+                          'BtCodecSweepTest': self.proto_module
+                              .BluetoothAudioTestResult(),
+                          'BtRangeCodecTest': self.proto_module
+                              .BluetoothAudioTestResult(),
+                          }
+
+    @staticmethod
+    def get_configuration_data(device):
+        """Gets the configuration data of a device.
+
+        Gets the configuration data of a device and organizes it in a
+        dictionary.
+
+        Args:
+            device: The device object to get the configuration data from.
+
+        Returns:
+            A dictionary containing configuration data of a device.
+        """
+        # TODO(b/126931820): Genericize and move to lib when generic DUT interface is implemented
+        data = {'device_class': device.__class__.__name__}
+
+        if device.__class__.__name__ == 'AndroidDevice':
+            # TODO(b/124066126): Add remaining config data
+            data = {'device_class': 'phone',
+                    'device_model': device.model,
+                    'android_release_id': device.build_info['build_id'],
+                    'android_build_type': device.build_info['build_type'],
+                    'android_build_number': device.build_info[
+                        'incremental_build_id'],
+                    'android_branch_name': 'git_qt-release',
+                    'software_version': device.build_info['build_id']}
+
+        if device.__class__.__name__ == 'ParentDevice':
+            data = {'device_class': 'headset',
+                    'device_model': device.dut_type,
+                    'software_version': device.get_version()[1][
+                        'Fw Build Label'],
+                    'android_build_number': device.version}
+
+        return data
+
+    def add_config_data_to_proto(self, proto, pri_device, conn_device=None):
+        """Add to configuration data field of proto.
+
+        Adds test start time and device configuration info.
+        Args:
+            proto: protobuf to add configuration data to.
+            pri_device: some controller object.
+            conn_device: optional second controller object.
+        """
+        pri_device_proto = proto.configuration_data.primary_device
+        conn_device_proto = proto.configuration_data.connected_device
+        proto.configuration_data.test_date_time = self.start_time
+
+        pri_config = self.get_configuration_data(pri_device)
+
+        for metric in dir(pri_device_proto):
+            if metric in pri_config:
+                setattr(pri_device_proto, metric, pri_config[metric])
+
+        if conn_device:
+            conn_config = self.get_configuration_data(conn_device)
+
+            for metric in dir(conn_device_proto):
+                if metric in conn_config:
+                    setattr(conn_device_proto, metric, conn_config[metric])
+
+    def get_proto_dict(self, test, proto):
+        """Return dict with proto, readable ascii proto, and test name."""
+        return {'proto': base64.b64encode(ProtoMetric(test, proto)
+                                          .get_binary()).decode('utf-8'),
+                'proto_ascii': ProtoMetric(test, proto).get_ascii(),
+                'test_name': test}
+
+    def add_proto_to_results(self, proto, test):
+        """Adds proto as ProtoMetric object to self.results."""
+        self.results.append(ProtoMetric(test, proto))
+
+    def get_results(self, results, test, pri_device, conn_device=None):
+        """Gets the metrics associated with each test case.
+
+        Gets the test case metrics and configuration data for each test case and
+        stores them for publishing.
+
+        Args:
+            results: A dictionary containing test metrics.
+            test: The name of the test case associated with these results.
+            pri_device: The primary AndroidDevice object for the test.
+            conn_device: The connected AndroidDevice object for the test, if
+                applicable.
+
+        """
+
+        proto_result = self.proto_map[test]
+        recursive_assign(proto_result, results)
+        self.add_config_data_to_proto(proto_result, pri_device, conn_device)
+        self.add_proto_to_results(proto_result, test)
+        return self.get_proto_dict(test, proto_result)
+
+    def end(self, event):
+        return self.publisher.publish(self.results)
diff --git a/acts_tests/acts_contrib/test_utils/bt/loggers/protos/__init__.py b/acts_tests/acts_contrib/test_utils/bt/loggers/protos/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/loggers/protos/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/bt/loggers/protos/bluetooth_metric.proto b/acts_tests/acts_contrib/test_utils/bt/loggers/protos/bluetooth_metric.proto
new file mode 100644
index 0000000..572f277
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/loggers/protos/bluetooth_metric.proto
@@ -0,0 +1,104 @@
+syntax = "proto2";
+
+package wireless.android.platform.testing.bluetooth.metrics;
+
+message BluetoothTestDevice {
+  optional string device_class = 1;
+  optional string device_model = 2;
+  optional string hardware_version = 3;
+  optional string software_version = 4;
+  optional string android_build_type = 5;
+  optional string android_branch_name = 6;
+  optional string android_build_number = 7;
+  optional string android_release_id = 8;
+}
+
+message BluetoothContinuousTestResultHeader {
+  optional int64 test_date_time = 1;
+  optional BluetoothTestDevice primary_device = 2;
+  optional BluetoothTestDevice connected_device = 3;
+}
+
+message BluetoothReconnectTestResult {
+  optional BluetoothContinuousTestResultHeader configuration_data = 1;
+  optional int32 connection_attempt_count = 2;
+  optional int32 connection_successful_count = 3;
+  optional int32 connection_failed_count = 4;
+  optional int32 connection_max_time_millis = 5;
+  optional int32 connection_min_time_millis = 6;
+  optional int32 connection_avg_time_millis = 7;
+  optional int32 acl_connection_max_time_millis = 8;
+  optional int32 acl_connection_min_time_millis = 9;
+  optional int32 acl_connection_avg_time_millis = 10;
+}
+
+message BluetoothPairAndConnectTestResult {
+  optional BluetoothContinuousTestResultHeader configuration_data = 1;
+  optional int32 pair_attempt_count = 2;
+  optional int32 pair_successful_count = 3;
+  optional int32 pair_failed_count = 4;
+  optional int32 pair_max_time_millis = 5;
+  optional int32 pair_min_time_millis = 6;
+  optional int32 pair_avg_time_millis = 7;
+  optional int32 first_connection_max_time_millis = 8;
+  optional int32 first_connection_min_time_millis = 9;
+  optional int32 first_connection_avg_time_millis = 10;
+}
+
+message BluetoothA2dpCodecConfig {
+  enum BluetoothA2dpCodec {
+    SBC = 0;
+    AAC = 1;
+    APTX = 2;
+    APTX_HD = 3;
+    LDAC = 4;
+  }
+  optional BluetoothA2dpCodec codec_type = 1;
+  optional int32 sample_rate = 2;
+  optional int32 bits_per_sample = 3;
+  optional int32 channel_mode = 4;
+}
+
+message AudioTestDataPoint {
+  optional int64 timestamp_since_beginning_of_test_millis = 1;
+  optional int64 audio_streaming_duration_millis = 2;
+  optional int32 attenuation_db = 3;
+  optional float total_harmonic_distortion_plus_noise_percent = 4;
+  optional int32 audio_glitches_count = 5;
+}
+
+message BluetoothAudioTestResult {
+  optional BluetoothContinuousTestResultHeader configuration_data = 1;
+  enum AudioProfile {
+    A2DP = 0;
+    HFP = 1;
+    HAP = 2;
+  }
+  optional AudioProfile audio_profile = 2;
+  optional int32 audio_latency_min_millis = 3;
+  optional int32 audio_latency_max_millis = 4;
+  optional int32 audio_latency_avg_millis = 5;
+  optional int32 audio_glitches_count = 6;
+  optional int32 audio_missed_packets_count = 7;
+  optional float total_harmonic_distortion_plus_noise = 8;
+  optional int64 audio_streaming_duration_millis = 9;
+  optional BluetoothA2dpCodecConfig a2dp_codec_config = 10;
+  repeated AudioTestDataPoint data_points = 11;
+}
+
+message BluetoothDataTestResult {
+  optional BluetoothContinuousTestResultHeader configuration_data = 1;
+  enum DataTransferProtocol {
+    RFCOMM = 0;
+    L2CAP = 1;
+    LE_COC = 2;
+  }
+  optional DataTransferProtocol data_transfer_protocol = 2;
+  optional int32 data_latency_min_millis = 3;
+  optional int32 data_latency_max_millis = 4;
+  optional int32 data_latency_avg_millis = 5;
+  optional int32 data_throughput_min_bytes_per_second = 6;
+  optional int32 data_throughput_max_bytes_per_second = 7;
+  optional int32 data_throughput_avg_bytes_per_second = 8;
+  optional int32 data_packet_size = 9;
+}
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/bt/loggers/protos/bluetooth_metric_pb2.py b/acts_tests/acts_contrib/test_utils/bt/loggers/protos/bluetooth_metric_pb2.py
new file mode 100644
index 0000000..d3c0a9f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/loggers/protos/bluetooth_metric_pb2.py
@@ -0,0 +1,831 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: bluetooth_metric.proto
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='bluetooth_metric.proto',
+  package='wireless.android.platform.testing.bluetooth.metrics',
+  syntax='proto2',
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x16\x62luetooth_metric.proto\x12\x33wireless.android.platform.testing.bluetooth.metrics\"\xe8\x01\n\x13\x42luetoothTestDevice\x12\x14\n\x0c\x64\x65vice_class\x18\x01 \x01(\t\x12\x14\n\x0c\x64\x65vice_model\x18\x02 \x01(\t\x12\x18\n\x10hardware_version\x18\x03 \x01(\t\x12\x18\n\x10software_version\x18\x04 \x01(\t\x12\x1a\n\x12\x61ndroid_build_type\x18\x05 \x01(\t\x12\x1b\n\x13\x61ndroid_branch_name\x18\x06 \x01(\t\x12\x1c\n\x14\x61ndroid_build_number\x18\x07 \x01(\t\x12\x1a\n\x12\x61ndroid_release_id\x18\x08 \x01(\t\"\x83\x02\n#BluetoothContinuousTestResultHeader\x12\x16\n\x0etest_date_time\x18\x01 \x01(\x03\x12`\n\x0eprimary_device\x18\x02 \x01(\x0b\x32H.wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice\x12\x62\n\x10\x63onnected_device\x18\x03 \x01(\x0b\x32H.wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice\"\xe0\x03\n\x1c\x42luetoothReconnectTestResult\x12t\n\x12\x63onfiguration_data\x18\x01 \x01(\x0b\x32X.wireless.android.platform.testing.bluetooth.metrics.BluetoothContinuousTestResultHeader\x12 \n\x18\x63onnection_attempt_count\x18\x02 \x01(\x05\x12#\n\x1b\x63onnection_successful_count\x18\x03 \x01(\x05\x12\x1f\n\x17\x63onnection_failed_count\x18\x04 \x01(\x05\x12\"\n\x1a\x63onnection_max_time_millis\x18\x05 \x01(\x05\x12\"\n\x1a\x63onnection_min_time_millis\x18\x06 \x01(\x05\x12\"\n\x1a\x63onnection_avg_time_millis\x18\x07 \x01(\x05\x12&\n\x1e\x61\x63l_connection_max_time_millis\x18\x08 \x01(\x05\x12&\n\x1e\x61\x63l_connection_min_time_millis\x18\t \x01(\x05\x12&\n\x1e\x61\x63l_connection_avg_time_millis\x18\n \x01(\x05\"\xc7\x03\n!BluetoothPairAndConnectTestResult\x12t\n\x12\x63onfiguration_data\x18\x01 \x01(\x0b\x32X.wireless.android.platform.testing.bluetooth.metrics.BluetoothContinuousTestResultHeader\x12\x1a\n\x12pair_attempt_count\x18\x02 \x01(\x05\x12\x1d\n\x15pair_successful_count\x18\x03 \x01(\x05\x12\x19\n\x11pair_failed_count\x18\x04 \x01(\x05\x12\x1c\n\x14pair_max_time_millis\x18\x05 \x01(\x05\x12\x1c\n\x14pair_min_time_millis\x18\x06 \x01(\x05\x12\x1c\n\x14pair_avg_time_millis\x18\x07 \x01(\x05\x12(\n first_connection_max_time_millis\x18\x08 \x01(\x05\x12(\n first_connection_min_time_millis\x18\t \x01(\x05\x12(\n first_connection_avg_time_millis\x18\n \x01(\x05\"\x9d\x02\n\x18\x42luetoothA2dpCodecConfig\x12t\n\ncodec_type\x18\x01 \x01(\x0e\x32`.wireless.android.platform.testing.bluetooth.metrics.BluetoothA2dpCodecConfig.BluetoothA2dpCodec\x12\x13\n\x0bsample_rate\x18\x02 \x01(\x05\x12\x17\n\x0f\x62its_per_sample\x18\x03 \x01(\x05\x12\x14\n\x0c\x63hannel_mode\x18\x04 \x01(\x05\"G\n\x12\x42luetoothA2dpCodec\x12\x07\n\x03SBC\x10\x00\x12\x07\n\x03\x41\x41\x43\x10\x01\x12\x08\n\x04\x41PTX\x10\x02\x12\x0b\n\x07\x41PTX_HD\x10\x03\x12\x08\n\x04LDAC\x10\x04\"\xdb\x01\n\x12\x41udioTestDataPoint\x12\x30\n(timestamp_since_beginning_of_test_millis\x18\x01 \x01(\x03\x12\'\n\x1f\x61udio_streaming_duration_millis\x18\x02 \x01(\x03\x12\x16\n\x0e\x61ttenuation_db\x18\x03 \x01(\x05\x12\x34\n,total_harmonic_distortion_plus_noise_percent\x18\x04 \x01(\x02\x12\x1c\n\x14\x61udio_glitches_count\x18\x05 \x01(\x05\"\xf6\x05\n\x18\x42luetoothAudioTestResult\x12t\n\x12\x63onfiguration_data\x18\x01 \x01(\x0b\x32X.wireless.android.platform.testing.bluetooth.metrics.BluetoothContinuousTestResultHeader\x12q\n\raudio_profile\x18\x02 \x01(\x0e\x32Z.wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.AudioProfile\x12 \n\x18\x61udio_latency_min_millis\x18\x03 \x01(\x05\x12 \n\x18\x61udio_latency_max_millis\x18\x04 \x01(\x05\x12 \n\x18\x61udio_latency_avg_millis\x18\x05 \x01(\x05\x12\x1c\n\x14\x61udio_glitches_count\x18\x06 \x01(\x05\x12\"\n\x1a\x61udio_missed_packets_count\x18\x07 \x01(\x05\x12,\n$total_harmonic_distortion_plus_noise\x18\x08 \x01(\x02\x12\'\n\x1f\x61udio_streaming_duration_millis\x18\t \x01(\x03\x12h\n\x11\x61\x32\x64p_codec_config\x18\n \x01(\x0b\x32M.wireless.android.platform.testing.bluetooth.metrics.BluetoothA2dpCodecConfig\x12\\\n\x0b\x64\x61ta_points\x18\x0b \x03(\x0b\x32G.wireless.android.platform.testing.bluetooth.metrics.AudioTestDataPoint\"*\n\x0c\x41udioProfile\x12\x08\n\x04\x41\x32\x44P\x10\x00\x12\x07\n\x03HFP\x10\x01\x12\x07\n\x03HAP\x10\x02\"\xd5\x04\n\x17\x42luetoothDataTestResult\x12t\n\x12\x63onfiguration_data\x18\x01 \x01(\x0b\x32X.wireless.android.platform.testing.bluetooth.metrics.BluetoothContinuousTestResultHeader\x12\x81\x01\n\x16\x64\x61ta_transfer_protocol\x18\x02 \x01(\x0e\x32\x61.wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.DataTransferProtocol\x12\x1f\n\x17\x64\x61ta_latency_min_millis\x18\x03 \x01(\x05\x12\x1f\n\x17\x64\x61ta_latency_max_millis\x18\x04 \x01(\x05\x12\x1f\n\x17\x64\x61ta_latency_avg_millis\x18\x05 \x01(\x05\x12,\n$data_throughput_min_bytes_per_second\x18\x06 \x01(\x05\x12,\n$data_throughput_max_bytes_per_second\x18\x07 \x01(\x05\x12,\n$data_throughput_avg_bytes_per_second\x18\x08 \x01(\x05\x12\x18\n\x10\x64\x61ta_packet_size\x18\t \x01(\x05\"9\n\x14\x44\x61taTransferProtocol\x12\n\n\x06RFCOMM\x10\x00\x12\t\n\x05L2CAP\x10\x01\x12\n\n\x06LE_COC\x10\x02'
+)
+
+
+
+_BLUETOOTHA2DPCODECCONFIG_BLUETOOTHA2DPCODEC = _descriptor.EnumDescriptor(
+  name='BluetoothA2dpCodec',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothA2dpCodecConfig.BluetoothA2dpCodec',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='SBC', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='AAC', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='APTX', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='APTX_HD', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='LDAC', index=4, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=1732,
+  serialized_end=1803,
+)
+_sym_db.RegisterEnumDescriptor(_BLUETOOTHA2DPCODECCONFIG_BLUETOOTHA2DPCODEC)
+
+_BLUETOOTHAUDIOTESTRESULT_AUDIOPROFILE = _descriptor.EnumDescriptor(
+  name='AudioProfile',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.AudioProfile',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='A2DP', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HFP', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HAP', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2744,
+  serialized_end=2786,
+)
+_sym_db.RegisterEnumDescriptor(_BLUETOOTHAUDIOTESTRESULT_AUDIOPROFILE)
+
+_BLUETOOTHDATATESTRESULT_DATATRANSFERPROTOCOL = _descriptor.EnumDescriptor(
+  name='DataTransferProtocol',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.DataTransferProtocol',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='RFCOMM', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='L2CAP', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='LE_COC', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3329,
+  serialized_end=3386,
+)
+_sym_db.RegisterEnumDescriptor(_BLUETOOTHDATATESTRESULT_DATATRANSFERPROTOCOL)
+
+
+_BLUETOOTHTESTDEVICE = _descriptor.Descriptor(
+  name='BluetoothTestDevice',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='device_class', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice.device_class', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='device_model', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice.device_model', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='hardware_version', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice.hardware_version', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='software_version', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice.software_version', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='android_build_type', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice.android_build_type', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='android_branch_name', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice.android_branch_name', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='android_build_number', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice.android_build_number', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='android_release_id', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice.android_release_id', index=7,
+      number=8, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=80,
+  serialized_end=312,
+)
+
+
+_BLUETOOTHCONTINUOUSTESTRESULTHEADER = _descriptor.Descriptor(
+  name='BluetoothContinuousTestResultHeader',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothContinuousTestResultHeader',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='test_date_time', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothContinuousTestResultHeader.test_date_time', index=0,
+      number=1, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='primary_device', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothContinuousTestResultHeader.primary_device', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='connected_device', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothContinuousTestResultHeader.connected_device', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=315,
+  serialized_end=574,
+)
+
+
+_BLUETOOTHRECONNECTTESTRESULT = _descriptor.Descriptor(
+  name='BluetoothReconnectTestResult',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='configuration_data', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult.configuration_data', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='connection_attempt_count', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult.connection_attempt_count', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='connection_successful_count', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult.connection_successful_count', index=2,
+      number=3, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='connection_failed_count', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult.connection_failed_count', index=3,
+      number=4, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='connection_max_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult.connection_max_time_millis', index=4,
+      number=5, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='connection_min_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult.connection_min_time_millis', index=5,
+      number=6, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='connection_avg_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult.connection_avg_time_millis', index=6,
+      number=7, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='acl_connection_max_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult.acl_connection_max_time_millis', index=7,
+      number=8, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='acl_connection_min_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult.acl_connection_min_time_millis', index=8,
+      number=9, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='acl_connection_avg_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult.acl_connection_avg_time_millis', index=9,
+      number=10, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=577,
+  serialized_end=1057,
+)
+
+
+_BLUETOOTHPAIRANDCONNECTTESTRESULT = _descriptor.Descriptor(
+  name='BluetoothPairAndConnectTestResult',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='configuration_data', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult.configuration_data', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='pair_attempt_count', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult.pair_attempt_count', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='pair_successful_count', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult.pair_successful_count', index=2,
+      number=3, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='pair_failed_count', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult.pair_failed_count', index=3,
+      number=4, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='pair_max_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult.pair_max_time_millis', index=4,
+      number=5, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='pair_min_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult.pair_min_time_millis', index=5,
+      number=6, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='pair_avg_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult.pair_avg_time_millis', index=6,
+      number=7, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='first_connection_max_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult.first_connection_max_time_millis', index=7,
+      number=8, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='first_connection_min_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult.first_connection_min_time_millis', index=8,
+      number=9, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='first_connection_avg_time_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult.first_connection_avg_time_millis', index=9,
+      number=10, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1060,
+  serialized_end=1515,
+)
+
+
+_BLUETOOTHA2DPCODECCONFIG = _descriptor.Descriptor(
+  name='BluetoothA2dpCodecConfig',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothA2dpCodecConfig',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='codec_type', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothA2dpCodecConfig.codec_type', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='sample_rate', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothA2dpCodecConfig.sample_rate', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='bits_per_sample', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothA2dpCodecConfig.bits_per_sample', index=2,
+      number=3, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='channel_mode', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothA2dpCodecConfig.channel_mode', index=3,
+      number=4, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _BLUETOOTHA2DPCODECCONFIG_BLUETOOTHA2DPCODEC,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1518,
+  serialized_end=1803,
+)
+
+
+_AUDIOTESTDATAPOINT = _descriptor.Descriptor(
+  name='AudioTestDataPoint',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.AudioTestDataPoint',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='timestamp_since_beginning_of_test_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.AudioTestDataPoint.timestamp_since_beginning_of_test_millis', index=0,
+      number=1, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='audio_streaming_duration_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.AudioTestDataPoint.audio_streaming_duration_millis', index=1,
+      number=2, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='attenuation_db', full_name='wireless.android.platform.testing.bluetooth.metrics.AudioTestDataPoint.attenuation_db', index=2,
+      number=3, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='total_harmonic_distortion_plus_noise_percent', full_name='wireless.android.platform.testing.bluetooth.metrics.AudioTestDataPoint.total_harmonic_distortion_plus_noise_percent', index=3,
+      number=4, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='audio_glitches_count', full_name='wireless.android.platform.testing.bluetooth.metrics.AudioTestDataPoint.audio_glitches_count', index=4,
+      number=5, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1806,
+  serialized_end=2025,
+)
+
+
+_BLUETOOTHAUDIOTESTRESULT = _descriptor.Descriptor(
+  name='BluetoothAudioTestResult',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='configuration_data', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.configuration_data', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='audio_profile', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.audio_profile', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='audio_latency_min_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.audio_latency_min_millis', index=2,
+      number=3, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='audio_latency_max_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.audio_latency_max_millis', index=3,
+      number=4, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='audio_latency_avg_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.audio_latency_avg_millis', index=4,
+      number=5, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='audio_glitches_count', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.audio_glitches_count', index=5,
+      number=6, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='audio_missed_packets_count', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.audio_missed_packets_count', index=6,
+      number=7, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='total_harmonic_distortion_plus_noise', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.total_harmonic_distortion_plus_noise', index=7,
+      number=8, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='audio_streaming_duration_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.audio_streaming_duration_millis', index=8,
+      number=9, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='a2dp_codec_config', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.a2dp_codec_config', index=9,
+      number=10, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='data_points', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult.data_points', index=10,
+      number=11, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _BLUETOOTHAUDIOTESTRESULT_AUDIOPROFILE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2028,
+  serialized_end=2786,
+)
+
+
+_BLUETOOTHDATATESTRESULT = _descriptor.Descriptor(
+  name='BluetoothDataTestResult',
+  full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='configuration_data', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.configuration_data', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='data_transfer_protocol', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.data_transfer_protocol', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='data_latency_min_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.data_latency_min_millis', index=2,
+      number=3, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='data_latency_max_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.data_latency_max_millis', index=3,
+      number=4, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='data_latency_avg_millis', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.data_latency_avg_millis', index=4,
+      number=5, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='data_throughput_min_bytes_per_second', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.data_throughput_min_bytes_per_second', index=5,
+      number=6, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='data_throughput_max_bytes_per_second', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.data_throughput_max_bytes_per_second', index=6,
+      number=7, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='data_throughput_avg_bytes_per_second', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.data_throughput_avg_bytes_per_second', index=7,
+      number=8, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='data_packet_size', full_name='wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult.data_packet_size', index=8,
+      number=9, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _BLUETOOTHDATATESTRESULT_DATATRANSFERPROTOCOL,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2789,
+  serialized_end=3386,
+)
+
+_BLUETOOTHCONTINUOUSTESTRESULTHEADER.fields_by_name['primary_device'].message_type = _BLUETOOTHTESTDEVICE
+_BLUETOOTHCONTINUOUSTESTRESULTHEADER.fields_by_name['connected_device'].message_type = _BLUETOOTHTESTDEVICE
+_BLUETOOTHRECONNECTTESTRESULT.fields_by_name['configuration_data'].message_type = _BLUETOOTHCONTINUOUSTESTRESULTHEADER
+_BLUETOOTHPAIRANDCONNECTTESTRESULT.fields_by_name['configuration_data'].message_type = _BLUETOOTHCONTINUOUSTESTRESULTHEADER
+_BLUETOOTHA2DPCODECCONFIG.fields_by_name['codec_type'].enum_type = _BLUETOOTHA2DPCODECCONFIG_BLUETOOTHA2DPCODEC
+_BLUETOOTHA2DPCODECCONFIG_BLUETOOTHA2DPCODEC.containing_type = _BLUETOOTHA2DPCODECCONFIG
+_BLUETOOTHAUDIOTESTRESULT.fields_by_name['configuration_data'].message_type = _BLUETOOTHCONTINUOUSTESTRESULTHEADER
+_BLUETOOTHAUDIOTESTRESULT.fields_by_name['audio_profile'].enum_type = _BLUETOOTHAUDIOTESTRESULT_AUDIOPROFILE
+_BLUETOOTHAUDIOTESTRESULT.fields_by_name['a2dp_codec_config'].message_type = _BLUETOOTHA2DPCODECCONFIG
+_BLUETOOTHAUDIOTESTRESULT.fields_by_name['data_points'].message_type = _AUDIOTESTDATAPOINT
+_BLUETOOTHAUDIOTESTRESULT_AUDIOPROFILE.containing_type = _BLUETOOTHAUDIOTESTRESULT
+_BLUETOOTHDATATESTRESULT.fields_by_name['configuration_data'].message_type = _BLUETOOTHCONTINUOUSTESTRESULTHEADER
+_BLUETOOTHDATATESTRESULT.fields_by_name['data_transfer_protocol'].enum_type = _BLUETOOTHDATATESTRESULT_DATATRANSFERPROTOCOL
+_BLUETOOTHDATATESTRESULT_DATATRANSFERPROTOCOL.containing_type = _BLUETOOTHDATATESTRESULT
+DESCRIPTOR.message_types_by_name['BluetoothTestDevice'] = _BLUETOOTHTESTDEVICE
+DESCRIPTOR.message_types_by_name['BluetoothContinuousTestResultHeader'] = _BLUETOOTHCONTINUOUSTESTRESULTHEADER
+DESCRIPTOR.message_types_by_name['BluetoothReconnectTestResult'] = _BLUETOOTHRECONNECTTESTRESULT
+DESCRIPTOR.message_types_by_name['BluetoothPairAndConnectTestResult'] = _BLUETOOTHPAIRANDCONNECTTESTRESULT
+DESCRIPTOR.message_types_by_name['BluetoothA2dpCodecConfig'] = _BLUETOOTHA2DPCODECCONFIG
+DESCRIPTOR.message_types_by_name['AudioTestDataPoint'] = _AUDIOTESTDATAPOINT
+DESCRIPTOR.message_types_by_name['BluetoothAudioTestResult'] = _BLUETOOTHAUDIOTESTRESULT
+DESCRIPTOR.message_types_by_name['BluetoothDataTestResult'] = _BLUETOOTHDATATESTRESULT
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+BluetoothTestDevice = _reflection.GeneratedProtocolMessageType('BluetoothTestDevice', (_message.Message,), {
+  'DESCRIPTOR' : _BLUETOOTHTESTDEVICE,
+  '__module__' : 'bluetooth_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.bluetooth.metrics.BluetoothTestDevice)
+  })
+_sym_db.RegisterMessage(BluetoothTestDevice)
+
+BluetoothContinuousTestResultHeader = _reflection.GeneratedProtocolMessageType('BluetoothContinuousTestResultHeader', (_message.Message,), {
+  'DESCRIPTOR' : _BLUETOOTHCONTINUOUSTESTRESULTHEADER,
+  '__module__' : 'bluetooth_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.bluetooth.metrics.BluetoothContinuousTestResultHeader)
+  })
+_sym_db.RegisterMessage(BluetoothContinuousTestResultHeader)
+
+BluetoothReconnectTestResult = _reflection.GeneratedProtocolMessageType('BluetoothReconnectTestResult', (_message.Message,), {
+  'DESCRIPTOR' : _BLUETOOTHRECONNECTTESTRESULT,
+  '__module__' : 'bluetooth_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.bluetooth.metrics.BluetoothReconnectTestResult)
+  })
+_sym_db.RegisterMessage(BluetoothReconnectTestResult)
+
+BluetoothPairAndConnectTestResult = _reflection.GeneratedProtocolMessageType('BluetoothPairAndConnectTestResult', (_message.Message,), {
+  'DESCRIPTOR' : _BLUETOOTHPAIRANDCONNECTTESTRESULT,
+  '__module__' : 'bluetooth_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.bluetooth.metrics.BluetoothPairAndConnectTestResult)
+  })
+_sym_db.RegisterMessage(BluetoothPairAndConnectTestResult)
+
+BluetoothA2dpCodecConfig = _reflection.GeneratedProtocolMessageType('BluetoothA2dpCodecConfig', (_message.Message,), {
+  'DESCRIPTOR' : _BLUETOOTHA2DPCODECCONFIG,
+  '__module__' : 'bluetooth_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.bluetooth.metrics.BluetoothA2dpCodecConfig)
+  })
+_sym_db.RegisterMessage(BluetoothA2dpCodecConfig)
+
+AudioTestDataPoint = _reflection.GeneratedProtocolMessageType('AudioTestDataPoint', (_message.Message,), {
+  'DESCRIPTOR' : _AUDIOTESTDATAPOINT,
+  '__module__' : 'bluetooth_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.bluetooth.metrics.AudioTestDataPoint)
+  })
+_sym_db.RegisterMessage(AudioTestDataPoint)
+
+BluetoothAudioTestResult = _reflection.GeneratedProtocolMessageType('BluetoothAudioTestResult', (_message.Message,), {
+  'DESCRIPTOR' : _BLUETOOTHAUDIOTESTRESULT,
+  '__module__' : 'bluetooth_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.bluetooth.metrics.BluetoothAudioTestResult)
+  })
+_sym_db.RegisterMessage(BluetoothAudioTestResult)
+
+BluetoothDataTestResult = _reflection.GeneratedProtocolMessageType('BluetoothDataTestResult', (_message.Message,), {
+  'DESCRIPTOR' : _BLUETOOTHDATATESTRESULT,
+  '__module__' : 'bluetooth_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.bluetooth.metrics.BluetoothDataTestResult)
+  })
+_sym_db.RegisterMessage(BluetoothDataTestResult)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/acts_tests/acts_contrib/test_utils/bt/native_bt_test_utils.py b/acts_tests/acts_contrib/test_utils/bt/native_bt_test_utils.py
new file mode 100644
index 0000000..2568249
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/native_bt_test_utils.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import logging
+
+from subprocess import call
+import time
+
+log = logging
+
+
+def setup_native_bluetooth(native_devices):
+    for n in native_devices:
+        droid = n.droid
+        pid = n.adb.shell("pidof -s bluetoothtbd")
+        if not pid:
+            call(
+                ["adb -s " + n.serial + " shell sh -c \"bluetoothtbd\" &"],
+                shell=True)
+        droid.BtBinderInitInterface()
+        time.sleep(5)  #temporary sleep statement
+        droid.BtBinderEnable()
+        time.sleep(5)  #temporary sleep statement
+        droid.BtBinderRegisterBLE()
+        time.sleep(5)  #temporary sleep statement
diff --git a/acts_tests/acts_contrib/test_utils/bt/protos/__init__.py b/acts_tests/acts_contrib/test_utils/bt/protos/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/protos/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/bt/protos/bluetooth.proto b/acts_tests/acts_contrib/test_utils/bt/protos/bluetooth.proto
new file mode 100644
index 0000000..46ab968
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/protos/bluetooth.proto
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 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.
+ */
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+// C++ namespace: bluetooth::metrics::BluetoothMetricsProto
+package bluetooth.metrics.BluetoothMetricsProto;
+
+option java_package = "com.android.bluetooth";
+option java_outer_classname = "BluetoothMetricsProto";
+
+message BluetoothLog {
+  // Session information that gets logged for every BT connection.
+  repeated BluetoothSession session = 1;
+
+  // Session information that gets logged for every Pair event.
+  repeated PairEvent pair_event = 2;
+
+  // Information for Wake locks.
+  repeated WakeEvent wake_event = 3;
+
+  // Scan event information.
+  repeated ScanEvent scan_event = 4;
+
+  // Number of bonded devices.
+  optional int32 num_bonded_devices = 5;
+
+  // Number of BluetoothSession including discarded ones beyond capacity
+  optional int64 num_bluetooth_session = 6;
+
+  // Number of PairEvent including discarded ones beyond capacity
+  optional int64 num_pair_event = 7;
+
+  // Number of WakeEvent including discarded ones beyond capacity
+  optional int64 num_wake_event = 8;
+
+  // Number of ScanEvent including discarded ones beyond capacity
+  optional int64 num_scan_event = 9;
+
+  // Statistics about Bluetooth profile connections
+  repeated ProfileConnectionStats profile_connection_stats = 10;
+
+  // Statistics about Headset profile connections
+  repeated HeadsetProfileConnectionStats headset_profile_connection_stats = 11;
+}
+
+// The information about the device.
+message DeviceInfo {
+  // Device type.
+  enum DeviceType {
+    // Type is unknown.
+    DEVICE_TYPE_UNKNOWN = 0;
+
+    DEVICE_TYPE_BREDR = 1;
+
+    DEVICE_TYPE_LE = 2;
+
+    DEVICE_TYPE_DUMO = 3;
+  }
+
+  // Device class
+  // https://cs.corp.google.com/#android/system/bt/stack/include/btm_api.h&q=major_computer.
+  optional int32 device_class = 1;
+
+  // Device type.
+  optional DeviceType device_type = 2;
+}
+
+// Information that gets logged for every Bluetooth connection.
+message BluetoothSession {
+  // Type of technology used in the connection.
+  enum ConnectionTechnologyType {
+    CONNECTION_TECHNOLOGY_TYPE_UNKNOWN = 0;
+
+    CONNECTION_TECHNOLOGY_TYPE_LE = 1;
+
+    CONNECTION_TECHNOLOGY_TYPE_BREDR = 2;
+  }
+
+  enum DisconnectReasonType {
+    UNKNOWN = 0;
+
+    // A metrics dump takes a snapshot of current Bluetooth session and thus
+    // is not a real disconnect, but a discontinuation in metrics logging.
+    // This enum indicates this situation.
+    METRICS_DUMP = 1;
+
+    NEXT_START_WITHOUT_END_PREVIOUS = 2;
+  }
+
+  // Duration of the session.
+  optional int64 session_duration_sec = 2;
+
+  // Technology type.
+  optional ConnectionTechnologyType connection_technology_type = 3;
+
+  // Reason for disconnecting.
+  optional string disconnect_reason = 4 [deprecated = true];
+
+  // The information about the device which it is connected to.
+  optional DeviceInfo device_connected_to = 5;
+
+  // The information about the RFComm session.
+  optional RFCommSession rfcomm_session = 6;
+
+  // The information about the A2DP audio session.
+  optional A2DPSession a2dp_session = 7;
+
+  // Numeric reason for disconnecting as defined in metrics.h
+  optional DisconnectReasonType disconnect_reason_type = 8;
+}
+
+message RFCommSession {
+  // bytes transmitted.
+  optional int32 rx_bytes = 1;
+
+  // bytes transmitted.
+  optional int32 tx_bytes = 2;
+}
+
+enum A2dpSourceCodec {
+  A2DP_SOURCE_CODEC_UNKNOWN = 0;
+  A2DP_SOURCE_CODEC_SBC = 1;
+  A2DP_SOURCE_CODEC_AAC = 2;
+  A2DP_SOURCE_CODEC_APTX = 3;
+  A2DP_SOURCE_CODEC_APTX_HD = 4;
+  A2DP_SOURCE_CODEC_LDAC = 5;
+}
+
+// Session information that gets logged for A2DP session.
+message A2DPSession {
+  // Media timer in milliseconds.
+  optional int32 media_timer_min_millis = 1;
+
+  // Media timer in milliseconds.
+  optional int32 media_timer_max_millis = 2;
+
+  // Media timer in milliseconds.
+  optional int32 media_timer_avg_millis = 3;
+
+  // Buffer overruns count.
+  optional int32 buffer_overruns_max_count = 4;
+
+  // Buffer overruns total.
+  optional int32 buffer_overruns_total = 5;
+
+  // Buffer underruns average.
+  optional float buffer_underruns_average = 6;
+
+  // Buffer underruns count.
+  optional int32 buffer_underruns_count = 7;
+
+  // Total audio time in this A2DP session
+  optional int64 audio_duration_millis = 8;
+
+  // Audio codec used in this A2DP session in A2DP source role
+  optional A2dpSourceCodec source_codec = 9;
+
+  // Whether A2DP offload is enabled in this A2DP session
+  optional bool is_a2dp_offload = 10;
+}
+
+message PairEvent {
+  // The reason for disconnecting
+  // See: system/bt/stack/include/hcidefs.h, HCI_ERR_CONN_FAILED_ESTABLISHMENT
+  optional int32 disconnect_reason = 1;
+
+  // Pair event time
+  optional int64 event_time_millis =
+      2;  // [(datapol.semantic_type) = ST_TIMESTAMP];
+
+  // The information about the device which it is paired to.
+  optional DeviceInfo device_paired_with = 3;
+}
+
+message WakeEvent {
+  // Information about the wake event type.
+  enum WakeEventType {
+    UNKNOWN = 0;
+    // WakeLock was acquired.
+    ACQUIRED = 1;
+    // WakeLock was released.
+    RELEASED = 2;
+  }
+
+  // Information about the wake event type.
+  optional WakeEventType wake_event_type = 1;
+
+  // Initiator of the scan. Only the first three names will be stored.
+  // e.g. com.company.app
+  optional string requestor = 2;
+
+  // Name of the wakelock (e.g. bluedroid_timer).
+  optional string name = 3;
+
+  // Time of the event.
+  optional int64 event_time_millis =
+      4;  // [(datapol.semantic_type) = ST_TIMESTAMP];
+}
+
+message ScanEvent {
+  // Scan type.
+  enum ScanTechnologyType {
+    SCAN_TYPE_UNKNOWN = 0;
+
+    SCAN_TECH_TYPE_LE = 1;
+
+    SCAN_TECH_TYPE_BREDR = 2;
+
+    SCAN_TECH_TYPE_BOTH = 3;
+  }
+
+  // Scan event type.
+  enum ScanEventType {
+    // Scan started.
+    SCAN_EVENT_START = 0;
+    // Scan stopped.
+    SCAN_EVENT_STOP = 1;
+  }
+
+  // Scan event type.
+  optional ScanEventType scan_event_type = 1;
+
+  // Initiator of the scan. Only the first three names will be stored.
+  // e.g. com.company.app
+  optional string initiator = 2;
+
+  // Technology used for scanning.
+  optional ScanTechnologyType scan_technology_type = 3;
+
+  // Number of results returned.
+  optional int32 number_results = 4;
+
+  // Time of the event.
+  optional int64 event_time_millis =
+      5;  // [(datapol.semantic_type) = ST_TIMESTAMP];
+}
+
+// Profile IDs defined in BluetoothProfile API class
+// Values must match API class values
+enum ProfileId {
+  PROFILE_UNKNOWN = 0;
+  HEADSET = 1;
+  A2DP = 2;
+  HEALTH = 3;
+  HID_HOST = 4;
+  PAN = 5;
+  PBAP = 6;
+  GATT = 7;
+  GATT_SERVER = 8;
+  MAP = 9;
+  SAP = 10;
+  A2DP_SINK = 11;
+  AVRCP_CONTROLLER = 12;
+  AVRCP = 13;
+  HEADSET_CLIENT = 16;
+  PBAP_CLIENT = 17;
+  MAP_CLIENT = 18;
+  HID_DEVICE = 19;
+  OPP = 20;
+  HEARING_AID = 21;
+}
+
+// Statistics about Bluetooth profile connections
+message ProfileConnectionStats {
+  // Profile id defined in BluetoothProfile.java
+  optional ProfileId profile_id = 1;
+
+  // Number of times that this profile is connected since last metrics dump
+  optional int32 num_times_connected = 2;
+}
+
+enum HeadsetProfileType {
+  HEADSET_PROFILE_UNKNOWN = 0;
+  HSP = 1;
+  HFP = 2;
+}
+
+// Statistics about headset profile connections
+message HeadsetProfileConnectionStats {
+  // Type of headset profile connected
+  optional HeadsetProfileType headset_profile_type = 1;
+
+  // Number of times this type of headset profile is connected
+  optional int32 num_times_connected = 2;
+}
diff --git a/acts_tests/acts_contrib/test_utils/bt/protos/bluetooth_pb2.py b/acts_tests/acts_contrib/test_utils/bt/protos/bluetooth_pb2.py
new file mode 100644
index 0000000..bce90e4
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/protos/bluetooth_pb2.py
@@ -0,0 +1,1140 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: bluetooth.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='bluetooth.proto',
+  package='bluetooth.metrics.BluetoothMetricsProto',
+  syntax='proto2',
+  serialized_options=b'\n\025com.android.bluetoothB\025BluetoothMetricsProtoH\003',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x0f\x62luetooth.proto\x12\'bluetooth.metrics.BluetoothMetricsProto\"\x8a\x05\n\x0c\x42luetoothLog\x12J\n\x07session\x18\x01 \x03(\x0b\x32\x39.bluetooth.metrics.BluetoothMetricsProto.BluetoothSession\x12\x46\n\npair_event\x18\x02 \x03(\x0b\x32\x32.bluetooth.metrics.BluetoothMetricsProto.PairEvent\x12\x46\n\nwake_event\x18\x03 \x03(\x0b\x32\x32.bluetooth.metrics.BluetoothMetricsProto.WakeEvent\x12\x46\n\nscan_event\x18\x04 \x03(\x0b\x32\x32.bluetooth.metrics.BluetoothMetricsProto.ScanEvent\x12\x1a\n\x12num_bonded_devices\x18\x05 \x01(\x05\x12\x1d\n\x15num_bluetooth_session\x18\x06 \x01(\x03\x12\x16\n\x0enum_pair_event\x18\x07 \x01(\x03\x12\x16\n\x0enum_wake_event\x18\x08 \x01(\x03\x12\x16\n\x0enum_scan_event\x18\t \x01(\x03\x12\x61\n\x18profile_connection_stats\x18\n \x03(\x0b\x32?.bluetooth.metrics.BluetoothMetricsProto.ProfileConnectionStats\x12p\n headset_profile_connection_stats\x18\x0b \x03(\x0b\x32\x46.bluetooth.metrics.BluetoothMetricsProto.HeadsetProfileConnectionStats\"\xdf\x01\n\nDeviceInfo\x12\x14\n\x0c\x64\x65vice_class\x18\x01 \x01(\x05\x12S\n\x0b\x64\x65vice_type\x18\x02 \x01(\x0e\x32>.bluetooth.metrics.BluetoothMetricsProto.DeviceInfo.DeviceType\"f\n\nDeviceType\x12\x17\n\x13\x44\x45VICE_TYPE_UNKNOWN\x10\x00\x12\x15\n\x11\x44\x45VICE_TYPE_BREDR\x10\x01\x12\x12\n\x0e\x44\x45VICE_TYPE_LE\x10\x02\x12\x14\n\x10\x44\x45VICE_TYPE_DUMO\x10\x03\"\x8f\x06\n\x10\x42luetoothSession\x12\x1c\n\x14session_duration_sec\x18\x02 \x01(\x03\x12v\n\x1a\x63onnection_technology_type\x18\x03 \x01(\x0e\x32R.bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.ConnectionTechnologyType\x12\x1d\n\x11\x64isconnect_reason\x18\x04 \x01(\tB\x02\x18\x01\x12P\n\x13\x64\x65vice_connected_to\x18\x05 \x01(\x0b\x32\x33.bluetooth.metrics.BluetoothMetricsProto.DeviceInfo\x12N\n\x0erfcomm_session\x18\x06 \x01(\x0b\x32\x36.bluetooth.metrics.BluetoothMetricsProto.RFCommSession\x12J\n\x0c\x61\x32\x64p_session\x18\x07 \x01(\x0b\x32\x34.bluetooth.metrics.BluetoothMetricsProto.A2DPSession\x12n\n\x16\x64isconnect_reason_type\x18\x08 \x01(\x0e\x32N.bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.DisconnectReasonType\"\x8b\x01\n\x18\x43onnectionTechnologyType\x12&\n\"CONNECTION_TECHNOLOGY_TYPE_UNKNOWN\x10\x00\x12!\n\x1d\x43ONNECTION_TECHNOLOGY_TYPE_LE\x10\x01\x12$\n CONNECTION_TECHNOLOGY_TYPE_BREDR\x10\x02\"Z\n\x14\x44isconnectReasonType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x10\n\x0cMETRICS_DUMP\x10\x01\x12#\n\x1fNEXT_START_WITHOUT_END_PREVIOUS\x10\x02\"3\n\rRFCommSession\x12\x10\n\x08rx_bytes\x18\x01 \x01(\x05\x12\x10\n\x08tx_bytes\x18\x02 \x01(\x05\"\xf9\x02\n\x0b\x41\x32\x44PSession\x12\x1e\n\x16media_timer_min_millis\x18\x01 \x01(\x05\x12\x1e\n\x16media_timer_max_millis\x18\x02 \x01(\x05\x12\x1e\n\x16media_timer_avg_millis\x18\x03 \x01(\x05\x12!\n\x19\x62uffer_overruns_max_count\x18\x04 \x01(\x05\x12\x1d\n\x15\x62uffer_overruns_total\x18\x05 \x01(\x05\x12 \n\x18\x62uffer_underruns_average\x18\x06 \x01(\x02\x12\x1e\n\x16\x62uffer_underruns_count\x18\x07 \x01(\x05\x12\x1d\n\x15\x61udio_duration_millis\x18\x08 \x01(\x03\x12N\n\x0csource_codec\x18\t \x01(\x0e\x32\x38.bluetooth.metrics.BluetoothMetricsProto.A2dpSourceCodec\x12\x17\n\x0fis_a2dp_offload\x18\n \x01(\x08\"\x92\x01\n\tPairEvent\x12\x19\n\x11\x64isconnect_reason\x18\x01 \x01(\x05\x12\x19\n\x11\x65vent_time_millis\x18\x02 \x01(\x03\x12O\n\x12\x64\x65vice_paired_with\x18\x03 \x01(\x0b\x32\x33.bluetooth.metrics.BluetoothMetricsProto.DeviceInfo\"\xdc\x01\n\tWakeEvent\x12Y\n\x0fwake_event_type\x18\x01 \x01(\x0e\x32@.bluetooth.metrics.BluetoothMetricsProto.WakeEvent.WakeEventType\x12\x11\n\trequestor\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x19\n\x11\x65vent_time_millis\x18\x04 \x01(\x03\"8\n\rWakeEventType\x12\x0b\n\x07UNKNOWN\x10\x00\x12\x0c\n\x08\x41\x43QUIRED\x10\x01\x12\x0c\n\x08RELEASED\x10\x02\"\xc4\x03\n\tScanEvent\x12Y\n\x0fscan_event_type\x18\x01 \x01(\x0e\x32@.bluetooth.metrics.BluetoothMetricsProto.ScanEvent.ScanEventType\x12\x11\n\tinitiator\x18\x02 \x01(\t\x12\x63\n\x14scan_technology_type\x18\x03 \x01(\x0e\x32\x45.bluetooth.metrics.BluetoothMetricsProto.ScanEvent.ScanTechnologyType\x12\x16\n\x0enumber_results\x18\x04 \x01(\x05\x12\x19\n\x11\x65vent_time_millis\x18\x05 \x01(\x03\"u\n\x12ScanTechnologyType\x12\x15\n\x11SCAN_TYPE_UNKNOWN\x10\x00\x12\x15\n\x11SCAN_TECH_TYPE_LE\x10\x01\x12\x18\n\x14SCAN_TECH_TYPE_BREDR\x10\x02\x12\x17\n\x13SCAN_TECH_TYPE_BOTH\x10\x03\":\n\rScanEventType\x12\x14\n\x10SCAN_EVENT_START\x10\x00\x12\x13\n\x0fSCAN_EVENT_STOP\x10\x01\"}\n\x16ProfileConnectionStats\x12\x46\n\nprofile_id\x18\x01 \x01(\x0e\x32\x32.bluetooth.metrics.BluetoothMetricsProto.ProfileId\x12\x1b\n\x13num_times_connected\x18\x02 \x01(\x05\"\x97\x01\n\x1dHeadsetProfileConnectionStats\x12Y\n\x14headset_profile_type\x18\x01 \x01(\x0e\x32;.bluetooth.metrics.BluetoothMetricsProto.HeadsetProfileType\x12\x1b\n\x13num_times_connected\x18\x02 \x01(\x05*\xbd\x01\n\x0f\x41\x32\x64pSourceCodec\x12\x1d\n\x19\x41\x32\x44P_SOURCE_CODEC_UNKNOWN\x10\x00\x12\x19\n\x15\x41\x32\x44P_SOURCE_CODEC_SBC\x10\x01\x12\x19\n\x15\x41\x32\x44P_SOURCE_CODEC_AAC\x10\x02\x12\x1a\n\x16\x41\x32\x44P_SOURCE_CODEC_APTX\x10\x03\x12\x1d\n\x19\x41\x32\x44P_SOURCE_CODEC_APTX_HD\x10\x04\x12\x1a\n\x16\x41\x32\x44P_SOURCE_CODEC_LDAC\x10\x05*\xa0\x02\n\tProfileId\x12\x13\n\x0fPROFILE_UNKNOWN\x10\x00\x12\x0b\n\x07HEADSET\x10\x01\x12\x08\n\x04\x41\x32\x44P\x10\x02\x12\n\n\x06HEALTH\x10\x03\x12\x0c\n\x08HID_HOST\x10\x04\x12\x07\n\x03PAN\x10\x05\x12\x08\n\x04PBAP\x10\x06\x12\x08\n\x04GATT\x10\x07\x12\x0f\n\x0bGATT_SERVER\x10\x08\x12\x07\n\x03MAP\x10\t\x12\x07\n\x03SAP\x10\n\x12\r\n\tA2DP_SINK\x10\x0b\x12\x14\n\x10\x41VRCP_CONTROLLER\x10\x0c\x12\t\n\x05\x41VRCP\x10\r\x12\x12\n\x0eHEADSET_CLIENT\x10\x10\x12\x0f\n\x0bPBAP_CLIENT\x10\x11\x12\x0e\n\nMAP_CLIENT\x10\x12\x12\x0e\n\nHID_DEVICE\x10\x13\x12\x07\n\x03OPP\x10\x14\x12\x0f\n\x0bHEARING_AID\x10\x15*C\n\x12HeadsetProfileType\x12\x1b\n\x17HEADSET_PROFILE_UNKNOWN\x10\x00\x12\x07\n\x03HSP\x10\x01\x12\x07\n\x03HFP\x10\x02\x42\x30\n\x15\x63om.android.bluetoothB\x15\x42luetoothMetricsProtoH\x03'
+)
+
+_A2DPSOURCECODEC = _descriptor.EnumDescriptor(
+  name='A2dpSourceCodec',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.A2dpSourceCodec',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='A2DP_SOURCE_CODEC_UNKNOWN', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='A2DP_SOURCE_CODEC_SBC', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='A2DP_SOURCE_CODEC_AAC', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='A2DP_SOURCE_CODEC_APTX', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='A2DP_SOURCE_CODEC_APTX_HD', index=4, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='A2DP_SOURCE_CODEC_LDAC', index=5, number=5,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3267,
+  serialized_end=3456,
+)
+_sym_db.RegisterEnumDescriptor(_A2DPSOURCECODEC)
+
+A2dpSourceCodec = enum_type_wrapper.EnumTypeWrapper(_A2DPSOURCECODEC)
+_PROFILEID = _descriptor.EnumDescriptor(
+  name='ProfileId',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.ProfileId',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='PROFILE_UNKNOWN', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HEADSET', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='A2DP', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HEALTH', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HID_HOST', index=4, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='PAN', index=5, number=5,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='PBAP', index=6, number=6,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='GATT', index=7, number=7,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='GATT_SERVER', index=8, number=8,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='MAP', index=9, number=9,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='SAP', index=10, number=10,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='A2DP_SINK', index=11, number=11,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='AVRCP_CONTROLLER', index=12, number=12,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='AVRCP', index=13, number=13,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HEADSET_CLIENT', index=14, number=16,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='PBAP_CLIENT', index=15, number=17,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='MAP_CLIENT', index=16, number=18,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HID_DEVICE', index=17, number=19,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='OPP', index=18, number=20,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HEARING_AID', index=19, number=21,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3459,
+  serialized_end=3747,
+)
+_sym_db.RegisterEnumDescriptor(_PROFILEID)
+
+ProfileId = enum_type_wrapper.EnumTypeWrapper(_PROFILEID)
+_HEADSETPROFILETYPE = _descriptor.EnumDescriptor(
+  name='HeadsetProfileType',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.HeadsetProfileType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='HEADSET_PROFILE_UNKNOWN', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HSP', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HFP', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3749,
+  serialized_end=3816,
+)
+_sym_db.RegisterEnumDescriptor(_HEADSETPROFILETYPE)
+
+HeadsetProfileType = enum_type_wrapper.EnumTypeWrapper(_HEADSETPROFILETYPE)
+A2DP_SOURCE_CODEC_UNKNOWN = 0
+A2DP_SOURCE_CODEC_SBC = 1
+A2DP_SOURCE_CODEC_AAC = 2
+A2DP_SOURCE_CODEC_APTX = 3
+A2DP_SOURCE_CODEC_APTX_HD = 4
+A2DP_SOURCE_CODEC_LDAC = 5
+PROFILE_UNKNOWN = 0
+HEADSET = 1
+A2DP = 2
+HEALTH = 3
+HID_HOST = 4
+PAN = 5
+PBAP = 6
+GATT = 7
+GATT_SERVER = 8
+MAP = 9
+SAP = 10
+A2DP_SINK = 11
+AVRCP_CONTROLLER = 12
+AVRCP = 13
+HEADSET_CLIENT = 16
+PBAP_CLIENT = 17
+MAP_CLIENT = 18
+HID_DEVICE = 19
+OPP = 20
+HEARING_AID = 21
+HEADSET_PROFILE_UNKNOWN = 0
+HSP = 1
+HFP = 2
+
+
+_DEVICEINFO_DEVICETYPE = _descriptor.EnumDescriptor(
+  name='DeviceType',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.DeviceInfo.DeviceType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='DEVICE_TYPE_UNKNOWN', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='DEVICE_TYPE_BREDR', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='DEVICE_TYPE_LE', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='DEVICE_TYPE_DUMO', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=835,
+  serialized_end=937,
+)
+_sym_db.RegisterEnumDescriptor(_DEVICEINFO_DEVICETYPE)
+
+_BLUETOOTHSESSION_CONNECTIONTECHNOLOGYTYPE = _descriptor.EnumDescriptor(
+  name='ConnectionTechnologyType',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.ConnectionTechnologyType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='CONNECTION_TECHNOLOGY_TYPE_UNKNOWN', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='CONNECTION_TECHNOLOGY_TYPE_LE', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='CONNECTION_TECHNOLOGY_TYPE_BREDR', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=1492,
+  serialized_end=1631,
+)
+_sym_db.RegisterEnumDescriptor(_BLUETOOTHSESSION_CONNECTIONTECHNOLOGYTYPE)
+
+_BLUETOOTHSESSION_DISCONNECTREASONTYPE = _descriptor.EnumDescriptor(
+  name='DisconnectReasonType',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.DisconnectReasonType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNKNOWN', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='METRICS_DUMP', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NEXT_START_WITHOUT_END_PREVIOUS', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=1633,
+  serialized_end=1723,
+)
+_sym_db.RegisterEnumDescriptor(_BLUETOOTHSESSION_DISCONNECTREASONTYPE)
+
+_WAKEEVENT_WAKEEVENTTYPE = _descriptor.EnumDescriptor(
+  name='WakeEventType',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.WakeEvent.WakeEventType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNKNOWN', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='ACQUIRED', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='RELEASED', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2472,
+  serialized_end=2528,
+)
+_sym_db.RegisterEnumDescriptor(_WAKEEVENT_WAKEEVENTTYPE)
+
+_SCANEVENT_SCANTECHNOLOGYTYPE = _descriptor.EnumDescriptor(
+  name='ScanTechnologyType',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.ScanEvent.ScanTechnologyType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='SCAN_TYPE_UNKNOWN', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='SCAN_TECH_TYPE_LE', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='SCAN_TECH_TYPE_BREDR', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='SCAN_TECH_TYPE_BOTH', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2806,
+  serialized_end=2923,
+)
+_sym_db.RegisterEnumDescriptor(_SCANEVENT_SCANTECHNOLOGYTYPE)
+
+_SCANEVENT_SCANEVENTTYPE = _descriptor.EnumDescriptor(
+  name='ScanEventType',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.ScanEvent.ScanEventType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='SCAN_EVENT_START', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='SCAN_EVENT_STOP', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2925,
+  serialized_end=2983,
+)
+_sym_db.RegisterEnumDescriptor(_SCANEVENT_SCANEVENTTYPE)
+
+
+_BLUETOOTHLOG = _descriptor.Descriptor(
+  name='BluetoothLog',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='session', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.session', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='pair_event', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.pair_event', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='wake_event', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.wake_event', index=2,
+      number=3, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='scan_event', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.scan_event', index=3,
+      number=4, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='num_bonded_devices', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.num_bonded_devices', index=4,
+      number=5, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='num_bluetooth_session', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.num_bluetooth_session', index=5,
+      number=6, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='num_pair_event', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.num_pair_event', index=6,
+      number=7, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='num_wake_event', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.num_wake_event', index=7,
+      number=8, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='num_scan_event', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.num_scan_event', index=8,
+      number=9, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='profile_connection_stats', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.profile_connection_stats', index=9,
+      number=10, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='headset_profile_connection_stats', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothLog.headset_profile_connection_stats', index=10,
+      number=11, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=61,
+  serialized_end=711,
+)
+
+
+_DEVICEINFO = _descriptor.Descriptor(
+  name='DeviceInfo',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.DeviceInfo',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='device_class', full_name='bluetooth.metrics.BluetoothMetricsProto.DeviceInfo.device_class', index=0,
+      number=1, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='device_type', full_name='bluetooth.metrics.BluetoothMetricsProto.DeviceInfo.device_type', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _DEVICEINFO_DEVICETYPE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=714,
+  serialized_end=937,
+)
+
+
+_BLUETOOTHSESSION = _descriptor.Descriptor(
+  name='BluetoothSession',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothSession',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='session_duration_sec', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.session_duration_sec', index=0,
+      number=2, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='connection_technology_type', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.connection_technology_type', index=1,
+      number=3, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='disconnect_reason', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.disconnect_reason', index=2,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\030\001', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='device_connected_to', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.device_connected_to', index=3,
+      number=5, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='rfcomm_session', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.rfcomm_session', index=4,
+      number=6, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='a2dp_session', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.a2dp_session', index=5,
+      number=7, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='disconnect_reason_type', full_name='bluetooth.metrics.BluetoothMetricsProto.BluetoothSession.disconnect_reason_type', index=6,
+      number=8, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _BLUETOOTHSESSION_CONNECTIONTECHNOLOGYTYPE,
+    _BLUETOOTHSESSION_DISCONNECTREASONTYPE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=940,
+  serialized_end=1723,
+)
+
+
+_RFCOMMSESSION = _descriptor.Descriptor(
+  name='RFCommSession',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.RFCommSession',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='rx_bytes', full_name='bluetooth.metrics.BluetoothMetricsProto.RFCommSession.rx_bytes', index=0,
+      number=1, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='tx_bytes', full_name='bluetooth.metrics.BluetoothMetricsProto.RFCommSession.tx_bytes', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1725,
+  serialized_end=1776,
+)
+
+
+_A2DPSESSION = _descriptor.Descriptor(
+  name='A2DPSession',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='media_timer_min_millis', full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession.media_timer_min_millis', index=0,
+      number=1, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='media_timer_max_millis', full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession.media_timer_max_millis', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='media_timer_avg_millis', full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession.media_timer_avg_millis', index=2,
+      number=3, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='buffer_overruns_max_count', full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession.buffer_overruns_max_count', index=3,
+      number=4, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='buffer_overruns_total', full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession.buffer_overruns_total', index=4,
+      number=5, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='buffer_underruns_average', full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession.buffer_underruns_average', index=5,
+      number=6, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='buffer_underruns_count', full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession.buffer_underruns_count', index=6,
+      number=7, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='audio_duration_millis', full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession.audio_duration_millis', index=7,
+      number=8, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='source_codec', full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession.source_codec', index=8,
+      number=9, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='is_a2dp_offload', full_name='bluetooth.metrics.BluetoothMetricsProto.A2DPSession.is_a2dp_offload', index=9,
+      number=10, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1779,
+  serialized_end=2156,
+)
+
+
+_PAIREVENT = _descriptor.Descriptor(
+  name='PairEvent',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.PairEvent',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='disconnect_reason', full_name='bluetooth.metrics.BluetoothMetricsProto.PairEvent.disconnect_reason', index=0,
+      number=1, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='event_time_millis', full_name='bluetooth.metrics.BluetoothMetricsProto.PairEvent.event_time_millis', index=1,
+      number=2, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='device_paired_with', full_name='bluetooth.metrics.BluetoothMetricsProto.PairEvent.device_paired_with', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2159,
+  serialized_end=2305,
+)
+
+
+_WAKEEVENT = _descriptor.Descriptor(
+  name='WakeEvent',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.WakeEvent',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='wake_event_type', full_name='bluetooth.metrics.BluetoothMetricsProto.WakeEvent.wake_event_type', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='requestor', full_name='bluetooth.metrics.BluetoothMetricsProto.WakeEvent.requestor', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='name', full_name='bluetooth.metrics.BluetoothMetricsProto.WakeEvent.name', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='event_time_millis', full_name='bluetooth.metrics.BluetoothMetricsProto.WakeEvent.event_time_millis', index=3,
+      number=4, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _WAKEEVENT_WAKEEVENTTYPE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2308,
+  serialized_end=2528,
+)
+
+
+_SCANEVENT = _descriptor.Descriptor(
+  name='ScanEvent',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.ScanEvent',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='scan_event_type', full_name='bluetooth.metrics.BluetoothMetricsProto.ScanEvent.scan_event_type', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='initiator', full_name='bluetooth.metrics.BluetoothMetricsProto.ScanEvent.initiator', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='scan_technology_type', full_name='bluetooth.metrics.BluetoothMetricsProto.ScanEvent.scan_technology_type', index=2,
+      number=3, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='number_results', full_name='bluetooth.metrics.BluetoothMetricsProto.ScanEvent.number_results', index=3,
+      number=4, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='event_time_millis', full_name='bluetooth.metrics.BluetoothMetricsProto.ScanEvent.event_time_millis', index=4,
+      number=5, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _SCANEVENT_SCANTECHNOLOGYTYPE,
+    _SCANEVENT_SCANEVENTTYPE,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2531,
+  serialized_end=2983,
+)
+
+
+_PROFILECONNECTIONSTATS = _descriptor.Descriptor(
+  name='ProfileConnectionStats',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.ProfileConnectionStats',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='profile_id', full_name='bluetooth.metrics.BluetoothMetricsProto.ProfileConnectionStats.profile_id', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='num_times_connected', full_name='bluetooth.metrics.BluetoothMetricsProto.ProfileConnectionStats.num_times_connected', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2985,
+  serialized_end=3110,
+)
+
+
+_HEADSETPROFILECONNECTIONSTATS = _descriptor.Descriptor(
+  name='HeadsetProfileConnectionStats',
+  full_name='bluetooth.metrics.BluetoothMetricsProto.HeadsetProfileConnectionStats',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='headset_profile_type', full_name='bluetooth.metrics.BluetoothMetricsProto.HeadsetProfileConnectionStats.headset_profile_type', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='num_times_connected', full_name='bluetooth.metrics.BluetoothMetricsProto.HeadsetProfileConnectionStats.num_times_connected', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=3113,
+  serialized_end=3264,
+)
+
+_BLUETOOTHLOG.fields_by_name['session'].message_type = _BLUETOOTHSESSION
+_BLUETOOTHLOG.fields_by_name['pair_event'].message_type = _PAIREVENT
+_BLUETOOTHLOG.fields_by_name['wake_event'].message_type = _WAKEEVENT
+_BLUETOOTHLOG.fields_by_name['scan_event'].message_type = _SCANEVENT
+_BLUETOOTHLOG.fields_by_name['profile_connection_stats'].message_type = _PROFILECONNECTIONSTATS
+_BLUETOOTHLOG.fields_by_name['headset_profile_connection_stats'].message_type = _HEADSETPROFILECONNECTIONSTATS
+_DEVICEINFO.fields_by_name['device_type'].enum_type = _DEVICEINFO_DEVICETYPE
+_DEVICEINFO_DEVICETYPE.containing_type = _DEVICEINFO
+_BLUETOOTHSESSION.fields_by_name['connection_technology_type'].enum_type = _BLUETOOTHSESSION_CONNECTIONTECHNOLOGYTYPE
+_BLUETOOTHSESSION.fields_by_name['device_connected_to'].message_type = _DEVICEINFO
+_BLUETOOTHSESSION.fields_by_name['rfcomm_session'].message_type = _RFCOMMSESSION
+_BLUETOOTHSESSION.fields_by_name['a2dp_session'].message_type = _A2DPSESSION
+_BLUETOOTHSESSION.fields_by_name['disconnect_reason_type'].enum_type = _BLUETOOTHSESSION_DISCONNECTREASONTYPE
+_BLUETOOTHSESSION_CONNECTIONTECHNOLOGYTYPE.containing_type = _BLUETOOTHSESSION
+_BLUETOOTHSESSION_DISCONNECTREASONTYPE.containing_type = _BLUETOOTHSESSION
+_A2DPSESSION.fields_by_name['source_codec'].enum_type = _A2DPSOURCECODEC
+_PAIREVENT.fields_by_name['device_paired_with'].message_type = _DEVICEINFO
+_WAKEEVENT.fields_by_name['wake_event_type'].enum_type = _WAKEEVENT_WAKEEVENTTYPE
+_WAKEEVENT_WAKEEVENTTYPE.containing_type = _WAKEEVENT
+_SCANEVENT.fields_by_name['scan_event_type'].enum_type = _SCANEVENT_SCANEVENTTYPE
+_SCANEVENT.fields_by_name['scan_technology_type'].enum_type = _SCANEVENT_SCANTECHNOLOGYTYPE
+_SCANEVENT_SCANTECHNOLOGYTYPE.containing_type = _SCANEVENT
+_SCANEVENT_SCANEVENTTYPE.containing_type = _SCANEVENT
+_PROFILECONNECTIONSTATS.fields_by_name['profile_id'].enum_type = _PROFILEID
+_HEADSETPROFILECONNECTIONSTATS.fields_by_name['headset_profile_type'].enum_type = _HEADSETPROFILETYPE
+DESCRIPTOR.message_types_by_name['BluetoothLog'] = _BLUETOOTHLOG
+DESCRIPTOR.message_types_by_name['DeviceInfo'] = _DEVICEINFO
+DESCRIPTOR.message_types_by_name['BluetoothSession'] = _BLUETOOTHSESSION
+DESCRIPTOR.message_types_by_name['RFCommSession'] = _RFCOMMSESSION
+DESCRIPTOR.message_types_by_name['A2DPSession'] = _A2DPSESSION
+DESCRIPTOR.message_types_by_name['PairEvent'] = _PAIREVENT
+DESCRIPTOR.message_types_by_name['WakeEvent'] = _WAKEEVENT
+DESCRIPTOR.message_types_by_name['ScanEvent'] = _SCANEVENT
+DESCRIPTOR.message_types_by_name['ProfileConnectionStats'] = _PROFILECONNECTIONSTATS
+DESCRIPTOR.message_types_by_name['HeadsetProfileConnectionStats'] = _HEADSETPROFILECONNECTIONSTATS
+DESCRIPTOR.enum_types_by_name['A2dpSourceCodec'] = _A2DPSOURCECODEC
+DESCRIPTOR.enum_types_by_name['ProfileId'] = _PROFILEID
+DESCRIPTOR.enum_types_by_name['HeadsetProfileType'] = _HEADSETPROFILETYPE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+BluetoothLog = _reflection.GeneratedProtocolMessageType('BluetoothLog', (_message.Message,), {
+  'DESCRIPTOR' : _BLUETOOTHLOG,
+  '__module__' : 'bluetooth_pb2'
+  # @@protoc_insertion_point(class_scope:bluetooth.metrics.BluetoothMetricsProto.BluetoothLog)
+  })
+_sym_db.RegisterMessage(BluetoothLog)
+
+DeviceInfo = _reflection.GeneratedProtocolMessageType('DeviceInfo', (_message.Message,), {
+  'DESCRIPTOR' : _DEVICEINFO,
+  '__module__' : 'bluetooth_pb2'
+  # @@protoc_insertion_point(class_scope:bluetooth.metrics.BluetoothMetricsProto.DeviceInfo)
+  })
+_sym_db.RegisterMessage(DeviceInfo)
+
+BluetoothSession = _reflection.GeneratedProtocolMessageType('BluetoothSession', (_message.Message,), {
+  'DESCRIPTOR' : _BLUETOOTHSESSION,
+  '__module__' : 'bluetooth_pb2'
+  # @@protoc_insertion_point(class_scope:bluetooth.metrics.BluetoothMetricsProto.BluetoothSession)
+  })
+_sym_db.RegisterMessage(BluetoothSession)
+
+RFCommSession = _reflection.GeneratedProtocolMessageType('RFCommSession', (_message.Message,), {
+  'DESCRIPTOR' : _RFCOMMSESSION,
+  '__module__' : 'bluetooth_pb2'
+  # @@protoc_insertion_point(class_scope:bluetooth.metrics.BluetoothMetricsProto.RFCommSession)
+  })
+_sym_db.RegisterMessage(RFCommSession)
+
+A2DPSession = _reflection.GeneratedProtocolMessageType('A2DPSession', (_message.Message,), {
+  'DESCRIPTOR' : _A2DPSESSION,
+  '__module__' : 'bluetooth_pb2'
+  # @@protoc_insertion_point(class_scope:bluetooth.metrics.BluetoothMetricsProto.A2DPSession)
+  })
+_sym_db.RegisterMessage(A2DPSession)
+
+PairEvent = _reflection.GeneratedProtocolMessageType('PairEvent', (_message.Message,), {
+  'DESCRIPTOR' : _PAIREVENT,
+  '__module__' : 'bluetooth_pb2'
+  # @@protoc_insertion_point(class_scope:bluetooth.metrics.BluetoothMetricsProto.PairEvent)
+  })
+_sym_db.RegisterMessage(PairEvent)
+
+WakeEvent = _reflection.GeneratedProtocolMessageType('WakeEvent', (_message.Message,), {
+  'DESCRIPTOR' : _WAKEEVENT,
+  '__module__' : 'bluetooth_pb2'
+  # @@protoc_insertion_point(class_scope:bluetooth.metrics.BluetoothMetricsProto.WakeEvent)
+  })
+_sym_db.RegisterMessage(WakeEvent)
+
+ScanEvent = _reflection.GeneratedProtocolMessageType('ScanEvent', (_message.Message,), {
+  'DESCRIPTOR' : _SCANEVENT,
+  '__module__' : 'bluetooth_pb2'
+  # @@protoc_insertion_point(class_scope:bluetooth.metrics.BluetoothMetricsProto.ScanEvent)
+  })
+_sym_db.RegisterMessage(ScanEvent)
+
+ProfileConnectionStats = _reflection.GeneratedProtocolMessageType('ProfileConnectionStats', (_message.Message,), {
+  'DESCRIPTOR' : _PROFILECONNECTIONSTATS,
+  '__module__' : 'bluetooth_pb2'
+  # @@protoc_insertion_point(class_scope:bluetooth.metrics.BluetoothMetricsProto.ProfileConnectionStats)
+  })
+_sym_db.RegisterMessage(ProfileConnectionStats)
+
+HeadsetProfileConnectionStats = _reflection.GeneratedProtocolMessageType('HeadsetProfileConnectionStats', (_message.Message,), {
+  'DESCRIPTOR' : _HEADSETPROFILECONNECTIONSTATS,
+  '__module__' : 'bluetooth_pb2'
+  # @@protoc_insertion_point(class_scope:bluetooth.metrics.BluetoothMetricsProto.HeadsetProfileConnectionStats)
+  })
+_sym_db.RegisterMessage(HeadsetProfileConnectionStats)
+
+
+DESCRIPTOR._options = None
+_BLUETOOTHSESSION.fields_by_name['disconnect_reason']._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/acts_tests/acts_contrib/test_utils/bt/pts/fuchsia_pts_ics_lib.py b/acts_tests/acts_contrib/test_utils/bt/pts/fuchsia_pts_ics_lib.py
new file mode 100644
index 0000000..f2f9b2c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/pts/fuchsia_pts_ics_lib.py
@@ -0,0 +1,365 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""This is a placeholder for all ICS values in PTS
+    that matter to Fuchsia devices.
+"""
+
+# A2DP Values are just a placeholder.
+A2DP_ICS = {
+    b'TSPC_ALL': b'FALSE',
+    b'TSPC_A2DP_0_1': b'FALSE',
+    b'TSPC_A2DP_0_2': b'FALSE',
+    b'TSPC_A2DP_0_3': b'FALSE',
+    b'TSPC_A2DP_1_1': b'TRUE',
+    b'TSPC_A2DP_1_2': b'TRUE',
+    b'TSPC_A2DP_2_1': b'TRUE',
+    b'TSPC_A2DP_2a_1': b'FALSE',
+    b'TSPC_A2DP_2a_2': b'TRUE',
+    b'TSPC_A2DP_2a_3': b'FALSE',
+    b'TSPC_A2DP_2b_1': b'FALSE',
+    b'TSPC_A2DP_2b_2': b'FALSE',
+    b'TSPC_A2DP_2_2': b'TRUE',
+    b'TSPC_A2DP_2_3': b'TRUE',
+    b'TSPC_A2DP_2_4': b'TRUE',
+    b'TSPC_A2DP_2_5': b'TRUE',
+    b'TSPC_A2DP_2_6': b'TRUE',
+    b'TSPC_A2DP_2_7': b'TRUE',
+    b'TSPC_A2DP_2_8': b'FALSE',
+    b'TSPC_A2DP_2_9': b'FALSE',
+    b'TSPC_A2DP_2_10': b'TRUE',
+    b'TSPC_A2DP_2_10a': b'FALSE',
+    b'TSPC_A2DP_2_11': b'FALSE',
+    b'TSPC_A2DP_2_12': b'FALSE',
+    b'TSPC_A2DP_2_13': b'TRUE',
+    b'TSPC_A2DP_2_14': b'TRUE',
+    b'TSPC_A2DP_2_15': b'FALSE',
+    b'TSPC_A2DP_2_16': b'FALSE',
+    b'TSPC_A2DP_2_17': b'FALSE',
+    b'TSPC_A2DP_3_1': b'TRUE',
+    b'TSPC_A2DP_3_1a': b'FALSE',
+    b'TSPC_A2DP_3_2': b'TRUE',
+    b'TSPC_A2DP_3_3': b'FALSE',
+    b'TSPC_A2DP_3_4': b'FALSE',
+    b'TSPC_A2DP_3_5': b'TRUE',
+    b'TSPC_A2DP_3_6': b'FALSE',
+    b'TSPC_A2DP_3_7': b'FALSE',
+    b'TSPC_A2DP_3_8': b'FALSE',
+    b'TSPC_A2DP_3a_1': b'TRUE',
+    b'TSPC_A2DP_3a_2': b'FALSE',
+    b'TSPC_A2DP_3a_3': b'TRUE',
+    b'TSPC_A2DP_3a_4': b'TRUE',
+    b'TSPC_A2DP_3a_5': b'TRUE',
+    b'TSPC_A2DP_3a_6': b'TRUE',
+    b'TSPC_A2DP_3a_7': b'TRUE',
+    b'TSPC_A2DP_3a_8': b'TRUE',
+    b'TSPC_A2DP_3a_9': b'FALSE',
+    b'TSPC_A2DP_3a_10': b'TRUE',
+    b'TSPC_A2DP_3a_11': b'FALSE',
+    b'TSPC_A2DP_3a_12': b'TRUE',
+    b'TSPC_A2DP_4_1': b'TRUE',
+    b'TSPC_A2DP_4_2': b'TRUE',
+    b'TSPC_A2DP_4_3': b'FALSE',
+    b'TSPC_A2DP_4_4': b'TRUE',
+    b'TSPC_A2DP_4_5': b'TRUE',
+    b'TSPC_A2DP_4_6': b'FALSE',
+    b'TSPC_A2DP_4_7': b'TRUE',
+    b'TSPC_A2DP_4_8': b'FALSE',
+    b'TSPC_A2DP_4_9': b'TRUE',
+    b'TSPC_A2DP_4_10': b'TRUE',
+    b'TSPC_A2DP_4_10a': b'FALSE',
+    b'TSPC_A2DP_4_11': b'FALSE',
+    b'TSPC_A2DP_4_12': b'FALSE',
+    b'TSPC_A2DP_4_13': b'TRUE',
+    b'TSPC_A2DP_4_14': b'TRUE',
+    b'TSPC_A2DP_4_15': b'FALSE',
+    b'TSPC_A2DP_5_1': b'TRUE',
+    b'TSPC_A2DP_5_1a': b'TRUE',
+    b'TSPC_A2DP_5_2': b'TRUE',
+    b'TSPC_A2DP_5_3': b'FALSE',
+    b'TSPC_A2DP_5_4': b'FALSE',
+    b'TSPC_A2DP_5_5': b'FALSE',
+    b'TSPC_A2DP_5a_1': b'TRUE',
+    b'TSPC_A2DP_5a_2': b'TRUE',
+    b'TSPC_A2DP_5a_3': b'TRUE',
+    b'TSPC_A2DP_5a_4': b'TRUE',
+    b'TSPC_A2DP_5a_5': b'TRUE',
+    b'TSPC_A2DP_5a_6': b'TRUE',
+    b'TSPC_A2DP_5a_7': b'TRUE',
+    b'TSPC_A2DP_5a_8': b'TRUE',
+    b'TSPC_A2DP_5a_9': b'TRUE',
+    b'TSPC_A2DP_5a_10': b'TRUE',
+    b'TSPC_A2DP_5a_11': b'TRUE',
+    b'TSPC_A2DP_5a_12': b'TRUE',
+    b'TSPC_A2DP_7a_1': b'FALSE',
+    b'TSPC_A2DP_7a_2': b'FALSE',
+    b'TSPC_A2DP_7a_3': b'FALSE',
+    b'TSPC_A2DP_7b_1': b'FALSE',
+    b'TSPC_A2DP_7b_2': b'FALSE',
+
+    # Not available in Launch Studio Yet
+    b'TSPC_A2DP_10_1': b'FALSE',
+    b'TSPC_A2DP_10_2': b'FALSE',
+    b'TSPC_A2DP_10_3': b'FALSE',
+    b'TSPC_A2DP_10_4': b'FALSE',
+    b'TSPC_A2DP_10_5': b'FALSE',
+    b'TSPC_A2DP_10_6': b'FALSE',
+    b'TSPC_A2DP_11_1': b'FALSE',
+    b'TSPC_A2DP_11_2': b'FALSE',
+    b'TSPC_A2DP_11_3': b'FALSE',
+    b'TSPC_A2DP_11_4': b'FALSE',
+    b'TSPC_A2DP_11_5': b'FALSE',
+    b'TSPC_A2DP_11_6': b'FALSE',
+    b'TSPC_A2DP_12_2': b'FALSE',
+    b'TSPC_A2DP_12_3': b'FALSE',
+    b'TSPC_A2DP_12_3': b'FALSE',
+    b'TSPC_A2DP_12_4': b'FALSE',
+    b'TSPC_A2DP_13_1': b'FALSE',
+    b'TSPC_A2DP_13_2': b'FALSE',
+    b'TSPC_A2DP_13_3': b'FALSE',
+    b'TSPC_A2DP_13_4': b'FALSE',
+    b'TSPC_A2DP_14_1': b'FALSE',
+    b'TSPC_A2DP_14_2': b'FALSE',
+    b'TSPC_A2DP_14_3': b'FALSE',
+    b'TSPC_A2DP_14_4': b'FALSE',
+    b'TSPC_A2DP_14_5': b'FALSE',
+    b'TSPC_A2DP_15_1': b'FALSE',
+    b'TSPC_A2DP_15_2': b'FALSE',
+    b'TSPC_A2DP_15_3': b'FALSE',
+    b'TSPC_A2DP_15_4': b'FALSE',
+    b'TSPC_A2DP_15_5': b'FALSE',
+    b'TSPC_A2DP_15_6': b'FALSE',
+    b'TSPC_A2DP_3_2a': b'FALSE',
+    b'TSPC_A2DP_3_2b': b'FALSE',
+    b'TSPC_A2DP_3_2c': b'FALSE',
+    b'TSPC_A2DP_3_2d': b'FALSE',
+    b'TSPC_A2DP_3_2e': b'FALSE',
+    b'TSPC_A2DP_3_2f': b'FALSE',
+    b'TSPC_A2DP_5_2a': b'FALSE',
+    b'TSPC_A2DP_5_2b': b'FALSE',
+    b'TSPC_A2DP_5_2c': b'FALSE',
+    b'TSPC_A2DP_8_2': b'FALSE',
+    b'TSPC_A2DP_8_3': b'FALSE',
+    b'TSPC_A2DP_8_4': b'FALSE',
+    b'TSPC_A2DP_9_1': b'FALSE',
+    b'TSPC_A2DP_9_2': b'FALSE',
+    b'TSPC_A2DP_9_3': b'FALSE',
+    b'TSPC_A2DP_9_4': b'FALSE',
+
+}
+
+
+GATT_ICS = {
+    b'TSPC_GATT_1_1': b'TRUE',
+    b'TSPC_GATT_1_2': b'TRUE',
+    b'TSPC_GATT_1a_1': b'TRUE',
+    b'TSPC_GATT_1a_2': b'TRUE',
+    b'TSPC_GATT_1a_3': b'TRUE',
+    b'TSPC_GATT_1a_4': b'TRUE',
+    b'TSPC_GATT_1a_5': b'FALSE',
+    b'TSPC_GATT_1a_6': b'FALSE',
+    b'TSPC_GATT_1a_7': b'FALSE',
+    b'TSPC_GATT_1a_8': b'FALSE',
+    b'TSPC_GATT_2_1': b'FALSE',
+    b'TSPC_GATT_2_2': b'TRUE',
+    b'TSPC_GATT_3_1': b'TRUE',
+    b'TSPC_GATT_3_2': b'TRUE',
+    b'TSPC_GATT_3_3': b'TRUE',
+    b'TSPC_GATT_3_4': b'TRUE',
+    b'TSPC_GATT_3_5': b'TRUE',
+    b'TSPC_GATT_3_6': b'FALSE',
+    b'TSPC_GATT_3_7': b'TRUE',
+    b'TSPC_GATT_3_8': b'TRUE',
+    b'TSPC_GATT_3_9': b'TRUE',
+    b'TSPC_GATT_3_10': b'TRUE',
+    b'TSPC_GATT_3_11': b'FALSE',
+    b'TSPC_GATT_3_12': b'TRUE',
+    b'TSPC_GATT_3_13': b'FALSE',
+    b'TSPC_GATT_3_14': b'TRUE',
+    b'TSPC_GATT_3_15': b'TRUE',
+    b'TSPC_GATT_3_16': b'TRUE',
+    b'TSPC_GATT_3_17': b'TRUE',
+    b'TSPC_GATT_3_18': b'TRUE',
+    b'TSPC_GATT_3_19': b'TRUE',
+    b'TSPC_GATT_3_20': b'TRUE',
+    b'TSPC_GATT_3_21': b'TRUE',
+    b'TSPC_GATT_3_22': b'TRUE',
+    b'TSPC_GATT_3_23': b'TRUE',
+    b'TSPC_GATT_3_24': b'FALSE',
+    b'TSPC_GATT_3_25': b'FALSE',
+    b'TSPC_GATT_3_26': b'FALSE',
+    b'TSPC_GATT_3B_1': b'FALSE',
+    b'TSPC_GATT_3B_2': b'FALSE',
+    b'TSPC_GATT_3B_3': b'FALSE',
+    b'TSPC_GATT_3B_4': b'FALSE',
+    b'TSPC_GATT_3B_5': b'FALSE',
+    b'TSPC_GATT_3B_6': b'FALSE',
+    b'TSPC_GATT_3B_7': b'FALSE',
+    b'TSPC_GATT_3B_8': b'FALSE',
+    b'TSPC_GATT_3B_9': b'FALSE',
+    b'TSPC_GATT_3B_10': b'FALSE',
+    b'TSPC_GATT_3B_11': b'FALSE',
+    b'TSPC_GATT_3B_12': b'FALSE',
+    b'TSPC_GATT_3B_13': b'FALSE',
+    b'TSPC_GATT_3B_14': b'FALSE',
+    b'TSPC_GATT_3B_15': b'FALSE',
+    b'TSPC_GATT_3B_16': b'FALSE',
+    b'TSPC_GATT_3B_17': b'FALSE',
+    b'TSPC_GATT_3B_18': b'FALSE',
+    b'TSPC_GATT_3B_19': b'FALSE',
+    b'TSPC_GATT_3B_20': b'FALSE',
+    b'TSPC_GATT_3B_21': b'FALSE',
+    b'TSPC_GATT_3B_22': b'FALSE',
+    b'TSPC_GATT_3B_23': b'FALSE',
+    b'TSPC_GATT_3B_24': b'FALSE',
+    b'TSPC_GATT_3B_25': b'FALSE',
+    b'TSPC_GATT_3B_26': b'FALSE',
+    b'TSPC_GATT_3B_27': b'FALSE',
+    b'TSPC_GATT_3B_28': b'FALSE',
+    b'TSPC_GATT_3B_29': b'FALSE',
+    b'TSPC_GATT_3B_30': b'FALSE',
+    b'TSPC_GATT_3B_31': b'FALSE',
+    b'TSPC_GATT_3B_32': b'FALSE',
+    b'TSPC_GATT_3B_33': b'FALSE',
+    b'TSPC_GATT_3B_34': b'FALSE',
+    b'TSPC_GATT_3B_35': b'FALSE',
+    b'TSPC_GATT_3B_36': b'FALSE',
+    b'TSPC_GATT_3B_37': b'FALSE',
+    b'TSPC_GATT_3B_38': b'FALSE',
+    b'TSPC_GATT_4_1': b'TRUE',
+    b'TSPC_GATT_4_2': b'TRUE',
+    b'TSPC_GATT_4_3': b'TRUE',
+    b'TSPC_GATT_4_4': b'TRUE',
+    b'TSPC_GATT_4_5': b'TRUE',
+    b'TSPC_GATT_4_6': b'TRUE',
+    b'TSPC_GATT_4_7': b'TRUE',
+    b'TSPC_GATT_4_8': b'TRUE',
+    b'TSPC_GATT_4_9': b'TRUE',
+    b'TSPC_GATT_4_10': b'TRUE',
+    b'TSPC_GATT_4_11': b'FALSE',
+    b'TSPC_GATT_4_12': b'TRUE',
+    b'TSPC_GATT_4_13': b'FALSE',
+    b'TSPC_GATT_4_14': b'TRUE',
+    b'TSPC_GATT_4_15': b'TRUE',
+    b'TSPC_GATT_4_16': b'TRUE',
+    b'TSPC_GATT_4_17': b'TRUE',
+    b'TSPC_GATT_4_18': b'TRUE',
+    b'TSPC_GATT_4_19': b'TRUE',
+    b'TSPC_GATT_4_20': b'TRUE',
+    b'TSPC_GATT_4_21': b'TRUE',
+    b'TSPC_GATT_4_22': b'TRUE',
+    b'TSPC_GATT_4_23': b'TRUE',
+    b'TSPC_GATT_4_24': b'FALSE',
+    b'TSPC_GATT_4_25': b'FALSE',
+    b'TSPC_GATT_4_26': b'FALSE',
+    b'TSPC_GATT_4_27': b'FALSE',
+    b'TSPC_GATT_4B_1': b'FALSE',
+    b'TSPC_GATT_4B_2': b'FALSE',
+    b'TSPC_GATT_4B_3': b'FALSE',
+    b'TSPC_GATT_4B_4': b'FALSE',
+    b'TSPC_GATT_4B_5': b'FALSE',
+    b'TSPC_GATT_4B_6': b'FALSE',
+    b'TSPC_GATT_4B_7': b'FALSE',
+    b'TSPC_GATT_4B_8': b'FALSE',
+    b'TSPC_GATT_4B_9': b'FALSE',
+    b'TSPC_GATT_4B_10': b'FALSE',
+    b'TSPC_GATT_4B_11': b'FALSE',
+    b'TSPC_GATT_4B_12': b'FALSE',
+    b'TSPC_GATT_4B_13': b'FALSE',
+    b'TSPC_GATT_4B_14': b'FALSE',
+    b'TSPC_GATT_4B_15': b'FALSE',
+    b'TSPC_GATT_4B_16': b'FALSE',
+    b'TSPC_GATT_4B_17': b'FALSE',
+    b'TSPC_GATT_4B_18': b'FALSE',
+    b'TSPC_GATT_4B_19': b'FALSE',
+    b'TSPC_GATT_4B_20': b'FALSE',
+    b'TSPC_GATT_4B_21': b'FALSE',
+    b'TSPC_GATT_4B_22': b'FALSE',
+    b'TSPC_GATT_4B_23': b'FALSE',
+    b'TSPC_GATT_4B_24': b'FALSE',
+    b'TSPC_GATT_4B_25': b'FALSE',
+    b'TSPC_GATT_4B_26': b'FALSE',
+    b'TSPC_GATT_4B_27': b'FALSE',
+    b'TSPC_GATT_4B_28': b'FALSE',
+    b'TSPC_GATT_4B_29': b'FALSE',
+    b'TSPC_GATT_4B_30': b'FALSE',
+    b'TSPC_GATT_4B_31': b'FALSE',
+    b'TSPC_GATT_4B_32': b'FALSE',
+    b'TSPC_GATT_4B_33': b'FALSE',
+    b'TSPC_GATT_4B_34': b'FALSE',
+    b'TSPC_GATT_4B_35': b'FALSE',
+    b'TSPC_GATT_4B_36': b'FALSE',
+    b'TSPC_GATT_4B_37': b'FALSE',
+    b'TSPC_GATT_4B_38': b'FALSE',
+    b'TSPC_GATT_6_2': b'TRUE',
+    b'TSPC_GATT_6_3': b'TRUE',
+    b'TSPC_GATT_7_1': b'TRUE',
+    b'TSPC_GATT_7_2': b'TRUE',
+    b'TSPC_GATT_7_3': b'TRUE',
+    b'TSPC_GATT_7_4': b'TRUE',
+    b'TSPC_GATT_7_5': b'FALSE',
+    b'TSPC_GATT_7_6': b'FALSE',
+    b'TSPC_GATT_7_7': b'FALSE',
+    b'TSPC_GATT_8_1': b'TRUE',
+    b'TSPC_GAP_0_2': b'FALSE',
+    b'TSPC_GAP_24_2': b'TRUE',
+    b'TSPC_GAP_24_3': b'TRUE',
+    b'TSPC_GAP_34_2': b'TRUE',
+    b'TSPC_GAP_34_3': b'TRUE',
+    b'TSPC_ALL': b'FALSE',
+}
+
+
+SDP_ICS = {
+    b'TSPC_ALL': b'FALSE',
+    b'TSPC_SDP_1_1': b'TRUE',
+    b'TSPC_SDP_1_2': b'TRUE',
+    b'TSPC_SDP_1_3': b'TRUE',
+    b'TSPC_SDP_1b_1': b'TRUE',
+    b'TSPC_SDP_1b_2': b'TRUE',
+    b'TSPC_SDP_2_1': b'TRUE',
+    b'TSPC_SDP_2_2': b'TRUE',
+    b'TSPC_SDP_2_3': b'TRUE',
+    b'TSPC_SDP_3_1': b'TRUE',
+    b'TSPC_SDP_4_1': b'TRUE',
+    b'TSPC_SDP_4_2': b'TRUE',
+    b'TSPC_SDP_4_3': b'TRUE',
+    b'TSPC_SDP_5_1': b'TRUE',
+    b'TSPC_SDP_6_1': b'TRUE',
+    b'TSPC_SDP_6_2': b'TRUE',
+    b'TSPC_SDP_6_3': b'TRUE',
+    b'TSPC_SDP_7_1': b'TRUE',
+    b'TSPC_SDP_8_1': b'FALSE',
+    b'TSPC_SDP_8_2': b'FALSE',
+    b'TSPC_SDP_9_1': b'TRUE',
+    b'TSPC_SDP_9_2': b'TRUE',
+    b'TSPC_SDP_9_3': b'FALSE',
+    b'TSPC_SDP_9_4': b'FALSE',
+    b'TSPC_SDP_9_5': b'TRUE',
+    b'TSPC_SDP_9_6': b'TRUE',
+    b'TSPC_SDP_9_7': b'FALSE',
+    b'TSPC_SDP_9_8': b'FALSE',
+    b'TSPC_SDP_9_9': b'TRUE',
+    b'TSPC_SDP_9_10': b'TRUE',
+    b'TSPC_SDP_9_11': b'TRUE',
+    b'TSPC_SDP_9_12': b'FALSE',
+    b'TSPC_SDP_9_13': b'FALSE',
+    b'TSPC_SDP_9_14': b'TRUE',
+    b'TSPC_SDP_9_15': b'FALSE',
+    b'TSPC_SDP_9_16': b'FALSE',
+    b'TSPC_SDP_9_17': b'TRUE',
+    b'TSPC_SDP_9_18': b'TRUE',
+    b'TSPC_SDP_9_19': b'TRUE',
+}
diff --git a/acts_tests/acts_contrib/test_utils/bt/pts/fuchsia_pts_ixit_lib.py b/acts_tests/acts_contrib/test_utils/bt/pts/fuchsia_pts_ixit_lib.py
new file mode 100644
index 0000000..b7d1c80
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/pts/fuchsia_pts_ixit_lib.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""This is a placeholder for all IXIT values in PTS
+    that matter to Fuchsia devices.
+"""
+
+A2DP_IXIT = {
+    b'TSPX_security_enabled': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_bd_addr_iut': (b'OCTETSTRING', b'000000000000'),
+    b'TSPX_SRC_class_of_device': (b'OCTETSTRING', b'080418'),
+    b'TSPX_SNK_class_of_device': (b'OCTETSTRING', b'04041C'),
+    b'TSPX_pin_code': (b'IA5STRING', b'0000'),
+    b'TSPX_delete_link_key': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_time_guard': (b'INTEGER', b'300000'),
+    b'TSPX_use_implicit_send': (b'BOOLEAN', b'TRUE'),
+    b'TSPX_media_directory':
+    (b'IA5STRING', b'C:\Program Files\Bluetooth SIG\Bluetooth PTS\\bin\\audio'),
+    b'TSPX_auth_password': (b'IA5STRING', b'0000'),
+    b'TSPX_auth_user_id': (b'IA5STRING', b'PTS'),
+    b'TSPX_rfcomm_channel': (b'INTEGER', b'8'),
+    b'TSPX_l2cap_psm': (b'OCTETSTRING', b'1011'),
+    b'TSPX_no_confirmations': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_cover_art_uuid': (b'OCTETSTRING', b'3EEE'),
+}
+
+GATT_IXIT = {
+    b'TSPX_bd_addr_iut': (b'OCTETSTRING', b'000000000000'),
+    b'TSPX_iut_device_name_in_adv_packet_for_random_address': (b'IA5STRING', b'tbd'),
+    b'TSPX_security_enabled': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_delete_link_key': (b'BOOLEAN', b'TRUE'),
+    b'TSPX_time_guard': (b'INTEGER', b'180000'),
+    b'TSPX_selected_handle': (b'OCTETSTRING', b'0012'),
+    b'TSPX_use_implicit_send': (b'BOOLEAN', b'TRUE'),
+    b'TSPX_secure_simple_pairing_pass_key_confirmation': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_iut_use_dynamic_bd_addr': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_iut_setup_att_over_br_edr': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_tester_database_file': (b'IA5STRING', b'C:\Program Files\Bluetooth SIG\Bluetooth PTS\Data\SIGDatabase\GATT_Qualification_Test_Databases.xml'),
+    b'TSPX_iut_is_client_periphral': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_iut_is_server_central': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_mtu_size': (b'INTEGER', b'23'),
+    b'TSPX_pin_code':  (b'IA5STRING', b'0000'),
+    b'TSPX_use_dynamic_pin': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_delete_ltk': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_tester_appearance': (b'OCTETSTRING', b'0000'),
+}
+
+SDP_IXIT = {
+    b'TSPX_sdp_service_search_pattern': (b'IA5STRING', b'0100'),
+    b'TSPX_sdp_service_search_pattern_no_results': (b'IA5STRING', b'EEEE'),
+    b'TSPX_sdp_service_search_pattern_additional_protocol_descriptor_list': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_bluetooth_profile_descriptor_list': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_browse_group_list': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_client_exe_url': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_documentation_url': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_icon_url': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_language_base_attribute_id_list': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_protocol_descriptor_list': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_provider_name': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_service_availability': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_service_data_base_state': (b'IA5STRING', b'1000'),
+    b'TSPX_sdp_service_search_pattern_service_description': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_service_id': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_service_info_time_to_live': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_version_number_list': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_service_name': (b'IA5STRING', b''),
+    b'TSPX_sdp_service_search_pattern_service_record_state': (b'IA5STRING', b''),
+    b'TSPX_sdp_unsupported_attribute_id': (b'OCTETSTRING', b'EEEE'),
+    b'TSPX_security_enabled': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_delete_link_key': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_bd_addr_iut': (b'OCTETSTRING', b''),
+    b'TSPX_class_of_device_pts': (b'OCTETSTRING', b'200404'),
+    b'TSPX_class_of_device_test_pts_initiator': (b'BOOLEAN', b'TRUE'),
+    b'TSPX_limited_inquiry_used': (b'BOOLEAN', b'FALSE'),
+    b'TSPX_pin_code': (b'IA5STRING', b'0000'),
+    b'TSPX_time_guard': (b'INTEGER', b'200000'),
+    b'TSPX_device_search_time': (b'INTEGER', b'20'),
+    b'TSPX_use_implicit_send': (b'BOOLEAN', b'TRUE'),
+    b'TSPX_secure_simple_pairing_pass_key_confirmation': (b'BOOLEAN', b'FALSE'),
+}
diff --git a/acts_tests/acts_contrib/test_utils/bt/pts/pts_base_class.py b/acts_tests/acts_contrib/test_utils/bt/pts/pts_base_class.py
new file mode 100644
index 0000000..6be3d2e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/pts/pts_base_class.py
@@ -0,0 +1,358 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""This is the PTS base class that is inherited from all PTS
+Tests.
+"""
+
+import ctypes
+import random
+import re
+import time
+import traceback
+
+from ctypes import *
+from datetime import datetime
+
+from acts import signals
+from acts.base_test import BaseTestClass
+from acts.controllers.bluetooth_pts_device import VERDICT_STRINGS
+from acts.controllers.fuchsia_device import FuchsiaDevice
+from acts.signals import TestSignal
+from acts_contrib.test_utils.abstract_devices.bluetooth_device import create_bluetooth_device
+from acts_contrib.test_utils.bt.bt_constants import gatt_transport
+from acts_contrib.test_utils.fuchsia.bt_test_utils import le_scan_for_device_by_name
+
+
+class PtsBaseClass(BaseTestClass):
+    """ Class for representing common functionality across all PTS tests.
+
+    This includes the ability to rerun tests due to PTS instability,
+    common PTS action mappings, and setup/teardown related devices.
+
+    """
+    scan_timeout_seconds = 10
+    peer_identifier = None
+
+    def setup_class(self):
+        super().setup_class()
+        if 'dut' in self.user_params:
+            if self.user_params['dut'] == 'fuchsia_devices':
+                self.dut = create_bluetooth_device(self.fuchsia_devices[0])
+            elif self.user_params['dut'] == 'android_devices':
+                self.dut = create_bluetooth_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_bluetooth_device(self.fuchsia_devices[0])
+
+        self.characteristic_read_not_permitted_uuid = self.user_params.get(
+            "characteristic_read_not_permitted_uuid")
+        self.characteristic_read_not_permitted_handle = self.user_params.get(
+            "characteristic_read_not_permitted_handle")
+        self.characteristic_read_invalid_handle = self.user_params.get(
+            "characteristic_read_invalid_handle")
+        self.characteristic_attribute_not_found_uuid = self.user_params.get(
+            "characteristic_attribute_not_found_uuid")
+        self.write_characteristic_not_permitted_handle = self.user_params.get(
+            "write_characteristic_not_permitted_handle")
+
+        self.pts = self.bluetooth_pts_device[0]
+        # MMI functions commented out until implemented. Added for tracking
+        # purposes.
+        self.pts_action_mapping = {
+            "A2DP": {
+                1: self.a2dp_mmi_iut_connectable,
+                1002: self.a2dp_mmi_iut_accept_connect,
+                1020: self.a2dp_mmi_initiate_open_stream,
+            },
+            "GATT": {
+                1: self.mmi_make_iut_connectable,
+                2: self.mmi_iut_initiate_connection,
+                3: self.mmi_iut_initiate_disconnection,
+                # 4: self.mmi_iut_no_security,
+                # 5: self.mmi_iut_initiate_br_connection,
+                10: self.mmi_discover_primary_service,
+                # 11: self.mmi_confirm_no_primary_service_small,
+                # 12: self.mmi_iut_mtu_exchange,
+                # 13: self.mmi_discover_all_service_record,
+                # 14: self.mmi_iut_discover_gatt_service_record,
+                15: self.mmi_iut_find_included_services,
+                # 16: self.mmi_confirm_no_characteristic_uuid_small,
+                17: self.mmi_confirm_primary_service,
+                # 18: self.mmi_send_primary_service_uuid,
+                # 19: self.mmi_confirm_primary_service_uuid,
+                # 22: self.confirm_primary_service_1801,
+                24: self.mmi_confirm_include_service,
+                26: self.mmi_confirm_characteristic_service,
+                # 27: self.perform_read_all_characteristics,
+                29: self.
+                mmi_discover_service_uuid_range,  # AKA: discover service by uuid
+                # 31: self.perform_read_all_descriptors,
+                48: self.mmi_iut_send_read_characteristic_handle,
+                58: self.mmi_iut_send_read_descriptor_handle,
+                70: self.mmi_send_write_command,
+                74: self.mmi_send_write_request,
+                76: self.mmi_send_prepare_write,
+                77: self.mmi_iut_send_prepare_write_greater_offset,
+                80: self.mmi_iut_send_prepare_write_greater,
+                110: self.mmi_iut_enter_handle_read_not_permitted,
+                111: self.mmi_iut_enter_uuid_read_not_permitted,
+                118: self.mmi_iut_enter_handle_invalid,
+                119: self.mmi_iut_enter_uuid_attribute_not_found,
+                120: self.mmi_iut_enter_handle_write_not_permitted,
+                2000: self.mmi_verify_secure_id,  # Enter pairing pin from DUT.
+            },
+            "SDP": {
+                # TODO: Implement MMIs as necessary
+            }
+        }
+        self.pts.bind_to(self.process_next_action)
+
+    def teardown_class(self):
+        self.pts.clean_up()
+
+    def setup_test(self):
+        # Always start the test with RESULT_INCOMP
+        self.pts.pts_test_result = VERDICT_STRINGS['RESULT_INCOMP']
+
+    def teardown_test(self):
+        return True
+
+    @staticmethod
+    def pts_test_wrap(fn):
+        def _safe_wrap_test_case(self, *args, **kwargs):
+            test_id = "{}:{}:{}".format(self.__class__.__name__, fn.__name__,
+                                        time.time())
+            log_string = "[Test ID] {}".format(test_id)
+            self.log.info(log_string)
+            try:
+                self.dut.log_info("Started " + log_string)
+                result = fn(self, *args, **kwargs)
+                self.dut.log_info("Finished " + log_string)
+                rerun_count = self.user_params.get("pts_auto_rerun_count", 0)
+                for i in range(int(rerun_count)):
+                    if result is not True:
+                        self.teardown_test()
+                        log_string = "[Rerun Test ID] {}. Run #{} run failed... Retrying".format(
+                            test_id, i + 1)
+                        self.log.info(log_string)
+                        self.setup_test()
+                        self.dut.log_info("Rerun Started " + log_string)
+                        result = fn(self, *args, **kwargs)
+                    else:
+                        return result
+                return result
+            except TestSignal:
+                raise
+            except Exception as e:
+                self.log.error(traceback.format_exc())
+                self.log.error(str(e))
+                raise
+            return fn(self, *args, **kwargs)
+
+        return _safe_wrap_test_case
+
+    def process_next_action(self, action):
+        func = self.pts_action_mapping.get(
+            self.pts.pts_profile_mmi_request).get(action, "Nothing")
+        if func != 'Nothing':
+            func()
+
+    ### BEGIN A2DP MMI Actions ###
+
+    def a2dp_mmi_iut_connectable(self):
+        self.dut.start_profile_a2dp_sink()
+        self.dut.set_discoverable(True)
+
+    def a2dp_mmi_iut_accept_connect(self):
+        self.dut.start_profile_a2dp_sink()
+        self.dut.set_discoverable(True)
+
+    def a2dp_mmi_initiate_open_stream(self):
+        self.dut.a2dp_initiate_open_stream()
+
+    ### END A2DP MMI Actions ###
+
+    ### BEGIN GATT MMI Actions ###
+
+    def create_write_value_by_size(self, size):
+        write_value = []
+        for i in range(size):
+            write_value.append(i % 256)
+        return write_value
+
+    def mmi_send_write_command(self):
+        description_to_parse = self.pts.current_implicit_send_description
+        raw_handle = re.search('handle = \'(.*)\'O with', description_to_parse)
+        handle = int(raw_handle.group(1), 16)
+        raw_size = re.search('with <= \'(.*)\' byte', description_to_parse)
+        size = int(raw_size.group(1))
+        self.dut.gatt_client_write_characteristic_without_response_by_handle(
+            self.peer_identifier, handle,
+            self.create_write_value_by_size(size))
+
+    def mmi_send_write_request(self):
+        description_to_parse = self.pts.current_implicit_send_description
+        raw_handle = re.search('handle = \'(.*)\'O with', description_to_parse)
+        handle = int(raw_handle.group(1), 16)
+        raw_size = re.search('with <= \'(.*)\' byte', description_to_parse)
+        size = int(raw_size.group(1))
+        offset = 0
+        self.dut.gatt_client_write_characteristic_by_handle(
+            self.peer_identifier, handle, offset,
+            self.create_write_value_by_size(size))
+
+    def mmi_send_prepare_write(self):
+        description_to_parse = self.pts.current_implicit_send_description
+        raw_handle = re.search('handle = \'(.*)\'O <=', description_to_parse)
+        handle = int(raw_handle.group(1), 16)
+        raw_size = re.search('<= \'(.*)\' byte', description_to_parse)
+        size = int(math.floor(int(raw_size.group(1)) / 2))
+        offset = int(size / 2)
+        self.dut.gatt_client_write_characteristic_by_handle(
+            self.peer_identifier, handle, offset,
+            self.create_write_value_by_size(size))
+
+    def mmi_iut_send_prepare_write_greater_offset(self):
+        description_to_parse = self.pts.current_implicit_send_description
+        raw_handle = re.search('handle = \'(.*)\'O and', description_to_parse)
+        handle = int(raw_handle.group(1), 16)
+        raw_offset = re.search('greater than \'(.*)\' byte',
+                               description_to_parse)
+        offset = int(raw_offset.group(1))
+        size = 1
+        self.dut.gatt_client_write_characteristic_by_handle(
+            self.peer_identifier, handle, offset,
+            self.create_write_value_by_size(size))
+
+    def mmi_iut_send_prepare_write_greater(self):
+        description_to_parse = self.pts.current_implicit_send_description
+        raw_handle = re.search('handle = \'(.*)\'O with', description_to_parse)
+        handle = int(raw_handle.group(1), 16)
+        raw_size = re.search('greater than \'(.*)\' byte',
+                             description_to_parse)
+        size = int(raw_size.group(1))
+        offset = 0
+        self.dut.gatt_client_write_characteristic_by_handle(
+            self.peer_identifier, handle, offset,
+            self.create_write_value_by_size(size))
+
+    def mmi_make_iut_connectable(self):
+        adv_data = {
+            "name": fuchsia_name,
+            "appearance": None,
+            "service_data": None,
+            "tx_power_level": None,
+            "service_uuids": None,
+            "manufacturer_data": None,
+            "uris": None,
+        }
+        scan_response = None
+        connectable = True
+        interval = 1000
+
+        self.dut.start_le_advertisement(adv_data, scan_response, interval,
+                                        connectable)
+
+    def mmi_iut_enter_uuid_read_not_permitted(self):
+        self.pts.extra_answers.append(
+            self.characteristic_read_not_permitted_uuid)
+
+    def mmi_iut_enter_handle_read_not_permitted(self):
+        self.pts.extra_answers.append(
+            self.characteristic_read_not_permitted_handle)
+
+    def mmi_iut_enter_handle_invalid(self):
+        self.pts.extra_answers.append(self.characteristic_read_invalid_handle)
+
+    def mmi_iut_enter_uuid_attribute_not_found(self):
+        self.pts.extra_answers.append(
+            self.characteristic_attribute_not_found_uuid)
+
+    def mmi_iut_enter_handle_write_not_permitted(self):
+        self.pts.extra_answers.append(
+            self.write_characteristic_not_permitted_handle)
+
+    def mmi_verify_secure_id(self):
+        self.pts.extra_answers.append(self.dut.get_pairing_pin())
+
+    def mmi_discover_service_uuid_range(self, uuid):
+        self.dut.gatt_client_mmi_discover_service_uuid_range(
+            self.peer_identifier, uuid)
+
+    def mmi_iut_initiate_connection(self):
+        autoconnect = False
+        transport = gatt_transport['le']
+        adv_name = "PTS"
+        self.peer_identifier = self.dut.le_scan_with_name_filter(
+            "PTS", self.scan_timeout_seconds)
+        if self.peer_identifier is None:
+            raise signals.TestFailure("Scanner unable to find advertisement.")
+        tries = 3
+        for _ in range(tries):
+            if self.dut.gatt_connect(self.peer_identifier, transport,
+                                     autoconnect):
+                return
+
+        raise signals.TestFailure("Unable to connect to peripheral.")
+
+    def mmi_iut_initiate_disconnection(self):
+        if not self.dut.gatt_disconnect(self.peer_identifier):
+            raise signals.TestFailure("Failed to disconnect from peer.")
+
+    def mmi_discover_primary_service(self):
+        self.dut.gatt_refresh()
+
+    def mmi_iut_find_included_services(self):
+        self.dut.gatt_refresh()
+
+        test_result = self.pts.execute_test(test_name)
+        return test_result
+
+    def mmi_confirm_primary_service(self):
+        # TODO: Write verifier that 1800 and 1801 exists. For now just pass.
+        return True
+
+    def mmi_confirm_characteristic_service(self):
+        # TODO: Write verifier that no services exist. For now just pass.
+        return True
+
+    def mmi_confirm_include_service(self, uuid_description):
+        # TODO: Write verifier that input services exist. For now just pass.
+        # Note: List comes in the form of a long string to parse:
+        # Attribute Handle = '0002'O Included Service Attribute handle = '0080'O,End Group Handle = '0085'O,Service UUID = 'A00B'O
+        # \n
+        # Attribute Handle = '0021'O Included Service Attribute handle = '0001'O,End Group Handle = '0006'O,Service UUID = 'A00D'O
+        # \n ...
+        return True
+
+    def mmi_iut_send_read_characteristic_handle(self):
+        description_to_parse = self.pts.current_implicit_send_description
+        raw_handle = re.search('handle = \'(.*)\'O to', description_to_parse)
+        handle = int(raw_handle.group(1), 16)
+        self.dut.gatt_client_read_characteristic_by_handle(
+            self.peer_identifier, handle)
+
+    def mmi_iut_send_read_descriptor_handle(self):
+        description_to_parse = self.pts.current_implicit_send_description
+        raw_handle = re.search('handle = \'(.*)\'O to', description_to_parse)
+        handle = int(raw_handle.group(1), 16)
+        self.dut.gatt_client_descriptor_read_by_handle(self.peer_identifier,
+                                                       handle)
+
+    ### END GATT MMI Actions ###
diff --git a/acts_tests/acts_contrib/test_utils/bt/rfcomm_lib.py b/acts_tests/acts_contrib/test_utils/bt/rfcomm_lib.py
new file mode 100644
index 0000000..f9aa117
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/rfcomm_lib.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""
+Bluetooth adapter libraries
+"""
+
+from acts_contrib.test_utils.bt.bt_constants import bt_rfcomm_uuids
+from acts_contrib.test_utils.bt.bt_test_utils import set_bt_scan_mode
+
+import pprint
+
+
+class RfcommLib():
+    def __init__(self, log, dut, target_mac_addr=None):
+        self.advertisement_list = []
+        self.dut = dut
+        self.log = log
+        self.target_mac_addr = target_mac_addr
+
+    def set_target_mac_addr(self, mac_addr):
+        self.target_mac_addr = mac_addr
+
+    def connect(self, line):
+        """Perform an RFCOMM connect"""
+        uuid = None
+        if len(line) > 0:
+            uuid = line
+        if uuid:
+            self.dut.droid.bluetoothRfcommBeginConnectThread(
+                self.target_mac_addr, uuid)
+        else:
+            self.dut.droid.bluetoothRfcommBeginConnectThread(
+                self.target_mac_addr)
+
+    def open_rfcomm_socket(self):
+        """Open rfcomm socket"""
+        self.dut.droid.rfcommCreateRfcommSocket(self.target_mac_addr, 1)
+
+    def open_l2cap_socket(self):
+        """Open L2CAP socket"""
+        self.dut.droid.rfcommCreateL2capSocket(self.target_mac_addr, 1)
+
+    def write(self, line):
+        """Write String data over an RFCOMM connection"""
+        self.dut.droid.bluetoothRfcommWrite(line)
+
+    def write_binary(self, line):
+        """Write String data over an RFCOMM connection"""
+        self.dut.droid.bluetoothRfcommWriteBinary(line)
+
+    def end_connect(self):
+        """End RFCOMM connection"""
+        self.dut.droid.bluetoothRfcommEndConnectThread()
+
+    def accept(self, line):
+        """Accept RFCOMM connection"""
+        uuid = None
+        if len(line) > 0:
+            uuid = line
+        if uuid:
+            self.dut.droid.bluetoothRfcommBeginAcceptThread(uuid)
+        else:
+            self.dut.droid.bluetoothRfcommBeginAcceptThread(
+                bt_rfcomm_uuids['base_uuid'])
+
+    def stop(self):
+        """Stop RFCOMM Connection"""
+        self.dut.droid.bluetoothRfcommStop()
+
+    def open_l2cap_socket(self):
+        """Open L2CAP socket"""
+        self.dut.droid.rfcommCreateL2capSocket(self.target_mac_addr, 1)
diff --git a/acts_tests/acts_contrib/test_utils/bt/shell_commands_lib.py b/acts_tests/acts_contrib/test_utils/bt/shell_commands_lib.py
new file mode 100644
index 0000000..0e0f099
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/shell_commands_lib.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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.
+"""
+Shell command library.
+"""
+
+
+class ShellCommands():
+    def __init__(self, log, dut):
+        self.dut = dut
+        self.log = log
+
+    def set_battery_level(self, level):
+        """Set the battery level via ADB shell
+        Args:
+            level: the percent level to set
+        """
+        self.dut.adb.shell("dumpsys battery set level {}".format(level))
+
+    def disable_ble_scanning(self):
+        """Disable BLE scanning via ADB shell"""
+        self.dut.adb.shell("settings put global ble_scan_always_enabled 0")
+
+    def enable_ble_scanning(self):
+        """Enable BLE scanning via ADB shell"""
+        self.dut.adb.shell("settings put global ble_scan_always_enabled 1")
+
+    def consume_cpu_core(self):
+        """Consume a CPU core on the Android device via ADB shell"""
+        self.dut.adb.shell("echo $$ > /dev/cpuset/top-app/tasks")
+        self.dut.adb.shell("cat /dev/urandom > /dev/null &")
diff --git a/acts_tests/acts_contrib/test_utils/bt/simulated_carkit_device.py b/acts_tests/acts_contrib/test_utils/bt/simulated_carkit_device.py
new file mode 100644
index 0000000..03ccdef
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/bt/simulated_carkit_device.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+
+from acts.controllers import android_device
+from acts_contrib.test_utils.bt.bt_test_utils import bluetooth_enabled_check
+
+# TODO: This class to be deprecated for
+# ../acts/test_utils/abstract_devices/bluetooth_handsfree_abstract_device.py
+
+
+class SimulatedCarkitDevice():
+    def __init__(self, serial):
+        self.ad = android_device.create(serial)[0]
+        if not bluetooth_enabled_check(self.ad):
+            asserts.fail("No able to turn on bluetooth")
+        self.mac_address = self.ad.droid.bluetoothGetLocalAddress()
+        self.ad.droid.bluetoothToggleState(False)
+        self.ad.droid.bluetoothMediaConnectToCarMBS()
+
+    def destroy(self):
+        self.ad.clean_up()
+
+    def accept_call(self):
+        return self.ad.droid.telecomAcceptRingingCall(None)
+
+    def end_call(self):
+        return self.ad.droid.telecomEndCall()
+
+    def enter_pairing_mode(self):
+        self.ad.droid.bluetoothStartPairingHelper(True)
+        return self.ad.droid.bluetoothMakeDiscoverable()
+
+    def next_track(self):
+        return self.ad.droid.bluetoothMediaPassthrough("skipNext")
+
+    def pause(self):
+        return self.ad.droid.bluetoothMediaPassthrough("pause")
+
+    def play(self):
+        return self.ad.droid.bluetoothMediaPassthrough("play")
+
+    def power_off(self):
+        return self.ad.droid.bluetoothToggleState(False)
+
+    def power_on(self):
+        return self.ad.droid.bluetoothToggleState(True)
+
+    def previous_track(self):
+        return self.ad.droid.bluetoothMediaPassthrough("skipPrev")
+
+    def reject_call(self):
+        return self.ad.droid.telecomCallDisconnect(
+            self.ad.droid.telecomCallGetCallIds()[0])
+
+    def volume_down(self):
+        target_step = self.ad.droid.getMediaVolume() - 1
+        target_step = max(target_step, 0)
+        return self.ad.droid.setMediaVolume(target_step)
+
+    def volume_up(self):
+        target_step = self.ad.droid.getMediaVolume() + 1
+        max_step = self.ad.droid.getMaxMediaVolume()
+        target_step = min(target_step, max_step)
+        return self.ad.droid.setMediaVolume(target_step)
diff --git a/acts_tests/acts_contrib/test_utils/car/__init__.py b/acts_tests/acts_contrib/test_utils/car/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/car/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/car/car_bt_utils.py b/acts_tests/acts_contrib/test_utils/car/car_bt_utils.py
new file mode 100644
index 0000000..a428528
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/car/car_bt_utils.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+# Defines utilities that can be used for making calls indenpendent of
+# subscription IDs. This can be useful when making calls over mediums not SIM
+# based.
+
+# Make a phone call to the specified URI. It is assumed that we are making the
+# call to the user selected default account.
+#
+# We usually want to make sure that the call has ended up in a good state.
+#
+# NOTE: This util is applicable to only non-conference type calls. It is best
+# suited to test cases where only one call is in action at any point of time.
+
+import queue
+import time
+
+from acts import logger
+from acts_contrib.test_utils.bt import bt_test_utils
+from acts_contrib.test_utils.bt.BtEnum import *
+
+
+def set_car_profile_priorities_off(car_droid, ph_droid):
+    """Sets priority of car related profiles to OFF. This avoids
+    autoconnect being triggered randomly. The use of this function
+    is encouraged when you're testing individual profiles in isolation
+
+    Args:
+        log: log object
+        car_droid: Car droid
+        ph_droid: Phone droid
+
+    Returns:
+        True if success, False if fail.
+    """
+    # TODO investigate MCE
+    car_profiles = [BluetoothProfile.A2DP_SINK,
+                    BluetoothProfile.HEADSET_CLIENT,
+                    BluetoothProfile.PBAP_CLIENT, BluetoothProfile.MAP_MCE]
+    bt_test_utils.set_profile_priority(car_droid, ph_droid, car_profiles,
+                                       BluetoothPriorityLevel.PRIORITY_OFF)
diff --git a/acts_tests/acts_contrib/test_utils/car/car_media_utils.py b/acts_tests/acts_contrib/test_utils/car/car_media_utils.py
new file mode 100644
index 0000000..84768b8
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/car/car_media_utils.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+#   Utilities that can be used for testing media related usecases.
+
+# Events dispatched from the RPC Server
+EVENT_PLAY_RECEIVED = "playReceived"
+EVENT_PAUSE_RECEIVED = "pauseReceived"
+EVENT_SKIP_NEXT_RECEIVED = "skipNextReceived"
+EVENT_SKIP_PREV_RECEIVED = "skipPrevReceived"
+
+# Passthrough Commands sent to the RPC Server
+CMD_MEDIA_PLAY = "play"
+CMD_MEDIA_PAUSE = "pause"
+CMD_MEDIA_SKIP_NEXT = "skipNext"
+CMD_MEDIA_SKIP_PREV = "skipPrev"
+
+# MediaMetaData keys. Keep them the same as in BluetoothMediaFacade.
+MEDIA_KEY_TITLE = "keyTitle"
+MEDIA_KEY_ALBUM = "keyAlbum"
+MEDIA_KEY_ARTIST = "keyArtist"
+MEDIA_KEY_DURATION = "keyDuration"
+MEDIA_KEY_NUM_TRACKS = "keyNumTracks"
+
+class PlaybackState:
+    PAUSE = 2
+    PLAY = 3
+
+def verifyEventReceived(log, device, event, timeout):
+    """
+    Verify if the event was received from the given device.
+    When a fromDevice talks to a toDevice and expects an event back,
+    this util function can be used to see if the toDevice posted it.
+    Args:
+        log:        The logging object
+        device:     The device to pop the event from
+        event:      The event we are interested in.
+        timeout:    The time in seconds before we timeout
+    Returns:
+        True        if the event was received
+        False       if we timed out waiting for the event
+    """
+    try:
+        device.ed.pop_event(event, timeout)
+    except Exception:
+        log.info(" {} Event Not received".format(event))
+        return False
+    log.info("Event Received : {}".format(event))
+    return True
+
+
+def send_media_passthrough_cmd(log,
+                               fromDevice,
+                               toDevice,
+                               cmd,
+                               expctEvent,
+                               timeout=1.0):
+    """
+    Send a media passthrough command from one device to another
+    via bluetooth.
+    Args:
+        log:        The logging object
+        fromDevice: The device to send the command from
+        toDevice:   The device the command is sent to
+        cmd:        The passthrough command to send
+        expctEvent: The expected event
+        timeout:    The time in seconds before we timeout, deafult = 1sec
+    Returns:
+        True        if the event was received
+        False       if we timed out waiting for the event
+    """
+    log.info("Sending passthru : {}".format(cmd))
+    fromDevice.droid.bluetoothMediaPassthrough(cmd)
+    return verifyEventReceived(log, toDevice, expctEvent, timeout)
+
+
+def log_metadata(log, metadata):
+    """
+    Log the Metadata to the console.
+    Args:
+        log:        The logging object
+        metadata:   Dictionary of the song's metadata
+    """
+    title = metadata[MEDIA_KEY_TITLE]
+    album = metadata[MEDIA_KEY_ALBUM]
+    artist = metadata[MEDIA_KEY_ARTIST]
+    duration = metadata[MEDIA_KEY_DURATION]
+    numTracks = metadata[MEDIA_KEY_NUM_TRACKS]
+    log.info("Playing Artist: {}, Album: {}, Title: {}".format(artist, album,
+                                                               title))
+    log.info("Duration: {}, NumTracks: {}".format(duration, numTracks))
+
+
+def compare_metadata(log, metadata1, metadata2):
+    """
+    Compares the Metadata between the two devices
+    Args:
+        log:        The logging object
+        metadata1    Media Metadata of device1
+        metadata2    Media Metadata of device2
+    Returns:
+        True        if the Metadata matches
+        False       if the Metadata do not match
+    """
+    log.info("Device1 metadata:")
+    log_metadata(log, metadata1)
+    log.info("Device2 metadata:")
+    log_metadata(log, metadata2)
+
+    if not (metadata1[MEDIA_KEY_TITLE] == metadata2[MEDIA_KEY_TITLE]):
+        log.info("Song Titles do not match")
+        return False
+
+    if not (metadata1[MEDIA_KEY_ALBUM] == metadata2[MEDIA_KEY_ALBUM]):
+        log.info("Song Albums do not match")
+        return False
+
+    if not (metadata1[MEDIA_KEY_ARTIST] == metadata2[MEDIA_KEY_ARTIST]):
+        log.info("Song Artists do not match")
+        return False
+
+    if not (metadata1[MEDIA_KEY_DURATION] == metadata2[MEDIA_KEY_DURATION]):
+        log.info("Song Duration do not match")
+        return False
+
+    if not (metadata1[MEDIA_KEY_NUM_TRACKS] == metadata2[MEDIA_KEY_NUM_TRACKS]
+    ):
+        log.info("Song Num Tracks do not match")
+        return False
+
+    return True
+
+
+def check_metadata(log, device1, device2):
+    """
+    Gets the now playing metadata from 2 devices and checks if they are the same
+    Args:
+        log:        The logging object
+        device1     Device 1
+        device2     Device 2
+    Returns:
+        True        if the Metadata matches
+        False       if the Metadata do not match
+    """
+    metadata1 = device1.droid.bluetoothMediaGetCurrentMediaMetaData()
+    if metadata1 is None:
+        return False
+
+    metadata2 = device2.droid.bluetoothMediaGetCurrentMediaMetaData()
+    if metadata2 is None:
+        return False
+    return compare_metadata(log, metadata1, metadata2)
+
+
+def isMediaSessionActive(log, device, mediaSession):
+    """
+    Checks if the passed mediaSession is active.
+    Used to see if the device is playing music.
+    Args:
+        log:            The logging object
+        device          Device to check
+        mediaSession    MediaSession to check if it is active
+    Returns:
+        True            if the given mediaSession is active
+        False           if the given mediaSession is not active
+    """
+    # Get a list of MediaSession tags (String) that is currently active
+    activeSessions = device.droid.bluetoothMediaGetActiveMediaSessions()
+    if len(activeSessions) > 0:
+        for session in activeSessions:
+            log.info(session)
+            if (session == mediaSession):
+                return True
+    log.info("No Media Sessions")
+    return False
diff --git a/acts_tests/acts_contrib/test_utils/car/car_telecom_utils.py b/acts_tests/acts_contrib/test_utils/car/car_telecom_utils.py
new file mode 100644
index 0000000..7dbac40
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/car/car_telecom_utils.py
@@ -0,0 +1,491 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+# Defines utilities that can be used for making calls indenpendent of
+# subscription IDs. This can be useful when making calls over mediums not SIM
+# based.
+
+# Make a phone call to the specified URI. It is assumed that we are making the
+# call to the user selected default account.
+#
+# We usually want to make sure that the call has ended up in a good state.
+#
+# NOTE: This util is applicable to only non-conference type calls. It is best
+# suited to test cases where only one call is in action at any point of time.
+
+import queue
+import time
+
+from acts import logger
+from acts_contrib.test_utils.tel import tel_defines
+
+def dial_number(log, ad, uri):
+    """Dial a number
+
+    Args:
+        log: log object
+        ad: android device object
+        uri: Tel number to dial
+
+    Returns:
+        True if success, False if fail.
+    """
+    log.info("Dialing up droid {} call uri {}".format(
+        ad.serial, uri))
+
+    # First check that we are not in call.
+    if ad.droid.telecomIsInCall():
+        log.info("We're still in call {}".format(ad.serial))
+        return False
+
+    # Start tracking updates.
+    ad.droid.telecomStartListeningForCallAdded()
+    #If a phone number is passed in
+    if "tel:" not in uri:
+        uri = "tel:" + uri
+    ad.droid.telecomCallTelUri(uri)
+
+    event = None
+    try:
+        event = ad.ed.pop_event(
+            tel_defines.EventTelecomCallAdded,
+            tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
+    except queue.Empty:
+        log.info(
+            "Did not get {} event!".format(tel_defines.EventTelecomCallAdded))
+        # Return failure.
+        return False
+    finally:
+        ad.droid.telecomStopListeningForCallAdded()
+
+    call_id = event['data']['CallId']
+    log.info("Call ID: {} dev {}".format(call_id, ad.serial))
+
+    if not call_id:
+        log.info("CallId is empty!")
+        return False
+    if not wait_for_dialing(log, ad):
+        return False
+
+    return call_id
+
+def wait_for_call_state(log, ad, call_id, state):
+    """Wait for the given call id to transition to the given call state.
+
+    Args:
+        log: log object
+        ad: android device object
+        call_id: ID of the call that we're waiting for the call state to
+        transition into.
+        state: desired final state.
+
+    Returns:
+        True if success, False if fail.
+    """
+    # Lets track the call now.
+    # NOTE: Disable this everywhere we return.
+    ad.droid.telecomCallStartListeningForEvent(
+        call_id, tel_defines.EVENT_CALL_STATE_CHANGED)
+
+    # We may have missed the update so do a quick check.
+    if ad.droid.telecomCallGetCallState(call_id) == state:
+        log.info("Call ID {} already in {} dev {}!".format(
+            call_id, state, ad.serial))
+        ad.droid.telecomCallStopListeningForEvent(call_id,
+            tel_defines.EventTelecomCallStateChanged)
+        return True
+
+    # If not then we need to poll for the event.
+    # We return if we have found a match or we timeout for further updates of
+    # the call
+    end_time = time.time() + 10
+    while True and time.time() < end_time:
+        try:
+            event = ad.ed.pop_event(
+                tel_defines.EventTelecomCallStateChanged,
+                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
+            call_state = event['data']['Event']
+            if call_state == state:
+                ad.droid.telecomCallStopListeningForEvent(call_id,
+                    tel_defines.EventTelecomCallStateChanged)
+                return True
+            else:
+                log.info("Droid {} in call {} state {}".format(
+                    ad.serial, call_id, call_state))
+                continue
+        except queue.Empty:
+            log.info("Did not get into state {} dev {}".format(
+                state, ad.serial))
+            ad.droid.telecomCallStopListeningForEvent(call_id,
+                tel_defines.EventTelecomCallStateChanged)
+            return False
+    return False
+
+def hangup_call(log, ad, call_id):
+    """Hangup a number
+
+    Args:
+        log: log object
+        ad: android device object
+        call_id: Call to hangup.
+
+    Returns:
+        True if success, False if fail.
+    """
+    log.info("Hanging up droid {} call {}".format(
+        ad.serial, call_id))
+    # First check that we are in call, otherwise fail.
+    if not ad.droid.telecomIsInCall():
+        log.info("We are not in-call {}".format(ad.serial))
+        return False
+
+    # Make sure we are registered with the events.
+    ad.droid.telecomStartListeningForCallRemoved()
+
+    # Disconnect call.
+    ad.droid.telecomCallDisconnect(call_id)
+
+    # Wait for removed event.
+    event = None
+    try:
+        event = ad.ed.pop_event(
+            tel_defines.EventTelecomCallRemoved,
+            tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
+    except queue.Empty:
+        log.info("Did not get TelecomCallRemoved event")
+        return False
+    finally:
+        ad.droid.telecomStopListeningForCallRemoved()
+
+    log.info("Removed call {}".format(event))
+    if event['data']['CallId'] != call_id:
+        return False
+
+    return True
+
+def hangup_conf(log, ad, conf_id, timeout=10):
+    """Hangup a conference call
+
+    Args:
+        log: log object
+        ad: android device object
+        conf_id: Conf call to hangup.
+
+    Returns:
+        True if success, False if fail.
+    """
+    log.info("hangup_conf: Hanging up droid {} call {}".format(
+        ad.serial, conf_id))
+
+    # First check that we are in call, otherwise fail.
+    if not ad.droid.telecomIsInCall():
+        log.info("We are not in-call {}".format(ad.serial))
+        return False
+
+    # Disconnect call.
+    ad.droid.telecomCallDisconnect(conf_id)
+
+    start_time = time.time()
+    while time.time() < start_time + timeout:
+        call_ids = get_calls_in_states(log, ad, [tel_defines.CALL_STATE_ACTIVE])
+        log.debug("Active calls {}".format(call_ids))
+        if not call_ids:
+            return True
+        time.sleep(1)
+    log.error("Failed to hang up all conference participants")
+    return False
+
+def accept_call(log, ad, call_id):
+    """Accept a number
+
+    Args:
+        log: log object
+        ad: android device object
+        call_id: Call to accept.
+
+    Returns:
+        True if success, False if fail.
+    """
+    log.info("Accepting call at droid {} call {}".format(
+        ad.serial, call_id))
+    # First check we are in call, otherwise fail.
+    if not ad.droid.telecomIsInCall():
+        log.info("We are not in-call {}".format(ad.serial))
+        return False
+
+    # Accept the call and wait for the call to be accepted on AG.
+    ad.droid.telecomCallAnswer(call_id, tel_defines.VT_STATE_AUDIO_ONLY)
+    if not wait_for_call_state(log, ad, call_id, tel_defines.CALL_STATE_ACTIVE):
+        log.error("Call {} on droid {} not active".format(
+            call_id, ad.serial))
+        return False
+
+    return True
+
+def wait_for_not_in_call(log, ad):
+    """Wait for the droid to be OUT OF CALLING state.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    ad.droid.telecomStartListeningForCallRemoved()
+
+    calls = ad.droid.telecomCallGetCallIds()
+    if len(calls) > 1:
+        log.info("More than one call {} {}".format(calls, ad.serial))
+        return False
+
+    if len(calls) > 0:
+        log.info("Got calls {} for {}".format(
+            calls, ad.serial))
+        try:
+            event = ad.ed.pop_event(
+                tel_defines.EventTelecomCallRemoved,
+                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
+        except queue.Empty:
+            log.info("wait_for_not_in_call Did not get {} droid {}".format(
+                tel_defines.EventTelecomCallRemoved,
+                ad.serial))
+            return False
+        finally:
+            ad.droid.telecomStopListeningForCallRemoved()
+
+    # Either we removed the only call or we never had a call previously, either
+    # ways simply check if we are in in call now.
+    return (not ad.droid.telecomIsInCall())
+
+def wait_for_dialing(log, ad):
+    """Wait for the droid to be in dialing state.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    # Start listening for events before anything else happens.
+    ad.droid.telecomStartListeningForCallAdded()
+
+    # First check if we re in call, then simply return.
+    if ad.droid.telecomIsInCall():
+        ad.droid.telecomStopListeningForCallAdded()
+        return True
+
+    call_id = None
+    # Now check if we already have calls matching the state.
+    calls_in_state = get_calls_in_states(log, ad,
+                                         [tel_defines.CALL_STATE_CONNECTING,
+                                         tel_defines.CALL_STATE_DIALING])
+
+    # If not then we need to poll for the calls themselves.
+    if len(calls_in_state) == 0:
+        event = None
+        try:
+            event = ad.ed.pop_event(
+                tel_defines.EventTelecomCallAdded,
+                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
+        except queue.Empty:
+            log.info("Did not get {}".format(
+                tel_defines.EventTelecomCallAdded))
+            return False
+        finally:
+            ad.droid.telecomStopListeningForCallAdded()
+        call_id = event['data']['CallId']
+    else:
+        call_id = calls_in_state[0]
+
+    # We may still not be in-call if the call setup is going on.
+    # We wait for the call state to move to dialing.
+    log.info("call id {} droid {}".format(call_id, ad.serial))
+    if not wait_for_call_state(
+        log, ad, call_id, tel_defines.CALL_STATE_DIALING):
+        return False
+
+    # Finally check the call state.
+    return ad.droid.telecomIsInCall()
+
+def wait_for_ringing(log, ad):
+    """Wait for the droid to be in ringing state.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    log.info("waiting for ringing {}".format(ad.serial))
+    # Start listening for events before anything else happens.
+    ad.droid.telecomStartListeningForCallAdded()
+
+    # First check if we re in call, then simply return.
+    if ad.droid.telecomIsInCall():
+        log.info("Device already in call {}".format(ad.serial))
+        ad.droid.telecomStopListeningForCallAdded()
+        return True
+
+    call_id = None
+    # Now check if we already have calls matching the state.
+    calls_in_state = ad.droid.telecomCallGetCallIds()
+
+    for c_id in calls_in_state:
+        if ad.droid.telecomCallGetCallState(c_id) == tel_defines.CALL_STATE_RINGING:
+            return True
+
+    event = None
+    call_id = None
+    try:
+        event = ad.ed.pop_event(
+            tel_defines.EventTelecomCallAdded,
+            tel_defines.MAX_WAIT_TIME_CALLEE_RINGING)
+    except queue.Empty:
+        log.info("Did not get {} droid {}".format(
+            tel_defines.EventTelecomCallAdded,
+            ad.serial))
+        return False
+    finally:
+        ad.droid.telecomStopListeningForCallAdded()
+    call_id = event['data']['CallId']
+    log.info("wait_for_ringing call found {} dev {}".format(
+        call_id, ad.serial))
+
+    # If the call already existed then we would have returned above otherwise
+    # we will verify that the newly added call is indeed ringing.
+    if not wait_for_call_state(
+        log, ad, call_id, tel_defines.CALL_STATE_RINGING):
+        log.info("No ringing call id {} droid {}".format(
+            call_id, ad.serial))
+        return False
+    return True
+
+def wait_for_active(log, ad):
+    """Wait for the droid to be in active call state.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    log.info("waiting for active {}".format(ad.serial))
+    # Start listening for events before anything else happens.
+    ad.droid.telecomStartListeningForCallAdded()
+
+    call_id = None
+    # Now check if we already have calls matching the state.
+    calls_in_state = ad.droid.telecomCallGetCallIds()
+
+    if len(calls_in_state) == 0:
+        event = None
+        try:
+            event = ad.ed.pop_event(
+                tel_defines.EventTelecomCallAdded,
+                tel_defines.MAX_WAIT_TIME_CALLEE_RINGING)
+        except queue.Empty:
+            log.info("Did not get {} droid {}".format(
+                tel_defines.EventTelecomCallAdded,
+                ad.serial))
+            return False
+        finally:
+            ad.droid.telecomStopListeningForCallAdded()
+        call_id = event['data']['CallId']
+        log.info("wait_for_ringing call found {} dev {}".format(
+            call_id, ad.serial))
+    else:
+        call_id = calls_in_state[0]
+
+    # We have found a new call to be added now wait it to transition into
+    # active state.
+    if not wait_for_call_state(
+        log, ad, call_id, tel_defines.CALL_STATE_ACTIVE):
+        log.info("No active call id {} droid {}".format(
+            call_id, ad.serial))
+        return False
+    return True
+
+def wait_for_conference(log, ad, participants=2, timeout=10):
+    """Wait for the droid to be in a conference with calls specified
+    in conf_calls.
+
+    Args:
+        log: log object
+        ad: android device object
+        participants: conference participant count
+
+    Returns:
+        call_id if success, None if fail.
+    """
+
+    def get_conference_id(callers):
+        for call_id in callers:
+            call_details = ad.droid.telecomCallGetCallById(call_id).get("Details")
+            if set([tel_defines.CALL_PROPERTY_CONFERENCE]).issubset(call_details.get("Properties")):
+                return call_id
+        return None
+
+    log.info("waiting for conference {}".format(ad.serial))
+    start_time = time.time()
+    while time.time() < start_time + timeout:
+        participant_callers = get_calls_in_states(log, ad, [tel_defines.CALL_STATE_ACTIVE])
+        if (len(participant_callers) == participants + 1):
+            return get_conference_id(participant_callers)
+        time.sleep(1)
+    return None
+
+def get_call_id_children(log, ad, call_id):
+    """Return the list of calls that are children to call_id
+
+    Args:
+        log: log object
+        ad: android device object
+        call_id: Conference call id
+
+    Returns:
+        List containing call_ids.
+    """
+    call = ad.droid.telecomCallGetCallById(call_id)
+    call_chld = set(call['Children'])
+    log.info("get_call_id_children droid {} call {} children {}".format(
+        ad.serial, call, call_chld))
+    return call_chld
+
+def get_calls_in_states(log, ad, call_states):
+    """Return the list of calls that are any of the states passed in call_states
+
+    Args:
+        log: log object
+        ad: android device object
+        call_states: List of desired call states
+
+    Returns:
+        List containing call_ids.
+    """
+    # Get the list of calls.
+    call_ids = ad.droid.telecomCallGetCallIds()
+    call_in_state = []
+    for call_id in call_ids:
+        call = ad.droid.telecomCallGetCallById(call_id)
+        log.info("Call id: {} desc: {}".format(call_id, call))
+        if call['State'] in call_states:
+            log.info("Adding call id {} to result set.".format(call_id))
+            call_in_state.append(call_id)
+    return call_in_state
diff --git a/acts_tests/acts_contrib/test_utils/car/tel_telecom_utils.py b/acts_tests/acts_contrib/test_utils/car/tel_telecom_utils.py
new file mode 100644
index 0000000..fb0fc84
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/car/tel_telecom_utils.py
@@ -0,0 +1,324 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+# Defines utilities that can be used for making calls independent of
+# subscription IDs. This can be useful when making calls over mediums not SIM
+# based.
+
+import queue
+import time
+
+from acts import logger
+from acts_contrib.test_utils.tel import tel_defines
+
+def dial_number(log, ad, uri):
+    """Dial a Tel: URI
+
+    Args:
+        log: log object
+        ad: android device object
+        uri: Tel uri dial
+
+    Returns:
+        True if success, False if fail.
+    """
+    if "tel:" not in uri:
+        uri = "tel:" + uri
+    log.info("Dialing up droid {} call uri {}".format(
+        ad.serial, uri))
+
+    # Start tracking updates.
+    ad.droid.telecomStartListeningForCallAdded()
+
+    ad.droid.telecomCallTelUri(uri)
+
+    event = None
+    try:
+        event = ad.ed.pop_event(
+            tel_defines.EventTelecomCallAdded,
+            tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
+    except queue.Empty:
+        log.info(
+            "Did not get {} event!".format(tel_defines.EventTelecomCallAdded))
+        # Return failure.
+        return False
+    finally:
+        ad.droid.telecomStopListeningForCallAdded()
+
+    call_id = event['data']['CallId']
+    log.info("Call ID: {} dev {}".format(call_id, ad.serial))
+
+    if not call_id:
+        log.info("CallId is empty!")
+        return False
+    if not wait_for_dialing(log, ad):
+        return False
+
+    return call_id
+
+def wait_for_call_state(log, ad, call_id, state):
+    """Wait for the given call id to transition to the given call state.
+
+    Args:
+        log: log object
+        ad: android device object
+        call_id: ID of the call that we're waiting for the call state to
+        transition into.
+        state: desired final state.
+
+    Returns:
+        True if success, False if fail.
+    """
+    # Lets track the call now.
+    ad.droid.telecomCallStartListeningForEvent(
+        call_id, tel_defines.EVENT_CALL_STATE_CHANGED)
+
+    # We may have missed the update so do a quick check.
+    if ad.droid.telecomCallGetCallState(call_id) == state:
+        log.info("Call ID {} already in {} dev {}!".format(
+            call_id, state, ad.serial))
+        return state
+
+    # If not then we need to poll for the event.
+    try:
+        event = ad.ed.pop_event(
+            tel_defines.EventTelecomCallStateChanged,
+            tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
+        call_state = event['data']['Event']
+    except queue.Empty:
+        log.info("Did not get into state {} dev {}".format(
+            state, ad.serial))
+        return None
+    finally:
+        ad.droid.telecomCallStopListeningForEvent(call_id,
+            tel_defines.EventTelecomCallStateChanged)
+    return call_state
+
+def hangup_call(log, ad, call_id):
+    """Hangup a number
+
+    Args:
+        log: log object
+        ad: android device object
+        call_id: Call to hangup.
+
+    Returns:
+        True if success, False if fail.
+    """
+    log.info("Hanging up droid {} call {}".format(
+        ad.serial, call_id))
+    # First check that we are in call, otherwise fail.
+    if not ad.droid.telecomIsInCall():
+        log.info("We are not in-call {}".format(ad.serial))
+        return False
+
+    # Make sure we are registered with the events.
+    ad.droid.telecomStartListeningForCallRemoved()
+
+    # Disconnect call.
+    ad.droid.telecomCallDisconnect(call_id)
+
+    # Wait for removed event.
+    event = None
+    try:
+        event = ad.ed.pop_event(
+            tel_defines.EventTelecomCallRemoved,
+            tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
+    except queue.Empty:
+        log.info("Did not get TelecomCallRemoved event")
+        return False
+    finally:
+        ad.droid.telecomStopListeningForCallRemoved()
+
+    log.info("Removed call {}".format(event))
+    if event['data']['CallId'] != call_id:
+        return False
+
+    return True
+
+def wait_for_not_in_call(log, ad):
+    """Wait for the droid to be OUT OF CALLING state.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    ad.droid.telecomStartListeningForCallRemoved()
+
+    calls = ad.droid.telecomCallGetCallIds()
+
+    timeout = time.time() + tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
+    remaining_time = tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE
+
+    if len(calls) == 0:
+        log.info("Got calls {} for {}".format(
+            calls, ad.serial))
+        try:
+            while len(calls) > 0:
+                event = ad.ed.pop_event(
+                    tel_defines.EventTelecomCallRemoved,
+                    remaining_time)
+                remaining_time = timeout - time.time()
+                if (remaining_time <= 0
+                        or len(ad.droid.telecomCallGetCallIds() == 0)):
+                    break
+
+        except queue.Empty:
+            log.info("wait_for_not_in_call Did not get {} droid {}".format(
+                tel_defines.EventTelecomCallRemoved,
+                ad.serial))
+            return False
+        finally:
+            ad.droid.telecomStopListeningForCallRemoved()
+
+    # Either we removed the only call or we never had a call previously, either
+    # ways simply check if we are in in call now.
+    return (not ad.droid.telecomIsInCall())
+
+def wait_for_dialing(log, ad):
+    """Wait for the droid to be in dialing state.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    # Start listening for events before anything else happens.
+    ad.droid.telecomStartListeningForCallAdded()
+
+    call_id = None
+    # Now check if we already have calls matching the state.
+    calls_in_state = get_calls_in_states(log, ad,
+                                         [tel_defines.CALL_STATE_CONNECTING,
+                                         tel_defines.CALL_STATE_DIALING])
+
+    # If not then we need to poll for the calls themselves.
+    if len(calls_in_state) == 0:
+        event = None
+        try:
+            event = ad.ed.pop_event(
+                tel_defines.EventTelecomCallAdded,
+                tel_defines.MAX_WAIT_TIME_CONNECTION_STATE_UPDATE)
+        except queue.Empty:
+            log.info("Did not get {}".format(
+                tel_defines.EventTelecomCallAdded))
+            return False
+        finally:
+            ad.droid.telecomStopListeningForCallAdded()
+        call_id = event['data']['CallId']
+    else:
+        call_id = calls_in_state[0]
+
+    # We may still not be in-call if the call setup is going on.
+    # We wait for the call state to move to dialing.
+    log.info("call id {} droid {}".format(call_id, ad.serial))
+    curr_state = wait_for_call_state(
+        log, ad, call_id, tel_defines.CALL_STATE_DIALING)
+    if curr_state == tel_defines.CALL_STATE_CONNECTING:
+        log.info("Got connecting state now waiting for dialing state.")
+        if wait_for_call_state(
+            log, ad, call_id, tel_defines.CALL_STATE_DIALING) != \
+            tel_defines.CALL_STATE_DIALING:
+            return False
+    else:
+        if curr_state != tel_defines.CALL_STATE_DIALING:
+            log.info("First state is not dialing")
+            return False
+
+    # Finally check the call state.
+    log.info("wait_for_dialing returns " + str(ad.droid.telecomIsInCall()) +
+             " " + str(ad.serial))
+    return ad.droid.telecomIsInCall()
+
+def wait_for_ringing(log, ad):
+    """Wait for the droid to be in ringing state.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        True if success, False if fail.
+    """
+    log.info("waiting for ringing {}".format(ad.serial))
+    # Start listening for events before anything else happens.
+    ad.droid.telecomStartListeningForCallAdded()
+
+    # First check if we re in call, then simply return.
+    if ad.droid.telecomIsInCall():
+        log.info("Device already in call {}".format(ad.serial))
+        ad.droid.telecomStopListeningForCallAdded()
+        return True
+
+    call_id = None
+    # Now check if we already have calls matching the state.
+    calls_in_state = ad.droid.telecomCallGetCallIds()
+
+    if len(calls_in_state) == 0:
+        event = None
+        try:
+            event = ad.ed.pop_event(
+                tel_defines.EventTelecomCallAdded,
+                tel_defines.MAX_WAIT_TIME_CALLEE_RINGING)
+        except queue.Empty:
+            log.info("Did not get {} droid {}".format(
+                tel_defines.EventTelecomCallAdded,
+                ad.serial))
+            return False
+        finally:
+            ad.droid.telecomStopListeningForCallAdded()
+        call_id = event['data']['CallId']
+        log.info("wait_for_ringing call found {} dev {}".format(
+            call_id, ad.serial))
+    else:
+        call_id = calls_in_state[0]
+
+    # A rining call is ringing when it is added so simply wait for ringing
+    # state.
+    if wait_for_call_state(
+        log, ad, call_id, tel_defines.CALL_STATE_RINGING) != \
+        tel_defines.CALL_STATE_RINGING:
+        log.info("No ringing call id {} droid {}".format(
+            call_id, ad.serial))
+        return False
+    return True
+
+def get_calls_in_states(log, ad, call_states):
+    """Return the list of calls that are any of the states passed in call_states
+
+    Args:
+        log: log object
+        ad: android device object
+        call_states: List of desired call states
+
+    Returns:
+        List containing call_ids.
+    """
+    # Get the list of calls.
+    call_ids = ad.droid.telecomCallGetCallIds()
+    call_in_state = []
+    for call_id in call_ids:
+        call = ad.droid.telecomCallGetCallById(call_id)
+        log.info("Call id: {} desc: {}".format(call_id, call))
+        if call['State'] in call_states:
+            log.info("Adding call id {} to result set.".format(call_id))
+            call_in_state.append(call_id)
+    return call_in_state
diff --git a/acts_tests/acts_contrib/test_utils/coex/CoexBaseTest.py b/acts_tests/acts_contrib/test_utils/coex/CoexBaseTest.py
new file mode 100644
index 0000000..e0532d9
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/coex/CoexBaseTest.py
@@ -0,0 +1,305 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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 json
+import os
+import threading
+import time
+
+from acts.base_test import BaseTestClass
+from acts.controllers import android_device
+from acts.controllers import relay_device_controller
+from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import enable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.coex.coex_test_utils import A2dpDumpsysParser
+from acts_contrib.test_utils.coex.coex_test_utils import (
+    collect_bluetooth_manager_dumpsys_logs)
+from acts_contrib.test_utils.coex.coex_test_utils import configure_and_start_ap
+from acts_contrib.test_utils.coex.coex_test_utils import iperf_result
+from acts_contrib.test_utils.coex.coex_test_utils import parse_fping_results
+from acts_contrib.test_utils.coex.coex_test_utils import wifi_connection_check
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi.wifi_performance_test_utils import get_iperf_arg_string
+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
+
+AVRCP_WAIT_TIME = 3
+
+
+class CoexBaseTest(BaseTestClass):
+
+    class IperfVariables:
+
+        def __init__(self, current_test_name):
+            self.iperf_started = False
+            self.bidirectional_client_path = None
+            self.bidirectional_server_path = None
+            self.iperf_server_path = None
+            self.iperf_client_path = None
+            self.throughput = []
+            self.protocol = current_test_name.split("_")[-2]
+            self.stream = current_test_name.split("_")[-1]
+            self.is_bidirectional = False
+            if self.stream == 'bidirectional':
+                self.is_bidirectional = True
+
+    def setup_class(self):
+        super().setup_class()
+        self.pri_ad = self.android_devices[0]
+        if len(self.android_devices) == 2:
+            self.sec_ad = self.android_devices[1]
+        elif len(self.android_devices) == 3:
+            self.third_ad = self.android_devices[2]
+        self.ssh_config = None
+
+        self.counter = 0
+        self.thread_list = []
+        if not setup_multiple_devices_for_bt_test(self.android_devices):
+            self.log.error('Failed to setup devices for bluetooth test')
+            return False
+        req_params = ['network', 'iperf']
+        opt_params = [
+            'AccessPoint', 'RetailAccessPoints', 'RelayDevice',
+            'required_devices'
+        ]
+        self.unpack_userparams(req_params, opt_params)
+
+        self.iperf_server = self.iperf_servers[0]
+        self.iperf_client = self.iperf_clients[0]
+
+        if hasattr(self, 'RelayDevice'):
+            self.audio_receiver = self.relay_devices[0]
+            self.audio_receiver.power_on()
+            self.headset_mac_address = self.audio_receiver.mac_address
+        else:
+            self.log.warning('Missing Relay config file.')
+
+        if hasattr(self, 'AccessPoint'):
+            self.ap = self.access_points[0]
+            configure_and_start_ap(self.ap, self.network)
+        elif hasattr(self, 'RetailAccessPoints'):
+            self.retail_access_points = retail_ap.create(
+                self.RetailAccessPoints)
+            self.retail_access_point = self.retail_access_points[0]
+            band = self.retail_access_point.band_lookup_by_channel(
+                self.network['channel'])
+            self.retail_access_point.set_channel(band, self.network['channel'])
+        else:
+            self.log.warning('config file have no access point information')
+
+        wifi_test_device_init(self.pri_ad)
+        wifi_connect(self.pri_ad, self.network, num_of_tries=5)
+
+    def setup_test(self):
+        self.tag = 0
+        self.result = {}
+        self.dev_list = {}
+        self.iperf_variables = self.IperfVariables(self.current_test_name)
+        self.a2dp_dumpsys = A2dpDumpsysParser()
+        self.log_path = os.path.join(self.pri_ad.log_path,
+                self.current_test_name)
+        os.makedirs(self.log_path, exist_ok=True)
+        self.json_file = os.path.join(self.log_path, 'test_results.json')
+        for a in self.android_devices:
+            a.ed.clear_all_events()
+        if not wifi_connection_check(self.pri_ad, self.network['SSID']):
+            self.log.error('Wifi connection does not exist')
+            return False
+        if not enable_bluetooth(self.pri_ad.droid, self.pri_ad.ed):
+            self.log.error('Failed to enable bluetooth')
+            return False
+        if hasattr(self, 'required_devices'):
+            if ('discovery' in self.current_test_name or
+                    'ble' in self.current_test_name):
+                self.create_android_relay_object()
+        else:
+            self.log.warning('required_devices is not given in config file')
+
+    def teardown_test(self):
+        self.parsing_results()
+        with open(self.json_file, 'a') as results_file:
+            json.dump(self.result, results_file, indent=4, sort_keys=True)
+        if not disable_bluetooth(self.pri_ad.droid):
+            self.log.info('Failed to disable bluetooth')
+            return False
+        self.destroy_android_and_relay_object()
+
+    def teardown_class(self):
+        if hasattr(self, 'AccessPoint'):
+            self.ap.close()
+        self.reset_wifi_and_store_results()
+
+    def reset_wifi_and_store_results(self):
+        """Resets wifi and store test results."""
+        reset_wifi(self.pri_ad)
+        wifi_toggle_state(self.pri_ad, False)
+
+    def create_android_relay_object(self):
+        """Creates android device object and relay device object if required
+        devices has android device and relay device."""
+        if 'AndroidDevice' in self.required_devices:
+            self.inquiry_devices = android_device.create(
+                self.required_devices['AndroidDevice'])
+            self.dev_list['AndroidDevice'] = self.inquiry_devices
+        if 'RelayDevice' in self.required_devices:
+            self.relay = relay_device_controller.create(
+                self.required_devices['RelayDevice'])
+            self.dev_list['RelayDevice'] = self.relay
+
+    def destroy_android_and_relay_object(self):
+        """Destroys android device object and relay device object if required
+        devices has android device and relay device."""
+        if hasattr(self, 'required_devices'):
+            if ('discovery' in self.current_test_name or
+                    'ble' in self.current_test_name):
+                if hasattr(self, 'inquiry_devices'):
+                    for device in range(len(self.inquiry_devices)):
+                        inquiry_device = self.inquiry_devices[device]
+                        if not disable_bluetooth(inquiry_device.droid):
+                            self.log.info('Failed to disable bluetooth')
+                    android_device.destroy(self.inquiry_devices)
+                if hasattr(self, 'relay'):
+                    relay_device_controller.destroy(self.relay)
+
+    def parsing_results(self):
+        """Result parser for fping results and a2dp packet drops."""
+        if 'fping' in self.current_test_name:
+            output_path = '{}{}{}'.format(self.pri_ad.log_path, '/Fping/',
+                                          'fping_%s.txt' % self.counter)
+            self.result['fping_loss%'] = parse_fping_results(
+                self.fping_params['fping_drop_tolerance'], output_path)
+            self.counter = +1
+        if 'a2dp_streaming' in self.current_test_name:
+            file_path = collect_bluetooth_manager_dumpsys_logs(
+                self.pri_ad, self.current_test_name)
+            self.result['a2dp_packet_drop'] = (
+                self.a2dp_dumpsys.parse(file_path))
+            if self.result['a2dp_packet_drop'] == 0:
+                self.result['a2dp_packet_drop'] = None
+
+    def run_iperf_and_get_result(self):
+        """Frames iperf command based on test and starts iperf client on
+        host machine.
+
+        Returns:
+            throughput: Throughput of the run.
+        """
+        self.iperf_server.start(tag=self.tag)
+        if self.iperf_variables.stream == 'ul':
+            iperf_args = get_iperf_arg_string(
+                duration=self.iperf['duration'],
+                reverse_direction=1,
+                traffic_type=self.iperf_variables.protocol
+            )
+        elif self.iperf_variables.stream == 'dl':
+            iperf_args = get_iperf_arg_string(
+                duration=self.iperf['duration'],
+                reverse_direction=0,
+                traffic_type=self.iperf_variables.protocol
+            )
+        ip = get_phone_ip(self.pri_ad)
+        self.tag = self.tag + 1
+        self.iperf_variables.iperf_client_path = (
+                self.iperf_client.start(ip, iperf_args, self.tag))
+
+        self.iperf_server.stop()
+        if (self.iperf_variables.protocol == 'udp' and
+                self.iperf_variables.stream == 'ul'):
+            throughput = iperf_result(
+                self.log, self.iperf_variables.protocol,
+                self.iperf_variables.iperf_server_path)
+        else:
+            throughput = iperf_result(self.log,
+                                    self.iperf_variables.protocol,
+                                    self.iperf_variables.iperf_client_path)
+
+        if not throughput:
+            self.log.error('Iperf failed/stopped')
+            self.iperf_variables.throughput.append(0)
+        else:
+            self.iperf_variables.throughput.append(
+                str(round(throughput, 2)) + "Mb/s")
+            self.log.info("Throughput: {} Mb/s".format(throughput))
+        self.result["throughput"] = self.iperf_variables.throughput
+        return throughput
+
+    def on_fail(self, test_name, begin_time):
+        """A function that is executed upon a test case failure.
+
+        Args:
+            test_name: Name of the test that triggered this function.
+            begin_time: Logline format timestamp taken when the test started.
+        """
+        self.log.info('Test {} failed, Fetching Btsnoop logs and bugreport'.
+                      format(test_name))
+        take_btsnoop_logs(self.android_devices, self, test_name)
+        self._take_bug_report(test_name, begin_time)
+
+    def run_thread(self, kwargs):
+        """Convenience function to start thread.
+
+        Args:
+            kwargs: Function object to start in thread.
+        """
+        for function in kwargs:
+            self.thread = threading.Thread(target=function)
+            self.thread_list.append(self.thread)
+            self.thread.start()
+
+    def teardown_thread(self):
+        """Convenience function to join thread."""
+        for thread_id in self.thread_list:
+            if thread_id.is_alive():
+                thread_id.join()
+
+    def get_call_volume(self):
+        """Function to get call volume when bluetooth headset connected.
+
+        Returns:
+            Call volume.
+        """
+        return self.pri_ad.adb.shell(
+            'settings list system|grep volume_bluetooth_sco_bt_sco_hs')
+
+    def change_volume(self):
+        """Changes volume with HFP call.
+
+        Returns: True if successful, otherwise False.
+        """
+        if 'Volume_up' and 'Volume_down' in (
+                self.relay_devices[0].relays.keys()):
+            current_volume = self.get_call_volume()
+            self.audio_receiver.press_volume_down()
+            time.sleep(AVRCP_WAIT_TIME)  # wait till volume_changes
+            if current_volume == self.get_call_volume():
+                self.log.error('Decrease volume failed')
+                return False
+            time.sleep(AVRCP_WAIT_TIME)
+            current_volume = self.get_call_volume()
+            self.audio_receiver.press_volume_up()
+            time.sleep(AVRCP_WAIT_TIME)  # wait till volume_changes
+            if current_volume == self.get_call_volume():
+                self.log.error('Increase volume failed')
+                return False
+        else:
+            self.log.warning(
+                'No volume control pins specified in relay config.')
+        return True
diff --git a/acts_tests/acts_contrib/test_utils/coex/CoexPerformanceBaseTest.py b/acts_tests/acts_contrib/test_utils/coex/CoexPerformanceBaseTest.py
new file mode 100644
index 0000000..3180660
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/coex/CoexPerformanceBaseTest.py
@@ -0,0 +1,508 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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 json
+import os
+import time
+from collections import defaultdict
+
+from acts.metrics.loggers.blackbox import BlackboxMetricLogger
+from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts_contrib.test_utils.coex.audio_test_utils import AudioCaptureResult
+from acts_contrib.test_utils.coex.audio_test_utils import get_audio_capture_device
+from acts_contrib.test_utils.coex.CoexBaseTest import CoexBaseTest
+from acts_contrib.test_utils.coex.coex_test_utils import bokeh_chart_plot
+from acts_contrib.test_utils.coex.coex_test_utils import collect_bluetooth_manager_dumpsys_logs
+from acts_contrib.test_utils.coex.coex_test_utils import multithread_func
+from acts_contrib.test_utils.coex.coex_test_utils import wifi_connection_check
+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.utils import get_current_epoch_time
+
+RSSI_POLL_RESULTS = 'Monitoring , Handle: 0x0003, POLL'
+RSSI_RESULTS = 'Monitoring , Handle: 0x0003, '
+
+
+def get_atten_range(start, stop, step):
+    """Function to derive attenuation range for tests.
+
+    Args:
+        start: Start attenuation value.
+        stop: Stop attenuation value.
+        step: Step attenuation value.
+    """
+    temp = start
+    while temp < stop:
+        yield temp
+        temp += step
+
+
+class CoexPerformanceBaseTest(CoexBaseTest):
+    """Base test class for performance tests.
+
+    Attributes:
+        rvr : Dict to save attenuation, throughput, fixed_attenuation.
+        a2dp_streaming : Used to denote a2dp test cases.
+    """
+
+    def __init__(self, controllers):
+        super().__init__(controllers)
+        self.a2dp_streaming = False
+        self.rvr = {}
+        self.bt_range_metric = BlackboxMetricLogger.for_test_case(
+            metric_name='bt_range')
+        self.wifi_max_atten_metric = BlackboxMetricLogger.for_test_case(
+            metric_name='wifi_max_atten')
+        self.wifi_min_atten_metric = BlackboxMetricLogger.for_test_case(
+            metric_name='wifi_min_atten')
+        self.wifi_range_metric = BlackboxMetricLogger.for_test_case(
+            metric_name='wifi_range_metric')
+
+    def setup_class(self):
+        req_params = ['test_params', 'Attenuator']
+        opt_params = ['audio_params']
+        self.unpack_userparams(req_params, opt_params)
+        if hasattr(self, 'Attenuator'):
+            self.num_atten = self.attenuators[0].instrument.num_atten
+        else:
+            self.log.error('Attenuator should be connected to run tests.')
+            return False
+        for i in range(self.num_atten):
+            self.attenuators[i].set_atten(0)
+        super().setup_class()
+        self.performance_files_list = []
+        if "performance_result_path" in self.user_params["test_params"]:
+            self.performance_files_list = [
+                os.path.join(self.test_params["performance_result_path"],
+                             files) for files in os.listdir(
+                                 self.test_params["performance_result_path"])
+            ]
+        self.bt_atten_range = list(get_atten_range(
+                            self.test_params["bt_atten_start"],
+                            self.test_params["bt_atten_stop"],
+                            self.test_params["bt_atten_step"]))
+        self.wifi_atten_range = list(get_atten_range(
+                            self.test_params["attenuation_start"],
+                            self.test_params["attenuation_stop"],
+                            self.test_params["attenuation_step"]))
+
+    def setup_test(self):
+        if ('a2dp_streaming' in self.current_test_name and
+                hasattr(self, 'audio_params')):
+            self.audio = get_audio_capture_device(self.sec_ad, self.audio_params)
+            self.a2dp_streaming = True
+        for i in range(self.num_atten):
+            self.attenuators[i].set_atten(0)
+        if not wifi_connection_check(self.pri_ad, self.network["SSID"]):
+            wifi_connect(self.pri_ad, self.network, num_of_tries=5)
+        super().setup_test()
+
+    def teardown_test(self):
+        self.performance_baseline_check()
+        for i in range(self.num_atten):
+            self.attenuators[i].set_atten(0)
+            current_atten = int(self.attenuators[i].get_atten())
+            self.log.debug(
+                "Setting attenuation to zero : Current atten {} : {}".format(
+                    self.attenuators[i], current_atten))
+        self.a2dp_streaming = False
+        if not disable_bluetooth(self.pri_ad.droid):
+            self.log.info("Failed to disable bluetooth")
+            return False
+        self.destroy_android_and_relay_object()
+        self.rvr = {}
+
+    def teardown_class(self):
+        self.reset_wifi_and_store_results()
+
+    def set_attenuation_and_run_iperf(self, called_func=None):
+        """Sets attenuation and runs iperf for Attenuation max value.
+
+        Args:
+            called_func : Function object to run.
+
+        Returns:
+            True if Pass
+            False if Fail
+        """
+        self.attenuators[self.num_atten - 1].set_atten(0)
+        self.rvr["bt_attenuation"] = []
+        self.rvr["test_name"] = self.current_test_name
+        self.rvr["bt_gap_analysis"] = {}
+        self.rvr["bt_range"] = []
+        status_flag = True
+        for bt_atten in self.bt_atten_range:
+            self.rvr[bt_atten] = {}
+            self.rvr[bt_atten]["fixed_attenuation"] = (
+                self.test_params["fixed_attenuation"][str(
+                    self.network["channel"])])
+            self.log.info('Setting bt attenuation to: {} dB'.format(bt_atten))
+            self.attenuators[self.num_atten - 1].set_atten(bt_atten)
+            for i in range(self.num_atten - 1):
+                self.attenuators[i].set_atten(0)
+            if not wifi_connection_check(self.pri_ad, self.network["SSID"]):
+                wifi_test_device_init(self.pri_ad)
+                wifi_connect(self.pri_ad, self.network, num_of_tries=5)
+            adb_rssi_results = self.pri_ad.search_logcat(RSSI_RESULTS)
+            if adb_rssi_results:
+                self.log.debug(adb_rssi_results[-1])
+                self.log.info('Android device: {}'.format(
+                    (adb_rssi_results[-1]['log_message']).split(',')[5]))
+            (self.rvr[bt_atten]["throughput_received"],
+             self.rvr[bt_atten]["a2dp_packet_drop"],
+             status_flag) = self.rvr_throughput(bt_atten, called_func)
+            self.wifi_max_atten_metric.metric_value = max(self.rvr[bt_atten]
+                                                          ["attenuation"])
+            self.wifi_min_atten_metric.metric_value = min(self.rvr[bt_atten]
+                                                          ["attenuation"])
+
+            if self.rvr[bt_atten]["throughput_received"]:
+                for i, atten in enumerate(self.rvr[bt_atten]["attenuation"]):
+                    if self.rvr[bt_atten]["throughput_received"][i] < 1.0:
+                        self.wifi_range_metric.metric_value = (
+                            self.rvr[bt_atten]["attenuation"][i-1])
+                        break
+                else:
+                    self.wifi_range_metric.metric_value = max(
+                        self.rvr[bt_atten]["attenuation"])
+            else:
+                self.wifi_range_metric.metric_value = max(
+                    self.rvr[bt_atten]["attenuation"])
+            if self.a2dp_streaming:
+                if not any(x > 0 for x in self.a2dp_dropped_list):
+                    self.rvr[bt_atten]["a2dp_packet_drop"] = []
+        if not self.rvr["bt_range"]:
+            self.rvr["bt_range"].append(0)
+        return status_flag
+
+    def rvr_throughput(self, bt_atten, called_func=None):
+        """Sets attenuation and runs the function passed.
+
+        Args:
+            bt_atten: Bluetooth attenuation.
+            called_func: Functions object to run parallely.
+
+        Returns:
+            Throughput, a2dp_drops and True/False.
+        """
+        self.iperf_received = []
+        self.iperf_variables.received = []
+        self.a2dp_dropped_list = []
+        self.rvr["bt_attenuation"].append(bt_atten)
+        self.rvr[bt_atten]["audio_artifacts"] = {}
+        self.rvr[bt_atten]["attenuation"] = []
+        self.rvr["bt_gap_analysis"][bt_atten] = {}
+        for atten in self.wifi_atten_range:
+            tag = '{}_{}'.format(bt_atten, atten)
+            self.rvr[bt_atten]["attenuation"].append(
+                atten + self.rvr[bt_atten]["fixed_attenuation"])
+            self.log.info('Setting wifi attenuation to: {} dB'.format(atten))
+            for i in range(self.num_atten - 1):
+                self.attenuators[i].set_atten(atten)
+            if not wifi_connection_check(self.pri_ad, self.network["SSID"]):
+                self.iperf_received.append(0)
+                return self.iperf_received, self.a2dp_dropped_list, False
+            time.sleep(5)  # Time for attenuation to set.
+            begin_time = get_current_epoch_time()
+            if self.a2dp_streaming:
+                self.audio.start()
+            if called_func:
+                if not multithread_func(self.log, called_func):
+                    self.iperf_received.append(float(str(
+                        self.iperf_variables.received[-1]).strip("Mb/s")))
+                    return self.iperf_received, self.a2dp_dropped_list, False
+            else:
+                self.run_iperf_and_get_result()
+
+            adb_rssi_poll_results = self.pri_ad.search_logcat(
+                RSSI_POLL_RESULTS, begin_time)
+            adb_rssi_results = self.pri_ad.search_logcat(
+                RSSI_RESULTS, begin_time)
+            if adb_rssi_results:
+                self.log.debug(adb_rssi_poll_results)
+                self.log.debug(adb_rssi_results[-1])
+                self.log.info('Android device: {}'.format((
+                    adb_rssi_results[-1]['log_message']).split(',')[5]))
+            if self.a2dp_streaming:
+                self.path = self.audio.stop()
+                analysis_path = AudioCaptureResult(
+                    self.path).audio_quality_analysis(self.audio_params)
+                with open(analysis_path) as f:
+                    self.rvr[bt_atten]["audio_artifacts"][atten] = f.readline()
+                content = json.loads(self.rvr[bt_atten]["audio_artifacts"][atten])
+                self.rvr["bt_gap_analysis"][bt_atten][atten] = {}
+                for idx, data in enumerate(content["quality_result"]):
+                    if data['artifacts']['delay_during_playback']:
+                        self.rvr["bt_gap_analysis"][bt_atten][atten][idx] = (
+                                data['artifacts']['delay_during_playback'])
+                        self.rvr["bt_range"].append(bt_atten)
+                    else:
+                        self.rvr["bt_gap_analysis"][bt_atten][atten][idx] = 0
+                file_path = collect_bluetooth_manager_dumpsys_logs(
+                    self.pri_ad, self.current_test_name)
+                self.a2dp_dropped_list.append(
+                    self.a2dp_dumpsys.parse(file_path))
+            self.iperf_received.append(
+                    float(str(self.iperf_variables.throughput[-1]).strip("Mb/s")))
+        for i in range(self.num_atten - 1):
+            self.attenuators[i].set_atten(0)
+        return self.iperf_received, self.a2dp_dropped_list, True
+
+    def performance_baseline_check(self):
+        """Checks for performance_result_path in config. If present, plots
+        comparision chart else plot chart for that particular test run.
+
+        Returns:
+            True if success, False otherwise.
+        """
+        if self.rvr:
+            with open(self.json_file, 'a') as results_file:
+                json.dump({str(k): v for k, v in self.rvr.items()},
+                          results_file, indent=4, sort_keys=True)
+            self.bt_range_metric.metric_value = self.rvr["bt_range"][0]
+            self.log.info('First occurrence of audio gap in bt '
+                          'range: {}'.format(self.bt_range_metric.metric_value))
+            self.log.info('Bluetooth min range: '
+                          '{} dB'.format(min(self.rvr['bt_attenuation'])))
+            self.log.info('Bluetooth max range: '
+                          '{} dB'.format(max(self.rvr['bt_attenuation'])))
+            self.plot_graph_for_attenuation()
+            if not self.performance_files_list:
+                self.log.warning('Performance file list is empty. Could not '
+                                 'calculate throughput limits')
+                return
+            self.throughput_pass_fail_check()
+        else:
+            self.log.error("Throughput dict empty!")
+            return False
+        return True
+
+    def plot_graph_for_attenuation(self):
+        """Plots graph and add as JSON formatted results for attenuation with
+        respect to its iperf values.
+        """
+        data_sets = defaultdict(dict)
+        legends = defaultdict(list)
+
+        x_label = 'WIFI Attenuation (dB)'
+        y_label = []
+
+        fig_property = {
+            "title": self.current_test_name,
+            "x_label": x_label,
+            "linewidth": 3,
+            "markersize": 10
+        }
+
+        for bt_atten in self.rvr["bt_attenuation"]:
+            y_label.insert(0, 'Throughput (Mbps)')
+            legends[bt_atten].insert(
+                0, str("BT Attenuation @ %sdB" % bt_atten))
+            data_sets[bt_atten]["attenuation"] = (
+                self.rvr[bt_atten]["attenuation"])
+            data_sets[bt_atten]["throughput_received"] = (
+                self.rvr[bt_atten]["throughput_received"])
+
+        if self.a2dp_streaming:
+            for bt_atten in self.bt_atten_range:
+                legends[bt_atten].insert(
+                    0, ('Packet drops(in %) @ {}dB'.format(bt_atten)))
+                data_sets[bt_atten]["a2dp_attenuation"] = (
+                    self.rvr[bt_atten]["attenuation"])
+                data_sets[bt_atten]["a2dp_packet_drops"] = (
+                    self.rvr[bt_atten]["a2dp_packet_drop"])
+            y_label.insert(0, "Packets Dropped")
+        fig_property["y_label"] = y_label
+        shaded_region = None
+
+        if "performance_result_path" in self.user_params["test_params"]:
+            shaded_region = self.comparision_results_calculation(data_sets, legends)
+
+        output_file_path = os.path.join(self.pri_ad.log_path,
+                                        self.current_test_name,
+                                        "attenuation_plot.html")
+        bokeh_chart_plot(list(self.rvr["bt_attenuation"]),
+                         data_sets,
+                         legends,
+                         fig_property,
+                         shaded_region=shaded_region,
+                         output_file_path=output_file_path)
+
+    def comparision_results_calculation(self, data_sets, legends):
+        """Compares rvr results with baseline values by calculating throughput
+        limits.
+
+        Args:
+            data_sets: including lists of x_data and lists of y_data.
+                ex: [[[x_data1], [x_data2]], [[y_data1],[y_data2]]]
+            legends: list of legend for each curve.
+
+        Returns:
+            None if test_file is not found, otherwise shaded_region
+            will be returned.
+        """
+        try:
+            attenuation_path = next(
+                file_name for file_name in self.performance_files_list
+                if self.current_test_name in file_name
+            )
+        except StopIteration:
+            self.log.warning("Test_file not found. "
+                             "No comparision values to calculate")
+            return
+        with open(attenuation_path, 'r') as throughput_file:
+            throughput_results = json.load(throughput_file)
+        for bt_atten in self.bt_atten_range:
+            throughput_received = []
+            user_attenuation = []
+            legends[bt_atten].insert(
+                0, ('Performance Results @ {}dB'.format(bt_atten)))
+            for att in self.rvr[bt_atten]["attenuation"]:
+                attenuation = att - self.rvr[bt_atten]["fixed_attenuation"]
+                throughput_received.append(throughput_results[str(bt_atten)]
+                    ["throughput_received"][attenuation])
+                user_attenuation.append(att)
+            data_sets[bt_atten][
+                "user_attenuation"] = user_attenuation
+            data_sets[bt_atten]["user_throughput"] = throughput_received
+        throughput_limits = self.get_throughput_limits(attenuation_path)
+        shaded_region = defaultdict(dict)
+        for bt_atten in self.bt_atten_range:
+            shaded_region[bt_atten] = {
+                "x_vector": throughput_limits[bt_atten]["attenuation"],
+                "lower_limit":
+                throughput_limits[bt_atten]["lower_limit"],
+                "upper_limit":
+                throughput_limits[bt_atten]["upper_limit"]
+            }
+        return shaded_region
+
+    def total_attenuation(self, performance_dict):
+        """Calculates attenuation with adding fixed attenuation.
+
+        Args:
+            performance_dict: dict containing attenuation and fixed attenuation.
+
+        Returns:
+            Total attenuation is returned.
+        """
+        if "fixed_attenuation" in self.test_params:
+            total_atten = [
+                att + performance_dict["fixed_attenuation"]
+                for att in performance_dict["attenuation"]
+            ]
+            return total_atten
+
+    def throughput_pass_fail_check(self):
+        """Check the test result and decide if it passed or failed
+        by comparing with throughput limits.The pass/fail tolerances are
+        provided in the config file.
+
+        Returns:
+            None if test_file is not found, True if successful,
+            False otherwise.
+        """
+        try:
+            performance_path = next(
+                file_name for file_name in self.performance_files_list
+                if self.current_test_name in file_name
+            )
+        except StopIteration:
+            self.log.warning("Test_file not found. Couldn't "
+                             "calculate throughput limits")
+            return
+        throughput_limits = self.get_throughput_limits(performance_path)
+
+        failure_count = 0
+        for bt_atten in self.bt_atten_range:
+            for idx, current_throughput in enumerate(
+                    self.rvr[bt_atten]["throughput_received"]):
+                current_att = self.rvr[bt_atten]["attenuation"][idx]
+                if (current_throughput <
+                        (throughput_limits[bt_atten]["lower_limit"][idx]) or
+                        current_throughput >
+                        (throughput_limits[bt_atten]["upper_limit"][idx])):
+                    failure_count = failure_count + 1
+                    self.log.info(
+                        "Throughput at {} dB attenuation is beyond limits. "
+                        "Throughput is {} Mbps. Expected within [{}, {}] Mbps.".
+                        format(
+                            current_att, current_throughput,
+                            throughput_limits[bt_atten]["lower_limit"][idx],
+                            throughput_limits[bt_atten]["upper_limit"][
+                                idx]))
+            if failure_count >= self.test_params["failure_count_tolerance"]:
+                self.log.error(
+                    "Test failed. Found {} points outside throughput limits.".
+                    format(failure_count))
+                return False
+            self.log.info(
+                "Test passed. Found {} points outside throughput limits.".
+                format(failure_count))
+            return True
+
+    def get_throughput_limits(self, performance_path):
+        """Compute throughput limits for current test.
+
+        Checks the RvR test result and compares to a throughput limits for
+        the same configuration. The pass/fail tolerances are provided in the
+        config file.
+
+        Args:
+            performance_path: path to baseline file used to generate limits
+
+        Returns:
+            throughput_limits: dict containing attenuation and throughput
+            limit data
+        """
+        with open(performance_path, 'r') as performance_file:
+            performance_results = json.load(performance_file)
+        throughput_limits = defaultdict(dict)
+        for bt_atten in self.bt_atten_range:
+            performance_attenuation = (self.total_attenuation(
+                performance_results[str(bt_atten)]))
+            attenuation = []
+            lower_limit = []
+            upper_limit = []
+            for idx, current_throughput in enumerate(
+                    self.rvr[bt_atten]["throughput_received"]):
+                current_att = self.rvr[bt_atten]["attenuation"][idx]
+                att_distances = [
+                    abs(current_att - performance_att)
+                    for performance_att in performance_attenuation
+                ]
+                sorted_distances = sorted(
+                    enumerate(att_distances), key=lambda x: x[1])
+                closest_indeces = [dist[0] for dist in sorted_distances[0:3]]
+                closest_throughputs = [
+                    performance_results[str(bt_atten)]["throughput_received"][
+                        index] for index in closest_indeces
+                ]
+                closest_throughputs.sort()
+                attenuation.append(current_att)
+                lower_limit.append(
+                    max(closest_throughputs[0] -
+                        max(self.test_params["abs_tolerance"],
+                            closest_throughputs[0] *
+                            self.test_params["pct_tolerance"] / 100), 0))
+                upper_limit.append(closest_throughputs[-1] + max(
+                    self.test_params["abs_tolerance"], closest_throughputs[-1] *
+                    self.test_params["pct_tolerance"] / 100))
+            throughput_limits[bt_atten]["attenuation"] = attenuation
+            throughput_limits[bt_atten]["lower_limit"] = lower_limit
+            throughput_limits[bt_atten]["upper_limit"] = upper_limit
+        return throughput_limits
+
diff --git a/acts_tests/acts_contrib/test_utils/coex/__init__.py b/acts_tests/acts_contrib/test_utils/coex/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/coex/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/coex/audio_capture_device.py b/acts_tests/acts_contrib/test_utils/coex/audio_capture_device.py
new file mode 100644
index 0000000..e76d054
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/coex/audio_capture_device.py
@@ -0,0 +1,229 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import os
+import pyaudio
+import wave
+
+from acts import context
+
+
+WAVE_FILE_TEMPLATE = 'recorded_audio_%s.wav'
+ADB_PATH = 'sdcard/Music/'
+ADB_FILE = 'rec.pcm'
+
+
+class AudioCaptureBase(object):
+    """Base class for Audio capture."""
+
+    def __init__(self):
+
+        self.wave_file = os.path.join(self.log_path, WAVE_FILE_TEMPLATE)
+        self.file_dir = self.log_path
+
+    @property
+    def log_path(self):
+        """Returns current log path."""
+        current_context = context.get_current_context()
+        full_out_dir = os.path.join(current_context.get_full_output_path(),
+                                    'AudioCapture')
+
+        os.makedirs(full_out_dir, exist_ok=True)
+        return full_out_dir
+
+    @property
+    def next_fileno(self):
+        counter = 0
+        while os.path.exists(self.wave_file % counter):
+            counter += 1
+        return counter
+
+    @property
+    def last_fileno(self):
+        return self.next_fileno - 1
+
+    @property
+    def get_last_record_duration_millis(self):
+        """Get duration of most recently recorded file.
+
+        Returns:
+            duration (float): duration of recorded file in milliseconds.
+        """
+        latest_file_path = self.wave_file % self.last_fileno
+        print (latest_file_path)
+        with wave.open(latest_file_path, 'r') as f:
+            frames = f.getnframes()
+            rate = f.getframerate()
+            duration = (frames / float(rate)) * 1000
+        return duration
+
+    def write_record_file(self, audio_params, frames):
+        """Writes the recorded audio into the file.
+
+        Args:
+            audio_params: A dict with audio configuration.
+            frames: Recorded audio frames.
+
+        Returns:
+            file_name: wave file name.
+        """
+        file_name = self.wave_file % self.next_fileno
+        logging.debug('writing to %s' % file_name)
+        wf = wave.open(file_name, 'wb')
+        wf.setnchannels(audio_params['channel'])
+        wf.setsampwidth(audio_params['sample_width'])
+        wf.setframerate(audio_params['sample_rate'])
+        wf.writeframes(frames)
+        wf.close()
+        return file_name
+
+
+class CaptureAudioOverAdb(AudioCaptureBase):
+    """Class to capture audio over android device which acts as the
+    a2dp sink or hfp client. This captures the digital audio and converts
+    to analog audio for post processing.
+    """
+
+    def __init__(self, ad, audio_params):
+        """Initializes CaptureAudioOverAdb.
+
+        Args:
+            ad: An android device object.
+            audio_params: Dict containing audio record settings.
+        """
+        super().__init__()
+        self._ad = ad
+        self.audio_params = audio_params
+        self.adb_path = None
+
+    def start(self):
+        """Start the audio capture over adb."""
+        self.adb_path = os.path.join(ADB_PATH, ADB_FILE)
+        cmd = 'ap2f --usage 1 --start --duration {} --target {}'.format(
+            self.audio_params['duration'], self.adb_path,
+        )
+        self._ad.adb.shell_nb(cmd)
+
+    def stop(self):
+        """Stops the audio capture and stores it in wave file.
+
+        Returns:
+            File name of the recorded file.
+        """
+        cmd = '{} {}'.format(self.adb_path, self.file_dir)
+        self._ad.adb.pull(cmd)
+        self._ad.adb.shell('rm {}'.format(self.adb_path))
+        return self._convert_pcm_to_wav()
+
+    def _convert_pcm_to_wav(self):
+        """Converts raw pcm data into wave file.
+
+        Returns:
+            file_path: Returns the file path of the converted file
+            (digital to analog).
+        """
+        file_to_read = os.path.join(self.file_dir, ADB_FILE)
+        with open(file_to_read, 'rb') as pcm_file:
+            frames = pcm_file.read()
+        file_path = self.write_record_file(self.audio_params, frames)
+        return file_path
+
+
+class CaptureAudioOverLocal(AudioCaptureBase):
+    """Class to capture audio on local server using the audio input devices
+    such as iMic/AudioBox. This class mandates input deivce to be connected to
+    the machine.
+    """
+    def __init__(self, audio_params):
+        """Initializes CaptureAudioOverLocal.
+
+        Args:
+            audio_params: Dict containing audio record settings.
+        """
+        super().__init__()
+        self.audio_params = audio_params
+        self.channels = self.audio_params['channel']
+        self.chunk = self.audio_params['chunk']
+        self.sample_rate = self.audio_params['sample_rate']
+        self.__input_device = None
+        self.audio = None
+        self.frames = []
+
+    @property
+    def name(self):
+        return self.__input_device["name"]
+
+    def __get_input_device(self):
+        """Checks for the audio capture device."""
+        if self.__input_device is None:
+            for i in range(self.audio.get_device_count()):
+                device_info = self.audio.get_device_info_by_index(i)
+                logging.debug('Device Information: {}'.format(device_info))
+                if self.audio_params['input_device'] in device_info['name']:
+                    self.__input_device = device_info
+                    break
+            else:
+                raise DeviceNotFound(
+                    'Audio Capture device {} not found.'.format(
+                        self.audio_params['input_device']))
+        return self.__input_device
+
+    def start(self, trim_beginning=0, trim_end=0):
+        """Starts audio recording on host machine.
+
+        Args:
+            trim_beginning: how many seconds to trim from the beginning
+            trim_end: how many seconds to trim from the end
+        """
+        self.audio = pyaudio.PyAudio()
+        self.__input_device = self.__get_input_device()
+        stream = self.audio.open(
+            format=pyaudio.paInt16,
+            channels=self.channels,
+            rate=self.sample_rate,
+            input=True,
+            frames_per_buffer=self.chunk,
+            input_device_index=self.__input_device['index'])
+        b_chunks = trim_beginning * (self.sample_rate // self.chunk)
+        e_chunks = trim_end * (self.sample_rate // self.chunk)
+        total_chunks = self.sample_rate // self.chunk * self.audio_params[
+            'duration']
+        for i in range(total_chunks):
+            try:
+                data = stream.read(self.chunk, exception_on_overflow=False)
+            except IOError as ex:
+                logging.error('Cannot record audio: {}'.format(ex))
+                return False
+            if b_chunks <= i < total_chunks - e_chunks:
+                self.frames.append(data)
+
+        stream.stop_stream()
+        stream.close()
+
+    def stop(self):
+        """Terminates the pulse audio instance.
+
+        Returns:
+            File name of the recorded audio file.
+        """
+        self.audio.terminate()
+        frames = b''.join(self.frames)
+        return self.write_record_file(self.audio_params, frames)
+
+
+class DeviceNotFound(Exception):
+    """Raises exception if audio capture device is not found."""
diff --git a/acts_tests/acts_contrib/test_utils/coex/audio_test_utils.py b/acts_tests/acts_contrib/test_utils/coex/audio_test_utils.py
new file mode 100644
index 0000000..39543a3
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/coex/audio_test_utils.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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 logging
+import numpy
+import os
+import scipy.io.wavfile as sciwav
+
+from acts_contrib.test_utils.coex.audio_capture_device import AudioCaptureBase
+from acts_contrib.test_utils.coex.audio_capture_device import CaptureAudioOverAdb
+from acts_contrib.test_utils.coex.audio_capture_device import CaptureAudioOverLocal
+from acts_contrib.test_utils.audio_analysis_lib import audio_analysis
+from acts_contrib.test_utils.audio_analysis_lib.check_quality import quality_analysis
+
+ANOMALY_DETECTION_BLOCK_SIZE = audio_analysis.ANOMALY_DETECTION_BLOCK_SIZE
+ANOMALY_GROUPING_TOLERANCE = audio_analysis.ANOMALY_GROUPING_TOLERANCE
+PATTERN_MATCHING_THRESHOLD = audio_analysis.PATTERN_MATCHING_THRESHOLD
+ANALYSIS_FILE_TEMPLATE = "audio_analysis_%s.txt"
+bits_per_sample = 32
+
+
+def get_audio_capture_device(ad, audio_params):
+    """Gets the device object of the audio capture device connected to server.
+
+    The audio capture device returned is specified by the audio_params
+    within user_params. audio_params must specify a "type" field, that
+    is either "AndroidDevice" or "Local"
+
+    Args:
+        ad: Android Device object.
+        audio_params: object containing variables to record audio.
+
+    Returns:
+        Object of the audio capture device.
+
+    Raises:
+        ValueError if audio_params['type'] is not "AndroidDevice" or
+            "Local".
+    """
+
+    if audio_params['type'] == 'AndroidDevice':
+        return CaptureAudioOverAdb(ad, audio_params)
+
+    elif audio_params['type'] == 'Local':
+        return CaptureAudioOverLocal(audio_params)
+
+    else:
+        raise ValueError('Unrecognized audio capture device '
+                         '%s' % audio_params['type'])
+
+
+class FileNotFound(Exception):
+    """Raises Exception if file is not present"""
+
+
+class AudioCaptureResult(AudioCaptureBase):
+    def __init__(self, path, audio_params=None):
+        """Initializes Audio Capture Result class.
+
+        Args:
+            path: Path of audio capture result.
+        """
+        super().__init__()
+        self.path = path
+        self.audio_params = audio_params
+        self.analysis_path = os.path.join(self.log_path,
+                                          ANALYSIS_FILE_TEMPLATE)
+        if self.audio_params:
+            self._trim_wave_file()
+
+    def THDN(self, win_size=None, step_size=None, q=1, freq=None):
+        """Calculate THD+N value for most recently recorded file.
+
+        Args:
+            win_size: analysis window size (must be less than length of
+                signal). Used with step size to analyze signal piece by
+                piece. If not specified, entire signal will be analyzed.
+            step_size: number of samples to move window per-analysis. If not
+                specified, entire signal will be analyzed.
+            q: quality factor for the notch filter used to remove fundamental
+                frequency from signal to isolate noise.
+            freq: the fundamental frequency to remove from the signal. If none,
+                the fundamental frequency will be determined using FFT.
+        Returns:
+            channel_results (list): THD+N value for each channel's signal.
+                List index corresponds to channel index.
+        """
+        if not (win_size and step_size):
+            return audio_analysis.get_file_THDN(filename=self.path,
+                                                q=q,
+                                                freq=freq)
+        else:
+            return audio_analysis.get_file_max_THDN(filename=self.path,
+                                                    step_size=step_size,
+                                                    window_size=win_size,
+                                                    q=q,
+                                                    freq=freq)
+
+    def detect_anomalies(self,
+                         freq=None,
+                         block_size=ANOMALY_DETECTION_BLOCK_SIZE,
+                         threshold=PATTERN_MATCHING_THRESHOLD,
+                         tolerance=ANOMALY_GROUPING_TOLERANCE):
+        """Detect anomalies in most recently recorded file.
+
+        An anomaly is defined as a sample in a recorded sine wave that differs
+        by at least the value defined by the threshold parameter from a golden
+        generated sine wave of the same amplitude, sample rate, and frequency.
+
+        Args:
+            freq (int|float): fundamental frequency of the signal. All other
+                frequencies are noise. If None, will be calculated with FFT.
+            block_size (int): the number of samples to analyze at a time in the
+                anomaly detection algorithm.
+            threshold (float): the threshold of the correlation index to
+                determine if two sample values match.
+            tolerance (float): the sample tolerance for anomaly time values
+                to be grouped as the same anomaly
+        Returns:
+            channel_results (list): anomaly durations for each channel's signal.
+                List index corresponds to channel index.
+        """
+        return audio_analysis.get_file_anomaly_durations(filename=self.path,
+                                                         freq=freq,
+                                                         block_size=block_size,
+                                                         threshold=threshold,
+                                                         tolerance=tolerance)
+
+    @property
+    def analysis_fileno(self):
+        """Returns the file number to dump audio analysis results."""
+        counter = 0
+        while os.path.exists(self.analysis_path % counter):
+            counter += 1
+        return counter
+
+    def audio_quality_analysis(self):
+        """Measures audio quality based on the audio file given as input.
+
+        Returns:
+            analysis_path on success.
+        """
+        analysis_path = self.analysis_path % self.analysis_fileno
+        if not os.path.exists(self.path):
+            raise FileNotFound("Recorded file not found")
+        try:
+            quality_analysis(filename=self.path,
+                             output_file=analysis_path,
+                             bit_width=bits_per_sample,
+                             rate=self.audio_params["sample_rate"],
+                             channel=self.audio_params["channel"],
+                             spectral_only=False)
+        except Exception as err:
+            logging.exception("Failed to analyze raw audio: %s" % err)
+        return analysis_path
+
+    def _trim_wave_file(self):
+        """Trim wave files.
+
+        """
+        original_record_file_name = 'original_' + os.path.basename(self.path)
+        original_record_file_path = os.path.join(os.path.dirname(self.path),
+                                                 original_record_file_name)
+        os.rename(self.path, original_record_file_path)
+        fs, data = sciwav.read(original_record_file_path)
+        trim_start = self.audio_params['trim_start']
+        trim_end = self.audio_params['trim_end']
+        trim = numpy.array([[trim_start, trim_end]])
+        trim = trim * fs
+        new_wave_file_list = []
+        for elem in trim:
+            # To check start and end doesn't exceed raw data dimension
+            start_read = min(elem[0], data.shape[0] - 1)
+            end_read = min(elem[1], data.shape[0] - 1)
+            new_wave_file_list.extend(data[start_read:end_read])
+        new_wave_file = numpy.array(new_wave_file_list)
+
+        sciwav.write(self.path, fs, new_wave_file)
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
new file mode 100644
index 0000000..62e12af
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/coex/coex_test_utils.py
@@ -0,0 +1,1331 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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 json
+import logging
+import math
+import os
+import re
+import time
+
+from acts import asserts
+from acts.controllers.ap_lib import hostapd_config
+from acts.controllers.ap_lib import hostapd_constants
+from acts.controllers.ap_lib import hostapd_security
+from acts.controllers.utils_lib.ssh import connection
+from acts.controllers.utils_lib.ssh import settings
+from acts.controllers.iperf_server import IPerfResult
+from acts.libs.proc import job
+from acts_contrib.test_utils.bt.bt_constants import (
+    bluetooth_profile_connection_state_changed)
+from acts_contrib.test_utils.bt.bt_constants import bt_default_timeout
+from acts_contrib.test_utils.bt.bt_constants import bt_profile_constants
+from acts_contrib.test_utils.bt.bt_constants import bt_profile_states
+from acts_contrib.test_utils.bt.bt_constants import bt_scan_mode_types
+from acts_contrib.test_utils.bt.bt_gatt_utils import GattTestUtilsError
+from acts_contrib.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
+from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import enable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import is_a2dp_src_device_connected
+from acts_contrib.test_utils.bt.bt_test_utils import is_a2dp_snk_device_connected
+from acts_contrib.test_utils.bt.bt_test_utils import is_hfp_client_device_connected
+from acts_contrib.test_utils.bt.bt_test_utils import is_map_mce_device_connected
+from acts_contrib.test_utils.bt.bt_test_utils import is_map_mse_device_connected
+from acts_contrib.test_utils.bt.bt_test_utils import set_bt_scan_mode
+from acts_contrib.test_utils.car.car_telecom_utils import wait_for_active
+from acts_contrib.test_utils.car.car_telecom_utils import wait_for_dialing
+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_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.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 bokeh.layouts import column
+from bokeh.models import tools as bokeh_tools
+from bokeh.plotting import figure, output_file, save
+
+THROUGHPUT_THRESHOLD = 100
+AP_START_TIME = 10
+DISCOVERY_TIME = 10
+BLUETOOTH_WAIT_TIME = 2
+AVRCP_WAIT_TIME = 3
+
+
+def avrcp_actions(pri_ad, bt_device):
+    """Performs avrcp controls like volume up, volume down, skip next and
+    skip previous.
+
+    Args:
+        pri_ad: Android device.
+        bt_device: bt device instance.
+
+    Returns:
+        True if successful, otherwise False.
+    """
+    current_volume = pri_ad.droid.getMediaVolume()
+    for _ in range(5):
+        bt_device.volume_up()
+        time.sleep(AVRCP_WAIT_TIME)
+    if current_volume == pri_ad.droid.getMediaVolume():
+        pri_ad.log.error("Increase volume failed")
+        return False
+    time.sleep(AVRCP_WAIT_TIME)
+    current_volume = pri_ad.droid.getMediaVolume()
+    for _ in range(5):
+        bt_device.volume_down()
+        time.sleep(AVRCP_WAIT_TIME)
+    if current_volume == pri_ad.droid.getMediaVolume():
+        pri_ad.log.error("Decrease volume failed")
+        return False
+
+    #TODO: (sairamganesh) validate next and previous calls.
+    bt_device.next()
+    time.sleep(AVRCP_WAIT_TIME)
+    bt_device.previous()
+    time.sleep(AVRCP_WAIT_TIME)
+    return True
+
+
+def connect_ble(pri_ad, sec_ad):
+    """Connect BLE device from DUT.
+
+    Args:
+        pri_ad: An android device object.
+        sec_ad: An android device object.
+
+    Returns:
+        True if successful, otherwise False.
+    """
+    adv_instances = []
+    gatt_server_list = []
+    bluetooth_gatt_list = []
+    pri_ad.droid.bluetoothEnableBLE()
+    sec_ad.droid.bluetoothEnableBLE()
+    gatt_server_cb = sec_ad.droid.gattServerCreateGattServerCallback()
+    gatt_server = sec_ad.droid.gattServerOpenGattServer(gatt_server_cb)
+    gatt_server_list.append(gatt_server)
+    try:
+        bluetooth_gatt, gatt_callback, adv_callback = (
+            orchestrate_gatt_connection(pri_ad, sec_ad))
+        bluetooth_gatt_list.append(bluetooth_gatt)
+    except GattTestUtilsError as err:
+        pri_ad.log.error(err)
+        return False
+    adv_instances.append(adv_callback)
+    connected_devices = sec_ad.droid.gattServerGetConnectedDevices(gatt_server)
+    pri_ad.log.debug("Connected device = {}".format(connected_devices))
+    return True
+
+
+def collect_bluetooth_manager_dumpsys_logs(pri_ad, test_name):
+    """Collect "adb shell dumpsys bluetooth_manager" logs.
+
+    Args:
+        pri_ad: An android device.
+        test_name: Current test case name.
+
+    Returns:
+        Dumpsys file path.
+    """
+    dump_counter = 0
+    dumpsys_path = os.path.join(pri_ad.log_path, test_name, "BluetoothDumpsys")
+    os.makedirs(dumpsys_path, exist_ok=True)
+    while os.path.exists(
+            os.path.join(dumpsys_path,
+                         "bluetooth_dumpsys_%s.txt" % dump_counter)):
+        dump_counter += 1
+    out_file = "bluetooth_dumpsys_%s.txt" % dump_counter
+    cmd = "adb -s {} shell dumpsys bluetooth_manager > {}/{}".format(
+        pri_ad.serial, dumpsys_path, out_file)
+    exe_cmd(cmd)
+    file_path = os.path.join(dumpsys_path, out_file)
+    return file_path
+
+
+def configure_and_start_ap(ap, network):
+    """Configure hostapd parameters and starts access point.
+
+    Args:
+        ap: An access point object.
+        network: A dictionary with wifi network details.
+    """
+    hostapd_sec = None
+    if network["security"] == "wpa2":
+        hostapd_sec = hostapd_security.Security(
+            security_mode=network["security"], password=network["password"])
+
+    config = hostapd_config.HostapdConfig(
+        n_capabilities=[hostapd_constants.N_CAPABILITY_HT40_MINUS],
+        mode=hostapd_constants.MODE_11N_PURE,
+        channel=network["channel"],
+        ssid=network["SSID"],
+        security=hostapd_sec)
+    ap.start_ap(config)
+    time.sleep(AP_START_TIME)
+
+
+def connect_dev_to_headset(pri_droid, dev_to_connect, profiles_set):
+    """Connects primary android device to headset.
+
+    Args:
+        pri_droid: Android device initiating connection.
+        dev_to_connect: Third party headset mac address.
+        profiles_set: Profiles to be connected.
+
+    Returns:
+        True if Pass
+        False if Fail
+    """
+    supported_profiles = bt_profile_constants.values()
+    for profile in profiles_set:
+        if profile not in supported_profiles:
+            pri_droid.log.info("Profile {} is not supported list {}".format(
+                profile, supported_profiles))
+            return False
+
+    paired = False
+    for paired_device in pri_droid.droid.bluetoothGetBondedDevices():
+        if paired_device['address'] == dev_to_connect:
+            paired = True
+            break
+
+    if not paired:
+        pri_droid.log.info("{} not paired to {}".format(pri_droid.serial,
+                                                        dev_to_connect))
+        return False
+
+    end_time = time.time() + 10
+    profile_connected = set()
+    sec_addr = dev_to_connect
+    pri_droid.log.info("Profiles to be connected {}".format(profiles_set))
+
+    while (time.time() < end_time and
+           not profile_connected.issuperset(profiles_set)):
+        if (bt_profile_constants['headset_client'] not in profile_connected and
+                bt_profile_constants['headset_client'] in profiles_set):
+            if is_hfp_client_device_connected(pri_droid, sec_addr):
+                profile_connected.add(bt_profile_constants['headset_client'])
+        if (bt_profile_constants['headset'] not in profile_connected and
+                bt_profile_constants['headset'] in profiles_set):
+            profile_connected.add(bt_profile_constants['headset'])
+        if (bt_profile_constants['a2dp'] not in profile_connected and
+                bt_profile_constants['a2dp'] in profiles_set):
+            if is_a2dp_src_device_connected(pri_droid, sec_addr):
+                profile_connected.add(bt_profile_constants['a2dp'])
+        if (bt_profile_constants['a2dp_sink'] not in profile_connected and
+                bt_profile_constants['a2dp_sink'] in profiles_set):
+            if is_a2dp_snk_device_connected(pri_droid, sec_addr):
+                profile_connected.add(bt_profile_constants['a2dp_sink'])
+        if (bt_profile_constants['map_mce'] not in profile_connected and
+                bt_profile_constants['map_mce'] in profiles_set):
+            if is_map_mce_device_connected(pri_droid, sec_addr):
+                profile_connected.add(bt_profile_constants['map_mce'])
+        if (bt_profile_constants['map'] not in profile_connected and
+                bt_profile_constants['map'] in profiles_set):
+            if is_map_mse_device_connected(pri_droid, sec_addr):
+                profile_connected.add(bt_profile_constants['map'])
+        time.sleep(0.1)
+
+    while not profile_connected.issuperset(profiles_set):
+        try:
+            time.sleep(10)
+            profile_event = pri_droid.ed.pop_event(
+                bluetooth_profile_connection_state_changed,
+                bt_default_timeout + 10)
+            pri_droid.log.info("Got event {}".format(profile_event))
+        except Exception:
+            pri_droid.log.error("Did not get {} profiles left {}".format(
+                bluetooth_profile_connection_state_changed, profile_connected))
+            return False
+        profile = profile_event['data']['profile']
+        state = profile_event['data']['state']
+        device_addr = profile_event['data']['addr']
+        if state == bt_profile_states['connected'] and (
+                device_addr == dev_to_connect):
+            profile_connected.add(profile)
+        pri_droid.log.info(
+            "Profiles connected until now {}".format(profile_connected))
+    return True
+
+
+def device_discoverable(pri_ad, sec_ad):
+    """Verifies whether the device is discoverable or not.
+
+    Args:
+        pri_ad: An primary android device object.
+        sec_ad: An secondary android device object.
+
+    Returns:
+        True if the device found, False otherwise.
+    """
+    pri_ad.droid.bluetoothMakeDiscoverable()
+    scan_mode = pri_ad.droid.bluetoothGetScanMode()
+    if scan_mode == bt_scan_mode_types['connectable_discoverable']:
+        pri_ad.log.info("Primary device scan mode is "
+                        "SCAN_MODE_CONNECTABLE_DISCOVERABLE.")
+    else:
+        pri_ad.log.info("Primary device scan mode is not "
+                        "SCAN_MODE_CONNECTABLE_DISCOVERABLE.")
+        return False
+    if sec_ad.droid.bluetoothStartDiscovery():
+        time.sleep(DISCOVERY_TIME)
+        droid_name = pri_ad.droid.bluetoothGetLocalName()
+        droid_address = pri_ad.droid.bluetoothGetLocalAddress()
+        get_discovered_devices = sec_ad.droid.bluetoothGetDiscoveredDevices()
+        find_flag = False
+
+        if get_discovered_devices:
+            for device in get_discovered_devices:
+                if 'name' in device and device['name'] == droid_name or (
+                        'address' in device and
+                        device["address"] == droid_address):
+                    pri_ad.log.info("Primary device is in the discovery "
+                                    "list of secondary device.")
+                    find_flag = True
+                    break
+        else:
+            pri_ad.log.info("Secondary device get all the discovered devices "
+                            "list is empty")
+            return False
+    else:
+        pri_ad.log.info("Secondary device start discovery process error.")
+        return False
+    if not find_flag:
+        return False
+    return True
+
+
+def device_discoverability(required_devices):
+    """Wrapper function to keep required_devices in discoverable mode.
+
+    Args:
+        required_devices: List of devices to be discovered.
+
+    Returns:
+        discovered_devices: List of BD_ADDR of devices in discoverable mode.
+    """
+    discovered_devices = []
+    if "AndroidDevice" in required_devices:
+        discovered_devices.extend(
+            android_device_discoverability(required_devices["AndroidDevice"]))
+    if "RelayDevice" in required_devices:
+        discovered_devices.extend(
+            relay_device_discoverability(required_devices["RelayDevice"]))
+    return discovered_devices
+
+
+def android_device_discoverability(droid_dev):
+    """To keep android devices in discoverable mode.
+
+    Args:
+        droid_dev: Android device object.
+
+    Returns:
+        device_list: List of device discovered.
+    """
+    device_list = []
+    for device in range(len(droid_dev)):
+        inquiry_device = droid_dev[device]
+        if enable_bluetooth(inquiry_device.droid, inquiry_device.ed):
+            if set_bt_scan_mode(inquiry_device,
+                                bt_scan_mode_types['connectable_discoverable']):
+                device_list.append(
+                    inquiry_device.droid.bluetoothGetLocalAddress())
+            else:
+                droid_dev.log.error(
+                    "Device {} scan mode is not in"
+                    "SCAN_MODE_CONNECTABLE_DISCOVERABLE.".format(
+                        inquiry_device.droid.bluetoothGetLocalAddress()))
+    return device_list
+
+
+def relay_device_discoverability(relay_devices):
+    """To keep relay controlled devices in discoverable mode.
+
+    Args:
+        relay_devices: Relay object.
+
+    Returns:
+        mac_address: Mac address of relay controlled device.
+    """
+    relay_device = relay_devices[0]
+    relay_device.power_on()
+    relay_device.enter_pairing_mode()
+    return relay_device.mac_address
+
+
+def disconnect_headset_from_dev(pri_ad, sec_ad, profiles_list):
+    """Disconnect primary from secondary on a specific set of profiles
+
+    Args:
+        pri_ad: Primary android_device initiating disconnection
+        sec_ad: Secondary android droid (sl4a interface to keep the
+          method signature the same connect_pri_to_sec above)
+        profiles_list: List of profiles we want to disconnect from
+
+    Returns:
+        True on Success
+        False on Failure
+    """
+    supported_profiles = bt_profile_constants.values()
+    for profile in profiles_list:
+        if profile not in supported_profiles:
+            pri_ad.log.info("Profile {} is not in supported list {}".format(
+                profile, supported_profiles))
+            return False
+
+    pri_ad.log.info(pri_ad.droid.bluetoothGetBondedDevices())
+
+    try:
+        pri_ad.droid.bluetoothDisconnectConnectedProfile(sec_ad, profiles_list)
+    except Exception as err:
+        pri_ad.log.error(
+            "Exception while trying to disconnect profile(s) {}: {}".format(
+                profiles_list, err))
+        return False
+
+    profile_disconnected = set()
+    pri_ad.log.info("Disconnecting from profiles: {}".format(profiles_list))
+
+    while not profile_disconnected.issuperset(profiles_list):
+        try:
+            profile_event = pri_ad.ed.pop_event(
+                bluetooth_profile_connection_state_changed, bt_default_timeout)
+            pri_ad.log.info("Got event {}".format(profile_event))
+        except Exception:
+            pri_ad.log.warning("Did not disconnect from Profiles")
+            return True
+
+        profile = profile_event['data']['profile']
+        state = profile_event['data']['state']
+        device_addr = profile_event['data']['addr']
+
+        if state == bt_profile_states['disconnected'] and (
+                device_addr == sec_ad):
+            profile_disconnected.add(profile)
+        pri_ad.log.info(
+            "Profiles disconnected so far {}".format(profile_disconnected))
+
+    return True
+
+
+def initiate_disconnect_from_hf(audio_receiver, pri_ad, sec_ad, duration):
+    """Initiates call and disconnect call on primary device.
+
+    Steps:
+    1. Initiate call from HF.
+    2. Wait for dialing state at DUT and wait for ringing at secondary device.
+    3. Accepts call from secondary device.
+    4. Wait for call active state at primary and secondary device.
+    5. Sleeps until given duration.
+    6. Disconnect call from primary device.
+    7. Wait for call is not present state.
+
+    Args:
+        audio_receiver: An relay device object.
+        pri_ad: An android device to disconnect call.
+        sec_ad: An android device accepting call.
+        duration: Duration of call in seconds.
+
+    Returns:
+        True if successful, False otherwise.
+    """
+    audio_receiver.press_initiate_call()
+    time.sleep(2)
+    flag = True
+    flag &= wait_for_dialing(logging, pri_ad)
+    flag &= wait_for_ringing(logging, sec_ad)
+    if not flag:
+        pri_ad.log.error("Outgoing call did not get established")
+        return False
+
+    if not wait_and_answer_call(logging, sec_ad):
+        pri_ad.log.error("Failed to answer call in second device.")
+        return False
+    if not wait_for_active(logging, pri_ad):
+        pri_ad.log.error("AG not in Active state.")
+        return False
+    if not wait_for_active(logging, sec_ad):
+        pri_ad.log.error("RE not in Active state.")
+        return False
+    time.sleep(duration)
+    if not hangup_call(logging, pri_ad):
+        pri_ad.log.error("Failed to hangup call.")
+        return False
+    flag = True
+    flag &= wait_for_not_in_call(logging, pri_ad)
+    flag &= wait_for_not_in_call(logging, sec_ad)
+    return flag
+
+
+def initiate_disconnect_call_dut(pri_ad, sec_ad, duration, callee_number):
+    """Initiates call and disconnect call on primary device.
+
+    Steps:
+    1. Initiate call from DUT.
+    2. Wait for dialing state at DUT and wait for ringing at secondary device.
+    3. Accepts call from secondary device.
+    4. Wait for call active state at primary and secondary device.
+    5. Sleeps until given duration.
+    6. Disconnect call from primary device.
+    7. Wait for call is not present state.
+
+    Args:
+        pri_ad: An android device to disconnect call.
+        sec_ad: An android device accepting call.
+        duration: Duration of call in seconds.
+        callee_number: Secondary device's phone number.
+
+    Returns:
+        True if successful, False otherwise.
+    """
+    if not initiate_call(logging, pri_ad, callee_number):
+        pri_ad.log.error("Failed to initiate call")
+        return False
+    time.sleep(2)
+
+    flag = True
+    flag &= wait_for_dialing(logging, pri_ad)
+    flag &= wait_for_ringing(logging, sec_ad)
+    if not flag:
+        pri_ad.log.error("Outgoing call did not get established")
+        return False
+
+    if not wait_and_answer_call(logging, sec_ad):
+        pri_ad.log.error("Failed to answer call in second device.")
+        return False
+    # Wait for AG, RE to go into an Active state.
+    if not wait_for_active(logging, pri_ad):
+        pri_ad.log.error("AG not in Active state.")
+        return False
+    if not wait_for_active(logging, sec_ad):
+        pri_ad.log.error("RE not in Active state.")
+        return False
+    time.sleep(duration)
+    if not hangup_call(logging, pri_ad):
+        pri_ad.log.error("Failed to hangup call.")
+        return False
+    flag = True
+    flag &= wait_for_not_in_call(logging, pri_ad)
+    flag &= wait_for_not_in_call(logging, sec_ad)
+
+    return flag
+
+
+def check_wifi_status(pri_ad, network, ssh_config=None):
+    """Function to check existence of wifi connection.
+
+    Args:
+        pri_ad: An android device.
+        network: network ssid.
+        ssh_config: ssh config for iperf client.
+    """
+    time.sleep(5)
+    proc = job.run("pgrep -f 'iperf3 -c'")
+    pid_list = proc.stdout.split()
+
+    while True:
+        iperf_proc = job.run(["pgrep", "-f", "iperf3"])
+        process_list = iperf_proc.stdout.split()
+        if not wifi_connection_check(pri_ad, network["SSID"]):
+            pri_ad.adb.shell("killall iperf3")
+            if ssh_config:
+                time.sleep(5)
+                ssh_settings = settings.from_config(ssh_config)
+                ssh_session = connection.SshConnection(ssh_settings)
+                result = ssh_session.run("pgrep iperf3")
+                res = result.stdout.split("\n")
+                for pid in res:
+                    try:
+                        ssh_session.run("kill -9 %s" % pid)
+                    except Exception as e:
+                        logging.warning("No such process: %s" % e)
+                for pid in pid_list[:-1]:
+                    job.run(["kill", " -9", " %s" % pid], ignore_status=True)
+            else:
+                job.run(["killall", " iperf3"], ignore_status=True)
+            break
+        elif pid_list[0] not in process_list:
+            break
+
+
+def iperf_result(log, protocol, result):
+    """Accepts the iperf result in json format and parse the output to
+    get throughput value.
+
+    Args:
+        log: Logger object.
+        protocol : TCP or UDP protocol.
+        result: iperf result's filepath.
+
+    Returns:
+        rx_rate: Data received from client.
+    """
+    if os.path.exists(result):
+        ip_cl = IPerfResult(result)
+
+        if protocol == "udp":
+            rx_rate = (math.fsum(ip_cl.instantaneous_rates) /
+                       len(ip_cl.instantaneous_rates))*8
+        else:
+            rx_rate = ip_cl.avg_receive_rate * 8
+        return rx_rate
+    else:
+        log.error("IPerf file not found")
+        return False
+
+
+def is_a2dp_connected(pri_ad, headset_mac_address):
+    """Convenience Function to see if the 2 devices are connected on A2DP.
+
+    Args:
+        pri_ad : An android device.
+        headset_mac_address : Mac address of headset.
+
+    Returns:
+        True:If A2DP connection exists, False otherwise.
+    """
+    devices = pri_ad.droid.bluetoothA2dpGetConnectedDevices()
+    for device in devices:
+        pri_ad.log.debug("A2dp Connected device {}".format(device["name"]))
+        if device["address"] == headset_mac_address:
+            return True
+    return False
+
+
+def media_stream_check(pri_ad, duration, headset_mac_address):
+    """Checks whether A2DP connecion is active or not for given duration of
+    time.
+
+    Args:
+        pri_ad : An android device.
+        duration : No of seconds to check if a2dp streaming is alive.
+        headset_mac_address : Headset mac address.
+
+    Returns:
+        True: If A2dp connection is active for entire duration.
+        False: If A2dp connection is not active.
+    """
+    while time.time() < duration:
+        if not is_a2dp_connected(pri_ad, headset_mac_address):
+            pri_ad.log.error('A2dp connection not active at %s', pri_ad.serial)
+            return False
+        time.sleep(1)
+    return True
+
+
+def multithread_func(log, tasks):
+    """Multi-thread function wrapper.
+
+    Args:
+        log: log object.
+        tasks: tasks to be executed in parallel.
+
+    Returns:
+       List of results of tasks
+    """
+    results = run_multithread_func(log, tasks)
+    for res in results:
+        if not res:
+            return False
+    return True
+
+
+def music_play_and_check(pri_ad, headset_mac_address, music_to_play, duration):
+    """Starts playing media and checks if media plays for n seconds.
+
+    Steps:
+    1. Starts media player on android device.
+    2. Checks if music streaming is ongoing for n seconds.
+    3. Stops media player.
+    4. Collect dumpsys logs.
+
+    Args:
+        pri_ad: An android device.
+        headset_mac_address: Mac address of third party headset.
+        music_to_play: Indicates the music file to play.
+        duration: Time in secs to indicate music time to play.
+
+    Returns:
+        True if successful, False otherwise.
+    """
+    pri_ad.droid.setMediaVolume(pri_ad.droid.getMaxMediaVolume() - 1)
+    pri_ad.log.info("current volume = {}".format(pri_ad.droid.getMediaVolume()))
+    pri_ad.log.debug("In music play and check")
+    if not start_media_play(pri_ad, music_to_play):
+        pri_ad.log.error("Start media play failed.")
+        return False
+    stream_time = time.time() + duration
+    if not media_stream_check(pri_ad, stream_time, headset_mac_address):
+        pri_ad.log.error("A2DP Connection check failed.")
+        pri_ad.droid.mediaPlayStop()
+        return False
+    pri_ad.droid.mediaPlayStop()
+    return True
+
+
+def music_play_and_check_via_app(pri_ad, headset_mac_address, duration=5):
+    """Starts google music player and check for A2DP connection.
+
+    Steps:
+    1. Starts Google music player on android device.
+    2. Checks for A2DP connection.
+
+    Args:
+        pri_ad: An android device.
+        headset_mac_address: Mac address of third party headset.
+        duration: Total time of music streaming.
+
+    Returns:
+        True if successful, False otherwise.
+    """
+    pri_ad.adb.shell("am start com.google.android.music")
+    time.sleep(3)
+    pri_ad.adb.shell("input keyevent 85")
+    stream_time = time.time() + duration
+    try:
+        if not media_stream_check(pri_ad, stream_time, headset_mac_address):
+            pri_ad.log.error("A2dp connection not active at %s", pri_ad.serial)
+            return False
+    finally:
+        pri_ad.adb.shell("am force-stop com.google.android.music")
+        return True
+
+
+def pair_dev_to_headset(pri_ad, dev_to_pair):
+    """Pairs primary android device with headset.
+
+    Args:
+        pri_ad: Android device initiating connection
+        dev_to_pair: Third party headset mac address.
+
+    Returns:
+        True if Pass
+        False if Fail
+    """
+    bonded_devices = pri_ad.droid.bluetoothGetBondedDevices()
+    for d in bonded_devices:
+        if d['address'] == dev_to_pair:
+            pri_ad.log.info("Successfully bonded to device {}".format(
+                dev_to_pair))
+            return True
+    pri_ad.droid.bluetoothStartDiscovery()
+    time.sleep(10)  # Wait until device gets discovered
+    pri_ad.droid.bluetoothCancelDiscovery()
+    pri_ad.log.debug("Discovered bluetooth devices: {}".format(
+        pri_ad.droid.bluetoothGetDiscoveredDevices()))
+    for device in pri_ad.droid.bluetoothGetDiscoveredDevices():
+        if device['address'] == dev_to_pair:
+
+            result = pri_ad.droid.bluetoothDiscoverAndBond(dev_to_pair)
+            pri_ad.log.info(result)
+            end_time = time.time() + bt_default_timeout
+            pri_ad.log.info("Verifying if device bonded with {}".format(
+                dev_to_pair))
+            time.sleep(5)  # Wait time until device gets paired.
+            while time.time() < end_time:
+                bonded_devices = pri_ad.droid.bluetoothGetBondedDevices()
+                for d in bonded_devices:
+                    if d['address'] == dev_to_pair:
+                        pri_ad.log.info(
+                            "Successfully bonded to device {}".format(
+                                dev_to_pair))
+                        return True
+    pri_ad.log.error("Failed to bond with {}".format(dev_to_pair))
+    return False
+
+
+def pair_and_connect_headset(pri_ad, headset_mac_address, profile_to_connect, retry=5):
+    """Pair and connect android device with third party headset.
+
+    Args:
+        pri_ad: An android device.
+        headset_mac_address: Mac address of third party headset.
+        profile_to_connect: Profile to be connected with headset.
+        retry: Number of times pair and connection should happen.
+
+    Returns:
+        True if pair and connect to headset successful, or raises exception
+        on failure.
+    """
+
+    paired = False
+    for i in range(1, retry):
+        if pair_dev_to_headset(pri_ad, headset_mac_address):
+            paired = True
+            break
+        else:
+            pri_ad.log.error("Attempt {} out of {}, Failed to pair, "
+                             "Retrying.".format(i, retry))
+
+    if paired:
+        for i in range(1, retry):
+            if connect_dev_to_headset(pri_ad, headset_mac_address,
+                                      profile_to_connect):
+                return True
+            else:
+                pri_ad.log.error("Attempt {} out of {}, Failed to connect, "
+                                 "Retrying.".format(i, retry))
+    else:
+        asserts.fail("Failed to pair and connect with {}".format(
+            headset_mac_address))
+
+
+def perform_classic_discovery(pri_ad, duration, file_name, dev_list=None):
+    """Convenience function to start and stop device discovery.
+
+    Args:
+        pri_ad: An android device.
+        duration: iperf duration of the test.
+        file_name: Json file to which result is dumped
+        dev_list: List of devices to be discoverable mode.
+
+    Returns:
+        True start and stop discovery is successful, False otherwise.
+    """
+    if dev_list:
+        devices_required = device_discoverability(dev_list)
+    else:
+        devices_required = None
+    iteration = 0
+    result = {}
+    result["discovered_devices"] = {}
+    discover_result = []
+    start_time = time.time()
+    while time.time() < start_time + duration:
+        if not pri_ad.droid.bluetoothStartDiscovery():
+            pri_ad.log.error("Failed to start discovery")
+            return False
+        time.sleep(DISCOVERY_TIME)
+        if not pri_ad.droid.bluetoothCancelDiscovery():
+            pri_ad.log.error("Failed to cancel discovery")
+            return False
+        pri_ad.log.info("Discovered device list {}".format(
+            pri_ad.droid.bluetoothGetDiscoveredDevices()))
+        if devices_required is not None:
+            result["discovered_devices"][iteration] = []
+            devices_name = {
+                element.get('name', element['address'])
+                for element in pri_ad.droid.bluetoothGetDiscoveredDevices()
+                if element["address"] in devices_required
+            }
+            result["discovered_devices"][iteration] = list(devices_name)
+            discover_result.extend([len(devices_name) == len(devices_required)])
+            iteration += 1
+            with open(file_name, 'a') as results_file:
+                json.dump(result, results_file, indent=4)
+            if False in discover_result:
+                return False
+        else:
+            pri_ad.log.warning("No devices are kept in discoverable mode")
+    return True
+
+
+def connect_wlan_profile(pri_ad, network):
+    """Disconnect and Connect to AP.
+
+    Args:
+        pri_ad: An android device.
+        network: Network to which AP to be connected.
+
+    Returns:
+        True if successful, False otherwise.
+    """
+    reset_wifi(pri_ad)
+    wifi_toggle_state(pri_ad, False)
+    wifi_test_device_init(pri_ad)
+    wifi_connect(pri_ad, network)
+    if not wifi_connection_check(pri_ad, network["SSID"]):
+        pri_ad.log.error("Wifi connection does not exist.")
+        return False
+    return True
+
+
+def toggle_bluetooth(pri_ad, duration):
+    """Toggles bluetooth on/off for N iterations.
+
+    Args:
+        pri_ad: An android device object.
+        duration: Iperf duration of the test.
+
+    Returns:
+        True if successful, False otherwise.
+    """
+    start_time = time.time()
+    while time.time() < start_time + duration:
+        if not enable_bluetooth(pri_ad.droid, pri_ad.ed):
+            pri_ad.log.error("Failed to enable bluetooth")
+            return False
+        time.sleep(BLUETOOTH_WAIT_TIME)
+        if not disable_bluetooth(pri_ad.droid):
+            pri_ad.log.error("Failed to turn off bluetooth")
+            return False
+        time.sleep(BLUETOOTH_WAIT_TIME)
+    return True
+
+
+def toggle_screen_state(pri_ad, duration):
+    """Toggles the screen state to on or off..
+
+    Args:
+        pri_ad: Android device.
+        duration: Iperf duration of the test.
+
+    Returns:
+        True if successful, False otherwise.
+    """
+    start_time = time.time()
+    while time.time() < start_time + duration:
+        if not pri_ad.ensure_screen_on():
+            pri_ad.log.error("User window cannot come up")
+            return False
+        if not pri_ad.go_to_sleep():
+            pri_ad.log.info("Screen off")
+    return True
+
+
+def setup_tel_config(pri_ad, sec_ad, sim_conf_file):
+    """Sets tel properties for primary device and secondary devices
+
+    Args:
+        pri_ad: An android device object.
+        sec_ad: An android device object.
+        sim_conf_file: Sim card map.
+
+    Returns:
+        pri_ad_num: Phone number of primary device.
+        sec_ad_num: Phone number of secondary device.
+    """
+    setup_droid_properties(logging, pri_ad, sim_conf_file)
+    pri_ad_num = get_phone_number(logging, pri_ad)
+    setup_droid_properties(logging, sec_ad, sim_conf_file)
+    sec_ad_num = get_phone_number(logging, sec_ad)
+    return pri_ad_num, sec_ad_num
+
+
+def start_fping(pri_ad, duration, fping_params):
+    """Starts fping to ping for DUT's ip address.
+
+    Steps:
+    1. Run fping command to check DUT's IP is alive or not.
+
+    Args:
+        pri_ad: An android device object.
+        duration: Duration of fping in seconds.
+        fping_params: List of parameters for fping to run.
+
+    Returns:
+        True if successful, False otherwise.
+    """
+    counter = 0
+    fping_path = ''.join((pri_ad.log_path, "/Fping"))
+    os.makedirs(fping_path, exist_ok=True)
+    while os.path.isfile(fping_path + "/fping_%s.txt" % counter):
+        counter += 1
+    out_file_name = "{}".format("fping_%s.txt" % counter)
+
+    full_out_path = os.path.join(fping_path, out_file_name)
+    cmd = "fping {} -D -c {}".format(get_phone_ip(pri_ad), duration)
+    if fping_params["ssh_config"]:
+        ssh_settings = settings.from_config(fping_params["ssh_config"])
+        ssh_session = connection.SshConnection(ssh_settings)
+        try:
+            with open(full_out_path, 'w') as outfile:
+                job_result = ssh_session.run(cmd)
+                outfile.write(job_result.stdout)
+                outfile.write("\n")
+                outfile.writelines(job_result.stderr)
+        except Exception as err:
+            pri_ad.log.error("Fping run has been failed. = {}".format(err))
+            return False
+    else:
+        cmd = cmd.split()
+        with open(full_out_path, "w") as f:
+            job.run(cmd)
+    result = parse_fping_results(fping_params["fping_drop_tolerance"],
+                                 full_out_path)
+    return bool(result)
+
+
+def parse_fping_results(failure_rate, full_out_path):
+    """Calculates fping results.
+
+    Steps:
+    1. Read the file and calculate the results.
+
+    Args:
+        failure_rate: Fping packet drop tolerance value.
+        full_out_path: path where the fping results has been stored.
+
+    Returns:
+        loss_percent: loss percentage of fping packet.
+    """
+    try:
+        result_file = open(full_out_path, "r")
+        lines = result_file.readlines()
+        res_line = lines[-1]
+        # Ex: res_line = "192.168.186.224 : xmt/rcv/%loss = 10/10/0%,
+        # min/avg/max = 36.7/251/1272"
+        loss_percent = re.search("[0-9]+%", res_line)
+        if int(loss_percent.group().strip("%")) > failure_rate:
+            logging.error("Packet drop observed")
+            return False
+        return loss_percent.group()
+    except Exception as e:
+        logging.error("Error in parsing fping results : %s" %(e))
+        return False
+
+
+def start_media_play(pri_ad, music_file_to_play):
+    """Starts media player on device.
+
+    Args:
+        pri_ad : An android device.
+        music_file_to_play : An audio file to play.
+
+    Returns:
+        True:If media player start music, False otherwise.
+    """
+    if not pri_ad.droid.mediaPlayOpen(
+            "file:///sdcard/Music/{}".format(music_file_to_play)):
+        pri_ad.log.error("Failed to play music")
+        return False
+
+    pri_ad.droid.mediaPlaySetLooping(True)
+    pri_ad.log.info("Music is now playing on device {}".format(pri_ad.serial))
+    return True
+
+
+def wifi_connection_check(pri_ad, ssid):
+    """Function to check existence of wifi connection.
+
+    Args:
+        pri_ad : An android device.
+        ssid : wifi ssid to check.
+
+    Returns:
+        True if wifi connection exists, False otherwise.
+    """
+    wifi_info = pri_ad.droid.wifiGetConnectionInfo()
+    if (wifi_info["SSID"] == ssid and
+            wifi_info["supplicant_state"] == "completed"):
+        return True
+    pri_ad.log.error("Wifi Connection check failed : {}".format(wifi_info))
+    return False
+
+
+def push_music_to_android_device(ad, audio_params):
+    """Add music to Android device as specified by the test config
+
+    Args:
+        ad: Android device
+        audio_params: Music file to push.
+
+    Returns:
+        True on success, False on failure
+    """
+    ad.log.info("Pushing music to the Android device")
+    android_music_path = "/sdcard/Music/"
+    music_path = audio_params["music_file"]
+    if type(music_path) is list:
+        ad.log.info("Media ready to push as is.")
+        for item in music_path:
+            music_file_to_play = item
+            ad.adb.push(item, android_music_path)
+        return music_file_to_play
+    else:
+        music_file_to_play = audio_params["music_file"]
+        ad.adb.push("{} {}".format(music_file_to_play, android_music_path))
+        return (os.path.basename(music_file_to_play))
+
+def bokeh_plot(data_sets,
+               legends,
+               fig_property,
+               shaded_region=None,
+               output_file_path=None):
+    """Plot bokeh figs.
+        Args:
+            data_sets: data sets including lists of x_data and lists of y_data
+                       ex: [[[x_data1], [x_data2]], [[y_data1],[y_data2]]]
+            legends: list of legend for each curve
+            fig_property: dict containing the plot property, including title,
+                      labels, linewidth, circle size, etc.
+            shaded_region: optional dict containing data for plot shading
+            output_file_path: optional path at which to save figure
+        Returns:
+            plot: bokeh plot figure object
+    """
+    tools = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save'
+    plot = figure(plot_width=1300,
+                  plot_height=700,
+                  title=fig_property['title'],
+                  tools=tools,
+                  output_backend="webgl")
+    plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="width"))
+    plot.add_tools(bokeh_tools.WheelZoomTool(dimensions="height"))
+    colors = [
+        'red', 'green', 'blue', 'olive', 'orange', 'salmon', 'black', 'navy',
+        'yellow', 'darkred', 'goldenrod'
+    ]
+    if shaded_region:
+        band_x = shaded_region["x_vector"]
+        band_x.extend(shaded_region["x_vector"][::-1])
+        band_y = shaded_region["lower_limit"]
+        band_y.extend(shaded_region["upper_limit"][::-1])
+        plot.patch(band_x,
+                   band_y,
+                   color='#7570B3',
+                   line_alpha=0.1,
+                   fill_alpha=0.1)
+
+    for x_data, y_data, legend in zip(data_sets[0], data_sets[1], legends):
+        index_now = legends.index(legend)
+        color = colors[index_now % len(colors)]
+        plot.line(x_data,
+                  y_data,
+                  legend=str(legend),
+                  line_width=fig_property['linewidth'],
+                  color=color)
+        plot.circle(x_data,
+                    y_data,
+                    size=fig_property['markersize'],
+                    legend=str(legend),
+                    fill_color=color)
+
+    # Plot properties
+    plot.xaxis.axis_label = fig_property['x_label']
+    plot.yaxis.axis_label = fig_property['y_label']
+    plot.legend.location = "top_right"
+    plot.legend.click_policy = "hide"
+    plot.title.text_font_size = {'value': '15pt'}
+    if output_file_path is not None:
+        output_file(output_file_path)
+        save(plot)
+    return plot
+
+def bokeh_chart_plot(bt_attenuation_range,
+               data_sets,
+               legends,
+               fig_property,
+               shaded_region=None,
+               output_file_path=None):
+    """Plot bokeh figs.
+
+    Args:
+        bt_attenuation_range: range of BT attenuation.
+        data_sets: data sets including lists of x_data and lists of y_data
+            ex: [[[x_data1], [x_data2]], [[y_data1],[y_data2]]]
+        legends: list of legend for each curve
+        fig_property: dict containing the plot property, including title,
+                      labels, linewidth, circle size, etc.
+        shaded_region: optional dict containing data for plot shading
+        output_file_path: optional path at which to save figure
+
+    Returns:
+        plot: bokeh plot figure object
+    """
+    TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
+    colors = [
+        'red', 'green', 'blue', 'olive', 'orange', 'salmon', 'black', 'navy',
+        'yellow', 'darkred', 'goldenrod'
+    ]
+    plot = []
+    data = [[], []]
+    legend = []
+    for i in bt_attenuation_range:
+        if "Packet drop" in legends[i][0]:
+            plot_info = {0: "A2dp_packet_drop_plot", 1: "throughput_plot"}
+        else:
+            plot_info = {0: "throughput_plot"}
+        for j in plot_info:
+            if "Packet drops" in legends[i][j]:
+                if data_sets[i]["a2dp_packet_drops"]:
+                    plot_i_j = figure(
+                        plot_width=1000,
+                        plot_height=500,
+                        title=fig_property['title'],
+                        tools=TOOLS)
+
+                    plot_i_j.add_tools(
+                        bokeh_tools.WheelZoomTool(dimensions="width"))
+                    plot_i_j.add_tools(
+                        bokeh_tools.WheelZoomTool(dimensions="height"))
+                    plot_i_j.xaxis.axis_label = fig_property['x_label']
+                    plot_i_j.yaxis.axis_label = fig_property['y_label'][j]
+                    plot_i_j.legend.location = "top_right"
+                    plot_i_j.legend.click_policy = "hide"
+                    plot_i_j.title.text_font_size = {'value': '15pt'}
+
+                    plot_i_j.line(
+                        data_sets[i]["a2dp_attenuation"],
+                        data_sets[i]["a2dp_packet_drops"],
+                        legend=legends[i][j],
+                        line_width=3,
+                        color=colors[j])
+                    plot_i_j.circle(
+                        data_sets[i]["a2dp_attenuation"],
+                        data_sets[i]["a2dp_packet_drops"],
+                        legend=str(legends[i][j]),
+                        fill_color=colors[j])
+                    plot.append(plot_i_j)
+            elif "Performance Results" in legends[i][j]:
+                plot_i_j = figure(
+                    plot_width=1000,
+                    plot_height=500,
+                    title=fig_property['title'],
+                    tools=TOOLS)
+                plot_i_j.add_tools(
+                    bokeh_tools.WheelZoomTool(dimensions="width"))
+                plot_i_j.add_tools(
+                    bokeh_tools.WheelZoomTool(dimensions="height"))
+                plot_i_j.xaxis.axis_label = fig_property['x_label']
+                plot_i_j.yaxis.axis_label = fig_property['y_label'][j]
+                plot_i_j.legend.location = "top_right"
+                plot_i_j.legend.click_policy = "hide"
+                plot_i_j.title.text_font_size = {'value': '15pt'}
+                data[0].insert(0, data_sets[i]["attenuation"])
+                data[1].insert(0, data_sets[i]["throughput_received"])
+                legend.insert(0, legends[i][j + 1])
+                plot_i_j.line(
+                    data_sets[i]["user_attenuation"],
+                    data_sets[i]["user_throughput"],
+                    legend=legends[i][j],
+                    line_width=3,
+                    color=colors[j])
+                plot_i_j.circle(
+                    data_sets[i]["user_attenuation"],
+                    data_sets[i]["user_throughput"],
+                    legend=str(legends[i][j]),
+                    fill_color=colors[j])
+                plot_i_j.line(
+                    data_sets[i]["attenuation"],
+                    data_sets[i]["throughput_received"],
+                    legend=legends[i][j + 1],
+                    line_width=3,
+                    color=colors[j])
+                plot_i_j.circle(
+                    data_sets[i]["attenuation"],
+                    data_sets[i]["throughput_received"],
+                    legend=str(legends[i][j + 1]),
+                    fill_color=colors[j])
+                if shaded_region:
+                    band_x = shaded_region[i]["x_vector"]
+                    band_x.extend(shaded_region[i]["x_vector"][::-1])
+                    band_y = shaded_region[i]["lower_limit"]
+                    band_y.extend(shaded_region[i]["upper_limit"][::-1])
+                    plot_i_j.patch(
+                        band_x,
+                        band_y,
+                        color='#7570B3',
+                        line_alpha=0.1,
+                        fill_alpha=0.1)
+                plot.append(plot_i_j)
+            else:
+                plot_i_j = figure(
+                    plot_width=1000,
+                    plot_height=500,
+                    title=fig_property['title'],
+                    tools=TOOLS)
+                plot_i_j.add_tools(
+                    bokeh_tools.WheelZoomTool(dimensions="width"))
+                plot_i_j.add_tools(
+                    bokeh_tools.WheelZoomTool(dimensions="height"))
+                plot_i_j.xaxis.axis_label = fig_property['x_label']
+                plot_i_j.yaxis.axis_label = fig_property['y_label'][j]
+                plot_i_j.legend.location = "top_right"
+                plot_i_j.legend.click_policy = "hide"
+                plot_i_j.title.text_font_size = {'value': '15pt'}
+                data[0].insert(0, data_sets[i]["attenuation"])
+                data[1].insert(0, data_sets[i]["throughput_received"])
+                legend.insert(0, legends[i][j])
+                plot_i_j.line(
+                    data_sets[i]["attenuation"],
+                    data_sets[i]["throughput_received"],
+                    legend=legends[i][j],
+                    line_width=3,
+                    color=colors[j])
+                plot_i_j.circle(
+                    data_sets[i]["attenuation"],
+                    data_sets[i]["throughput_received"],
+                    legend=str(legends[i][j]),
+                    fill_color=colors[j])
+                plot.append(plot_i_j)
+    fig_property['y_label'] = "Throughput (Mbps)"
+    all_plot = bokeh_plot(data, legend, fig_property, shaded_region=None,
+            output_file_path=None)
+    plot.insert(0, all_plot)
+    if output_file_path is not None:
+        output_file(output_file_path)
+        save(column(plot))
+    return plot
+
+
+class A2dpDumpsysParser():
+
+    def __init__(self):
+        self.count_list = []
+        self.frame_list = []
+        self.dropped_count = None
+
+    def parse(self, file_path):
+        """Convenience function to parse a2dp dumpsys logs.
+
+        Args:
+            file_path: Path of dumpsys logs.
+
+        Returns:
+            dropped_list containing packet drop count for every iteration.
+            drop containing list of all packets dropped for test suite.
+        """
+        a2dp_dumpsys_info = []
+        with open(file_path) as dumpsys_file:
+            for line in dumpsys_file:
+                if "A2DP State:" in line:
+                    a2dp_dumpsys_info.append(line)
+                elif "Counts (max dropped)" not in line and len(
+                        a2dp_dumpsys_info) > 0:
+                    a2dp_dumpsys_info.append(line)
+                elif "Counts (max dropped)" in line:
+                    a2dp_dumpsys_info = ''.join(a2dp_dumpsys_info)
+                    a2dp_info = a2dp_dumpsys_info.split("\n")
+                    # Ex: Frames per packet (total/max/ave) : 5034 / 1 / 0
+                    frames = int(re.split("[':/()]", str(a2dp_info[-3]))[-3])
+                    self.frame_list.append(frames)
+                    # Ex : Counts (flushed/dropped/dropouts) : 0 / 4 / 0
+                    count = int(re.split("[':/()]", str(a2dp_info[-2]))[-2])
+                    if count > 0:
+                        for i in range(len(self.count_list)):
+                            count = count - self.count_list[i]
+                        self.count_list.append(count)
+                        if len(self.frame_list) > 1:
+                            last_frame = self.frame_list[-1] - self.frame_list[
+                                -2]
+                            self.dropped_count = (count / last_frame) * 100
+                        else:
+                            self.dropped_count = (
+                                count / self.frame_list[-1]) * 100
+                    else:
+                        self.dropped_count = count
+                    logging.info(a2dp_dumpsys_info)
+                    return self.dropped_count
diff --git a/acts_tests/acts_contrib/test_utils/coex/hotspot_utils.py b/acts_tests/acts_contrib/test_utils/coex/hotspot_utils.py
new file mode 100644
index 0000000..a6109bd
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/coex/hotspot_utils.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+
+# WiFi Frequency and channel map.
+wifi_channel_map = {
+    2412: 1,
+    2417: 2,
+    2422: 3,
+    2427: 4,
+    2432: 5,
+    2437: 6,
+    2442: 7,
+    2447: 8,
+    2452: 9,
+    2457: 10,
+    2462: 11,
+    2467: 12,
+    2472: 13,
+    2484: 14,
+    5170: 34,
+    5180: 36,
+    5190: 38,
+    5200: 40,
+    5210: 42,
+    5220: 44,
+    5230: 46,
+    5240: 48,
+    5260: 52,
+    5280: 56,
+    5300: 60,
+    5320: 64,
+    5500: 100,
+    5520: 104,
+    5540: 108,
+    5560: 112,
+    5580: 116,
+    5600: 120,
+    5620: 124,
+    5640: 128,
+    5660: 132,
+    5680: 136,
+    5700: 140,
+    5720: 144,
+    5745: 149,
+    5755: 151,
+    5765: 153,
+    5775: 155,
+    5795: 159,
+    5785: 157,
+    5805: 161,
+    5825: 165
+}
+
+# Supported lte band.
+# TODO:(@sairamganesh) Make a common function to support different SKU's.
+
+supported_lte_bands = ['OB1', 'OB2', 'OB3', 'OB4', 'OB5', 'OB7', 'OB8',
+                       'OB12', 'OB13', 'OB14', 'OB17', 'OB18', 'OB19',
+                       'OB20', 'OB25', 'OB26', 'OB28', 'OB30', 'OB38',
+                       'OB39', 'OB40', 'OB41', 'OB46', 'OB48', 'OB66',
+                       'OB71'
+                       ]
+
+# list of TDD Bands supported.
+tdd_band_list = ['OB33', 'OB34', 'OB35', 'OB36', 'OB37', 'OB38', 'OB39', 'OB40',
+                 'OB41', 'OB42', 'OB43', 'OB44']
+
+# lte band channel map.
+# For every band three channels are chosen(Low, Mid and High)
+band_channel_map = {
+    'OB1': [25, 300, 575],
+    'OB2': [625, 900, 1175],
+    'OB3': [1225, 1575, 1925],
+    'OB4': [1975, 2175, 2375],
+    'OB5': [2425, 2525, 2625],
+    'OB7': [3100],
+    'OB8': [3475, 3625, 3775],
+    'OB12': [5035, 5095, 5155],
+    'OB13': [5205, 5230, 5255],
+    'OB14': [5310, 5330, 5355],
+    'OB17': [5755, 5790, 5825],
+    'OB18': [5875, 5925, 5975],
+    'OB19': [6025, 6075, 6125],
+    'OB20': [6180, 6300, 6425],
+    'OB25': [8065, 8365, 8665],
+    'OB26': [8715, 8865, 9010],
+    'OB28': [9235, 9435, 9635],
+    'OB30': [9795, 9820, 9840],
+    'OB38': [37750, 38000, 38245],
+    'OB39': [38250, 38450, 38645],
+    'OB40': [38650, 39150, 39645],
+    'OB41': [39650, 40620, 41585],
+    'OB46': [46790, 50665, 54535],
+    'OB48': [55240, 55990, 56735],
+    'OB66': [66461, 66886, 67331],
+    'OB71': [68611, 68761, 68906]
+}
diff --git a/acts_tests/acts_contrib/test_utils/fuchsia/__init__.py b/acts_tests/acts_contrib/test_utils/fuchsia/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/fuchsia/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/fuchsia/bt_test_utils.py b/acts_tests/acts_contrib/test_utils/fuchsia/bt_test_utils.py
new file mode 100644
index 0000000..c5a9250
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/fuchsia/bt_test_utils.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import time
+
+
+def le_scan_for_device_by_name(fd,
+                               log,
+                               search_name,
+                               timeout,
+                               partial_match=False):
+    """Scan for and returns the first BLE advertisement with the device name.
+
+    Args:
+        fd: The Fuchsia device to start LE scanning on.
+        name: The name to find.
+        timeout: How long to scan for.
+        partial_match: Only do a partial match for the LE advertising name.
+          This will return the first result that had a partial match.
+
+    Returns:
+        The dictionary of device information.
+    """
+    scan_filter = {"name_substring": search_name}
+    fd.gattc_lib.bleStartBleScan(scan_filter)
+    end_time = time.time() + timeout
+    found_device = None
+    while time.time() < end_time and not found_device:
+        time.sleep(1)
+        scan_res = fd.gattc_lib.bleGetDiscoveredDevices()['result']
+        for device in scan_res:
+            name, did, connectable = device["name"], device["id"], device[
+                "connectable"]
+            if name == search_name or (partial_match and search_name in name):
+                log.info("Successfully found advertisement! name, id: {}, {}".
+                         format(name, did))
+                found_device = device
+    fd.gattc_lib.bleStopBleScan()
+    if not found_device:
+        log.error("Failed to find device with name {}.".format(search_name))
+        return found_device
+    return found_device
diff --git a/acts_tests/acts_contrib/test_utils/fuchsia/sdp_records.py b/acts_tests/acts_contrib/test_utils/fuchsia/sdp_records.py
new file mode 100644
index 0000000..1da2e62
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/fuchsia/sdp_records.py
@@ -0,0 +1,491 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from acts_contrib.test_utils.bt.bt_constants import bt_attribute_values
+from acts_contrib.test_utils.bt.bt_constants import sig_uuid_constants
+
+BASE_UUID = sig_uuid_constants['BASE_UUID']
+
+# A list of pre-defined SDP definitions
+sdp_pts_record_list = [
+    {
+        'service_class_uuids': [BASE_UUID.format(sig_uuid_constants['AudioSink'])],
+        'protocol_descriptors': [
+            {
+                'protocol': int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data': int(sig_uuid_constants['AVDTP'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVDTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['AdvancedAudioDistribution'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors': [{
+            'protocol':
+            int(sig_uuid_constants['L2CAP'], 16),
+            'params': [
+                {
+                    'data': int(sig_uuid_constants['AVDTP'], 16),
+                },
+                {
+                    'data': int(sig_uuid_constants['AVCTP'], 16),
+                },
+                {
+                    'data': int(sig_uuid_constants['GenericAudio'], 16),
+                },
+            ]
+        }],
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_SERVICE_AVAILABILITY'],
+            'element': {
+                'data': 0xff  # Indicate all available
+            }
+        }]
+    },
+    {
+        'service_class_uuids': [
+            BASE_UUID.format(sig_uuid_constants['A/V_RemoteControlTarget']),
+            BASE_UUID.format(sig_uuid_constants['A/V_RemoteControl']),
+            BASE_UUID.format(sig_uuid_constants['A/V_RemoteControlController'])
+        ],
+        'protocol_descriptors': [
+            {
+                'protocol': int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data': int(sig_uuid_constants['AVCTP'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVCTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['A/V_RemoteControl'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors':
+        None,
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_A2DP_SUPPORTED_FEATURES'],
+            'element': {
+                'data': 0x0011
+            }
+        }]
+    },
+    {
+        'service_class_uuids': [BASE_UUID.format(sig_uuid_constants['PANU'])],
+        'protocol_descriptors': [
+            {
+                'protocol': int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data': int(sig_uuid_constants['NAP'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVCTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['A/V_RemoteControl'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors':
+        None,
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_A2DP_SUPPORTED_FEATURES'],
+            'element': {
+                'data': 0x0011
+            }
+        }]
+    },
+    {
+        'service_class_uuids': [BASE_UUID.format(sig_uuid_constants['SerialPort'])],
+        'protocol_descriptors': [
+            {
+                'protocol':
+                int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data':
+                    int(sig_uuid_constants['SerialPort'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVCTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['A/V_RemoteControl'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors':
+        None,
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_A2DP_SUPPORTED_FEATURES'],
+            'element': {
+                'data': 0x0011
+            }
+        }]
+    },
+    {
+        'service_class_uuids': [BASE_UUID.format(sig_uuid_constants['DialupNetworking'])],
+        'protocol_descriptors': [
+            {
+                'protocol':
+                int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data':
+                    int(sig_uuid_constants['DialupNetworking'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVCTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['A/V_RemoteControl'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors':
+        None,
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_A2DP_SUPPORTED_FEATURES'],
+            'element': {
+                'data': 0x0011
+            }
+        }]
+    },
+    {
+        'service_class_uuids': [BASE_UUID.format(sig_uuid_constants['OBEXObjectPush'])],
+        'protocol_descriptors': [
+            {
+                'protocol':
+                int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data':
+                    int(sig_uuid_constants['OBEXObjectPush'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVCTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['A/V_RemoteControl'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors':
+        None,
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_A2DP_SUPPORTED_FEATURES'],
+            'element': {
+                'data': 0x0011
+            }
+        }]
+    },
+    {
+        'service_class_uuids': [BASE_UUID.format(sig_uuid_constants['OBEXFileTransfer'])],
+        'protocol_descriptors': [
+            {
+                'protocol':
+                int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data':
+                    int(sig_uuid_constants['OBEXFileTransfer'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVCTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['A/V_RemoteControl'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors':
+        None,
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_A2DP_SUPPORTED_FEATURES'],
+            'element': {
+                'data': 0x0011
+            }
+        }]
+    },
+    {
+        'service_class_uuids': [BASE_UUID.format(sig_uuid_constants['Headset'])],
+        'protocol_descriptors': [
+            {
+                'protocol': int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data': int(sig_uuid_constants['Headset'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVCTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['A/V_RemoteControl'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors':
+        None,
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_A2DP_SUPPORTED_FEATURES'],
+            'element': {
+                'data': 0x0011
+            }
+        }]
+    },
+    {
+        'service_class_uuids': [BASE_UUID.format(sig_uuid_constants['HandsfreeAudioGateway'])],
+        'protocol_descriptors': [
+            {
+                'protocol':
+                int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data':
+                    int(sig_uuid_constants['HandsfreeAudioGateway'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVCTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['A/V_RemoteControl'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors':
+        None,
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_A2DP_SUPPORTED_FEATURES'],
+            'element': {
+                'data': 0x0011
+            }
+        }]
+    },
+    {
+        'service_class_uuids': [BASE_UUID.format(sig_uuid_constants['Handsfree'])],
+        'protocol_descriptors': [
+            {
+                'protocol': int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data': int(sig_uuid_constants['Handsfree'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVCTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['A/V_RemoteControl'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors':
+        None,
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_A2DP_SUPPORTED_FEATURES'],
+            'element': {
+                'data': 0x0011
+            }
+        }]
+    },
+    {
+        'service_class_uuids': [BASE_UUID.format(sig_uuid_constants['SIM_Access'])],
+        'protocol_descriptors': [
+            {
+                'protocol': int(sig_uuid_constants['L2CAP'], 16),
+                'params': [{
+                    'data': int(sig_uuid_constants['SIM_Access'], 16),
+                }]
+            },
+            {
+                'protocol': int(sig_uuid_constants['AVCTP'], 16),
+                'params': [{
+                    'data': 0x103  # to indicate 1.3
+                }]
+            },
+        ],
+        'profile_descriptors': [{
+            'profile_id':
+            int(sig_uuid_constants['A/V_RemoteControl'], 16),
+            'major_version':
+            1,
+            'minor_version':
+            2,
+        }],
+        'additional_protocol_descriptors':
+        None,
+        'information': [{
+            'language': "en",
+            'name': "A2DP",
+            'description': "Advanced Audio Distribution Profile",
+            'provider': "Fuchsia"
+        }],
+        'additional_attributes': [{
+            'id':
+            bt_attribute_values['ATTR_A2DP_SUPPORTED_FEATURES'],
+            'element': {
+                'data': 0x0011
+            }
+        }]
+    }
+]
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/fuchsia/utils.py b/acts_tests/acts_contrib/test_utils/fuchsia/utils.py
new file mode 100644
index 0000000..8014220
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/fuchsia/utils.py
@@ -0,0 +1,124 @@
+#!/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 os
+
+from acts.libs.proc.job import Error
+
+
+def http_file_download_by_curl(fd,
+                               url,
+                               out_path='/tmp/',
+                               curl_loc='/bin/curl',
+                               remove_file_after_check=True,
+                               timeout=3600,
+                               limit_rate=None,
+                               additional_args=None,
+                               retry=3):
+    """Download http file by ssh curl.
+
+    Args:
+        fd: Fuchsia Device Object.
+        url: The url that file to be downloaded from.
+        out_path: Optional. Where to download file to.
+            out_path is /tmp by default.
+        curl_loc: Location of curl binary on fd.
+        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.
+        additional_args: Any additional args for curl.
+        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 = curl_loc
+    if limit_rate:
+        curl_cmd += ' --limit-rate %s' % limit_rate
+    if retry:
+        curl_cmd += ' --retry %s' % retry
+    if additional_args:
+        curl_cmd += ' %s' % additional_args
+    curl_cmd += ' --url %s > %s' % (url, file_path)
+    try:
+        fd.log.info(
+            'Download %s to %s by ssh command %s' % (url, file_path, curl_cmd))
+
+        status = fd.send_command_ssh(curl_cmd, timeout=timeout)
+        if isinstance(status, Error):
+            status = status.result
+        if not status.stderr:
+            if int(status.exit_status) != 0:
+                fd.log.warning('Curl command: "%s" failed with error %s' %
+                               (curl_cmd, status.exit_status))
+                return False
+
+            if _check_file_existence(fd, file_path):
+                fd.log.info(
+                    '%s is downloaded to %s successfully' % (url, file_path))
+                return True
+        else:
+            fd.log.warning('Fail to download %s' % url)
+            return False
+    except Exception as e:
+        fd.log.warning('Download %s failed with exception %s' % (url, e))
+        return False
+    finally:
+        if remove_file_after_check:
+            fd.log.info('Remove the downloaded file %s' % file_path)
+            fd.send_command_ssh('rm %s' % file_path)
+
+
+def _generate_file_directory_and_file_name(url, out_path):
+    """Splits the file from the url and specifies the appropriate location of
+       where to store the downloaded file.
+
+    Args:
+        url: A url to the file that is going to be downloaded.
+        out_path: The location of where to store the file that is downloaded.
+
+    Returns:
+        file_directory: The directory of where to store the downloaded file.
+        file_name: The name of the file that is being downloaded.
+    """
+    file_name = url.split('/')[-1]
+    if not out_path:
+        file_directory = '/tmp/'
+    elif not out_path.endswith('/'):
+        file_directory, file_name = os.path.split(out_path)
+    else:
+        file_directory = out_path
+    return file_directory, file_name
+
+
+def _check_file_existence(fd, file_path):
+    """Check file existence by file_path. If expected_file_size
+       is provided, then also check if the file meet the file size requirement.
+
+    Args:
+        fd: A fuchsia device
+        file_path: Where to store the file on the fuchsia device.
+    """
+    out = fd.send_command_ssh('ls -al "%s"' % file_path)
+    if isinstance(out, Error):
+        out = out.result
+    if 'No such file or directory' in out.stdout:
+        fd.log.debug('File %s does not exist.' % file_path)
+        return False
+    else:
+        fd.log.debug('File %s exists.' % file_path)
+        return True
diff --git a/acts_tests/acts_contrib/test_utils/gnss/__init__.py b/acts_tests/acts_contrib/test_utils/gnss/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/gnss/dut_log_test_utils.py b/acts_tests/acts_contrib/test_utils/gnss/dut_log_test_utils.py
new file mode 100644
index 0000000..6dfa77d
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/dut_log_test_utils.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import time
+import errno
+
+DEVICE_CFG_FOLDER = "/data/vendor/radio/diag_logs/cfg/"
+DEVICE_DIAGMDLOG_FOLDER = "/data/vendor/radio/diag_logs/logs/"
+MDLOG_SETTLING_TIME = 2
+MDLOG_PROCESS_KILL_TIME = 3
+NOHUP_CMD = "nohup diag_mdlog -f {} -o {} -s 100 -c &> /dev/null &"
+
+
+def find_device_qxdm_log_mask(ad, maskfile):
+    """Finds device's diagmd mask file
+
+           Args:
+               ad: the target android device, AndroidDevice object
+               maskfile: Device's mask file name
+
+           Return:
+               exists, if cfg file is present
+
+           Raises:
+               FileNotFoundError if maskfile is not present
+    """
+
+    if ".cfg" not in maskfile:
+        # errno.ENOENT - No such file or directory
+        raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
+                                maskfile)
+    else:
+        cfg_path = os.path.join(DEVICE_CFG_FOLDER, maskfile)
+        device_mask_file = ad.adb.shell('test -e %s && echo exists' % cfg_path)
+        return device_mask_file
+
+
+def set_diagmdlog_command(ad, maskfile):
+    """Sets diagmdlog command to run in background
+
+       Args:
+           ad: the target android device, AndroidDevice object
+           maskfile: mask file name
+
+    """
+    cfg_path = os.path.join(DEVICE_CFG_FOLDER, maskfile)
+    ad.adb.shell(NOHUP_CMD.format(cfg_path, DEVICE_DIAGMDLOG_FOLDER))
+    ad.log.info("Running diag_mdlog in the background")
+    time.sleep(MDLOG_SETTLING_TIME)
+
+
+def verify_diagmd_folder_exists(ad):
+    """Verify diagmd folder existence in device
+
+       Args:
+           ad: the target android device, AndroidDevice object
+
+    """
+    mask_folder_exists = ad.adb.shell(
+        'test -d %s && echo exists' % DEVICE_CFG_FOLDER)
+    diag_folder_exists = ad.adb.shell(
+        'test -d %s && echo exists' % DEVICE_DIAGMDLOG_FOLDER)
+    if not mask_folder_exists and diag_folder_exists:
+        ad.adb.shell("mkdir " + DEVICE_CFG_FOLDER)
+        ad.adb.shell("mkdir " + DEVICE_DIAGMDLOG_FOLDER)
+
+
+def start_diagmdlog_background(ad, maskfile="default.cfg", is_local=True):
+    """Runs diagmd_log in background
+
+       Args:
+           ad: the target android device, AndroidDevice object
+           maskfile: Local Mask file path or Device's mask file name
+           is_local: False, take cfgfile from config.
+                     True, find cfgfile in device and run diagmdlog
+
+       Raises:
+           FileNotFoundError if maskfile is not present
+           ProcessLookupError if diagmdlog process not present
+    """
+    if is_local:
+        find_device_qxdm_log_mask(ad, maskfile)
+        set_diagmdlog_command(ad, maskfile)
+    else:
+        if not os.path.isfile(maskfile):
+            # errno.ENOENT - No such file or directory
+            raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT),
+                                    maskfile)
+        else:
+            cfgfilename = os.path.basename(maskfile)
+            verify_diagmd_folder_exists(ad)
+            ad.adb.push("{} {}".format(maskfile, DEVICE_CFG_FOLDER))
+            set_diagmdlog_command(ad, cfgfilename)
+    output = ad.adb.shell("pgrep diag_mdlog")
+    ad.log.info("Checking diag_mdlog in process")
+    if not output:
+        # errno.ESRCH - No such process
+        raise ProcessLookupError(errno.ESRCH, os.strerror(errno.ESRCH),
+                                 "diag_mdlog")
+
+
+def stop_background_diagmdlog(ad, local_logpath, keep_logs=True):
+    """Stop diagmdlog and pulls diag_mdlog from android device
+
+       Args:
+           ad: the target android device, AndroidDevice object
+           local_logpath: Local file path to pull the diag_mdlog logs
+           keep_logs: False, delete log files from the diag_mdlog path
+
+       Raises:
+           ProcessLookupError if diagmdlog process not present
+    """
+    ps_output = ad.adb.shell("pgrep diag_mdlog")
+    ad.log.info("Checking diag_mdlog in process")
+    if ps_output:
+        output = ad.adb.shell("diag_mdlog -k")
+        time.sleep(MDLOG_PROCESS_KILL_TIME)
+        if "stopping" in output:
+            ad.log.debug("Stopping diag_mdlog")
+            ad.adb.pull("{} {}".format(DEVICE_DIAGMDLOG_FOLDER, local_logpath))
+            ad.log.debug("Pulling diag_logs from the device to local")
+            if not keep_logs:
+                ad.adb.shell("rm -rf " + DEVICE_DIAGMDLOG_FOLDER + "*.*")
+                ad.log.debug("diagmd logs are deleted from device")
+            else:
+                ad.log.debug("diagmd logs are not deleted from device")
+        else:
+            output = ad.adb.shell("pidof diag_mdlog")
+            if output:
+                ad.adb.shell("kill -9 {}".format(output))
+                ad.log.debug("Kill the existing qxdm process")
+                ad.adb.pull("{} {}".format(DEVICE_DIAGMDLOG_FOLDER,
+                                           local_logpath))
+                ad.log.debug("Pulling diag_logs from the device to local")
+            else:
+                # errno.ESRCH - No such process
+                raise ProcessLookupError(errno.ESRCH, os.strerror(errno.ESRCH),
+                                         "diag_mdlog")
+    else:
+        # errno.ESRCH - No such process
+        raise ProcessLookupError(errno.ESRCH, os.strerror(errno.ESRCH),
+                                 "diag_mdlog")
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
new file mode 100644
index 0000000..0c94b49
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/gnss_test_utils.py
@@ -0,0 +1,1475 @@
+#!/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.
+
+import time
+import re
+import os
+import math
+import shutil
+import fnmatch
+import posixpath
+import tempfile
+from collections import namedtuple
+
+from acts import utils
+from acts import signals
+from acts.libs.proc import job
+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 SL4A_APK_NAME
+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.instrumentation.device.command.instrumentation_command_builder import InstrumentationCommandBuilder
+from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import InstrumentationTestCommandBuilder
+from acts.utils import get_current_epoch_time
+from acts.utils import epoch_to_human_time
+
+WifiEnums = wutils.WifiEnums
+PULL_TIMEOUT = 300
+GNSSSTATUS_LOG_PATH = (
+    "/storage/emulated/0/Android/data/com.android.gpstool/files/")
+QXDM_MASKS = ["GPS.cfg", "GPS-general.cfg", "default.cfg"]
+TTFF_REPORT = namedtuple(
+    "TTFF_REPORT", "utc_time ttff_loop ttff_sec ttff_pe ttff_ant_cn "
+                   "ttff_base_cn")
+TRACK_REPORT = namedtuple(
+    "TRACK_REPORT", "l5flag pe ant_top4cn ant_cn base_top4cn base_cn")
+LOCAL_PROP_FILE_CONTENTS = """\
+log.tag.LocationManagerService=VERBOSE
+log.tag.GnssLocationProvider=VERBOSE
+log.tag.GnssMeasurementsProvider=VERBOSE
+log.tag.GpsNetInitiatedHandler=VERBOSE
+log.tag.GnssNetInitiatedHandler=VERBOSE
+log.tag.GnssNetworkConnectivityHandler=VERBOSE
+log.tag.ConnectivityService=VERBOSE
+log.tag.ConnectivityManager=VERBOSE
+log.tag.GnssVisibilityControl=VERBOSE
+log.tag.NtpTimeHelper=VERBOSE
+log.tag.NtpTrustedTime=VERBOSE
+log.tag.GnssPsdsDownloader=VERBOSE
+log.tag.Gnss=VERBOSE
+log.tag.GnssConfiguration=VERBOSE"""
+TEST_PACKAGE_NAME = "com.google.android.apps.maps"
+LOCATION_PERMISSIONS = [
+    "android.permission.ACCESS_FINE_LOCATION",
+    "android.permission.ACCESS_COARSE_LOCATION"
+]
+GNSSTOOL_PACKAGE_NAME = "com.android.gpstool"
+GNSSTOOL_PERMISSIONS = [
+    "android.permission.ACCESS_FINE_LOCATION",
+    "android.permission.READ_EXTERNAL_STORAGE",
+    "android.permission.ACCESS_COARSE_LOCATION",
+    "android.permission.CALL_PHONE",
+    "android.permission.WRITE_CONTACTS",
+    "android.permission.CAMERA",
+    "android.permission.WRITE_EXTERNAL_STORAGE",
+    "android.permission.READ_CONTACTS",
+    "android.permission.ACCESS_BACKGROUND_LOCATION"
+]
+
+
+class GnssTestUtilsError(Exception):
+    pass
+
+
+def remount_device(ad):
+    """Remount device file system to read and write.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    for retries in range(5):
+        ad.root_adb()
+        if ad.adb.getprop("ro.boot.veritymode") == "enforcing":
+            ad.adb.disable_verity()
+            reboot(ad)
+        remount_result = ad.adb.remount()
+        ad.log.info("Attempt %d - %s" % (retries + 1, remount_result))
+        if "remount succeeded" in remount_result:
+            break
+
+
+def reboot(ad):
+    """Reboot device and check if mobile data is available.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    ad.log.info("Reboot device to make changes take effect.")
+    ad.reboot()
+    ad.unlock_screen(password=None)
+    if not int(ad.adb.shell("settings get global mobile_data")) == 1:
+        set_mobile_data(ad, True)
+    utils.sync_device_time(ad)
+
+
+def enable_gnss_verbose_logging(ad):
+    """Enable GNSS VERBOSE Logging and persistent logcat.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    remount_device(ad)
+    ad.log.info("Enable GNSS VERBOSE Logging and persistent logcat.")
+    ad.adb.shell("echo -e '\nDEBUG_LEVEL = 5' >> /vendor/etc/gps.conf")
+    ad.adb.shell("echo %r >> /data/local.prop" % LOCAL_PROP_FILE_CONTENTS)
+    ad.adb.shell("chmod 644 /data/local.prop")
+    ad.adb.shell("setprop persist.logd.logpersistd.size 20000")
+    ad.adb.shell("setprop persist.logd.size 16777216")
+    ad.adb.shell("setprop persist.vendor.radio.adb_log_on 1")
+    ad.adb.shell("setprop persist.logd.logpersistd logcatd")
+    ad.adb.shell("setprop log.tag.copresGcore VERBOSE")
+    ad.adb.shell("sync")
+
+
+def get_am_flags(value):
+    """Returns the (value, type) flags for a given python value."""
+    if type(value) is bool:
+        return str(value).lower(), 'boolean'
+    elif type(value) is str:
+        return value, 'string'
+    raise ValueError("%s should be either 'boolean' or 'string'" % value)
+
+
+def enable_compact_and_particle_fusion_log(ad):
+    """Enable CompactLog, FLP particle fusion log and disable gms
+    location-based quake monitoring.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    ad.root_adb()
+    ad.log.info("Enable FLP flags and Disable GMS location-based quake "
+                "monitoring.")
+    overrides = {
+        'compact_log_enabled': True,
+        'flp_use_particle_fusion': True,
+        'flp_particle_fusion_extended_bug_report': True,
+        'flp_event_log_size': '86400',
+        'proks_config': '28',
+        'flp_particle_fusion_bug_report_window_sec': '86400',
+        'flp_particle_fusion_bug_report_max_buffer_size': '86400',
+        'seismic_data_collection': False,
+        'Ealert__enable': False,
+    }
+    for flag, python_value in overrides.items():
+        value, type = get_am_flags(python_value)
+        cmd = ("am broadcast -a com.google.android.gms.phenotype.FLAG_OVERRIDE "
+               "--es package com.google.android.location --es user \* "
+               "--esa flags %s --esa values %s --esa types %s "
+               "com.google.android.gms" % (flag, value, type))
+        ad.adb.shell(cmd)
+    ad.adb.shell("am force-stop com.google.android.gms")
+    ad.adb.shell("am broadcast -a com.google.android.gms.INITIALIZE")
+
+
+def disable_xtra_throttle(ad):
+    """Disable XTRA throttle will have no limit to download XTRA data.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    remount_device(ad)
+    ad.log.info("Disable XTRA Throttle.")
+    ad.adb.shell("echo -e '\nXTRA_TEST_ENABLED=1' >> /vendor/etc/gps.conf")
+    ad.adb.shell("echo -e '\nXTRA_THROTTLE_ENABLED=0' >> /vendor/etc/gps.conf")
+
+
+def enable_supl_mode(ad):
+    """Enable SUPL back on for next test item.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    remount_device(ad)
+    ad.log.info("Enable SUPL mode.")
+    ad.adb.shell("echo -e '\nSUPL_MODE=1' >> /etc/gps_debug.conf")
+
+
+def disable_supl_mode(ad):
+    """Kill SUPL to test XTRA only test item.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    remount_device(ad)
+    ad.log.info("Disable SUPL mode.")
+    ad.adb.shell("echo -e '\nSUPL_MODE=0' >> /etc/gps_debug.conf")
+    reboot(ad)
+
+
+def kill_xtra_daemon(ad):
+    """Kill XTRA daemon to test SUPL only test item.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    ad.root_adb()
+    ad.log.info("Disable XTRA-daemon until next reboot.")
+    ad.adb.shell("killall xtra-daemon", ignore_status=True)
+
+
+def disable_private_dns_mode(ad):
+    """Due to b/118365122, it's better to disable private DNS mode while
+       testing. 8.8.8.8 private dns sever is unstable now, sometimes server
+       will not response dns query suddenly.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    tutils.get_operator_name(ad.log, ad, subId=None)
+    if ad.adb.shell("settings get global private_dns_mode") != "off":
+        ad.log.info("Disable Private DNS mode.")
+        ad.adb.shell("settings put global private_dns_mode off")
+
+
+def _init_device(ad):
+    """Init GNSS test devices.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    enable_gnss_verbose_logging(ad)
+    enable_compact_and_particle_fusion_log(ad)
+    disable_xtra_throttle(ad)
+    enable_supl_mode(ad)
+    ad.adb.shell("settings put system screen_off_timeout 1800000")
+    wutils.wifi_toggle_state(ad, False)
+    ad.log.info("Setting Bluetooth state to False")
+    ad.droid.bluetoothToggleState(False)
+    set_gnss_qxdm_mask(ad, QXDM_MASKS)
+    check_location_service(ad)
+    set_wifi_and_bt_scanning(ad, True)
+    disable_private_dns_mode(ad)
+    reboot(ad)
+    init_gtw_gpstool(ad)
+
+
+def connect_to_wifi_network(ad, network):
+    """Connection logic for open and psk wifi networks.
+
+    Args:
+        ad: An AndroidDevice object.
+        network: Dictionary with network info.
+    """
+    SSID = network[WifiEnums.SSID_KEY]
+    ad.ed.clear_all_events()
+    wutils.reset_wifi(ad)
+    wutils.start_wifi_connection_scan_and_ensure_network_found(ad, SSID)
+    wutils.wifi_connect(ad, network, num_of_tries=5)
+
+
+def set_wifi_and_bt_scanning(ad, state=True):
+    """Set Wi-Fi and Bluetooth scanning on/off in Settings -> Location
+
+    Args:
+        ad: An AndroidDevice object.
+        state: True to turn on "Wi-Fi and Bluetooth scanning".
+            False to turn off "Wi-Fi and Bluetooth scanning".
+    """
+    ad.root_adb()
+    if state:
+        ad.adb.shell("settings put global wifi_scan_always_enabled 1")
+        ad.adb.shell("settings put global ble_scan_always_enabled 1")
+        ad.log.info("Wi-Fi and Bluetooth scanning are enabled")
+    else:
+        ad.adb.shell("settings put global wifi_scan_always_enabled 0")
+        ad.adb.shell("settings put global ble_scan_always_enabled 0")
+        ad.log.info("Wi-Fi and Bluetooth scanning are disabled")
+
+
+def check_location_service(ad):
+    """Set location service on.
+       Verify if location service is available.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    remount_device(ad)
+    utils.set_location_service(ad, True)
+    location_mode = int(ad.adb.shell("settings get secure location_mode"))
+    ad.log.info("Current Location Mode >> %d" % location_mode)
+    if location_mode != 3:
+        raise signals.TestError("Failed to turn Location on")
+
+
+def clear_logd_gnss_qxdm_log(ad):
+    """Clear /data/misc/logd,
+    /storage/emulated/0/Android/data/com.android.gpstool/files and
+    /data/vendor/radio/diag_logs/logs from previous test item then reboot.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    remount_device(ad)
+    ad.log.info("Clear Logd, GNSS and QXDM Log from previous test item.")
+    ad.adb.shell("rm -rf /data/misc/logd", ignore_status=True)
+    ad.adb.shell(
+        'find %s -name "*.txt" -type f -delete' % GNSSSTATUS_LOG_PATH,
+        ignore_status=True)
+    output_path = posixpath.join(DEFAULT_QXDM_LOG_PATH, "logs")
+    ad.adb.shell("rm -rf %s" % output_path, ignore_status=True)
+    reboot(ad)
+
+
+def get_gnss_qxdm_log(ad, qdb_path):
+    """Get /storage/emulated/0/Android/data/com.android.gpstool/files and
+    /data/vendor/radio/diag_logs/logs for test item.
+
+    Args:
+        ad: An AndroidDevice object.
+        qdb_path: The path of qdsp6m.qdb on different projects.
+    """
+    log_path = ad.device_log_path
+    os.makedirs(log_path, exist_ok=True)
+    gnss_log_name = "gnssstatus_log_%s_%s" % (ad.model, ad.serial)
+    gnss_log_path = posixpath.join(log_path, gnss_log_name)
+    os.makedirs(gnss_log_path, exist_ok=True)
+    ad.log.info("Pull GnssStatus Log to %s" % gnss_log_path)
+    ad.adb.pull("%s %s" % (GNSSSTATUS_LOG_PATH+".", gnss_log_path),
+                timeout=PULL_TIMEOUT, ignore_status=True)
+    shutil.make_archive(gnss_log_path, "zip", gnss_log_path)
+    shutil.rmtree(gnss_log_path)
+    output_path = posixpath.join(DEFAULT_QXDM_LOG_PATH, "logs/.")
+    file_count = ad.adb.shell(
+        "find %s -type f -iname *.qmdl | wc -l" % output_path)
+    if not int(file_count) == 0:
+        qxdm_log_name = "QXDM_%s_%s" % (ad.model, ad.serial)
+        qxdm_log_path = posixpath.join(log_path, qxdm_log_name)
+        os.makedirs(qxdm_log_path, exist_ok=True)
+        ad.log.info("Pull QXDM Log %s to %s" % (output_path, qxdm_log_path))
+        ad.adb.pull("%s %s" % (output_path, qxdm_log_path),
+                    timeout=PULL_TIMEOUT, ignore_status=True)
+        for path in qdb_path:
+            output = ad.adb.pull("%s %s" % (path, qxdm_log_path),
+                                 timeout=PULL_TIMEOUT, ignore_status=True)
+            if "No such file or directory" in output:
+                continue
+            break
+        shutil.make_archive(qxdm_log_path, "zip", qxdm_log_path)
+        shutil.rmtree(qxdm_log_path)
+    else:
+        ad.log.error("QXDM file count is %d. There is no QXDM log on device."
+                     % int(file_count))
+
+
+def set_mobile_data(ad, state):
+    """Set mobile data on or off and check mobile data state.
+
+    Args:
+        ad: An AndroidDevice object.
+        state: True to enable mobile data. False to disable mobile data.
+    """
+    ad.root_adb()
+    if state:
+        ad.log.info("Enable mobile data.")
+        ad.adb.shell("svc data enable")
+    else:
+        ad.log.info("Disable mobile data.")
+        ad.adb.shell("svc data disable")
+    time.sleep(5)
+    out = int(ad.adb.shell("settings get global mobile_data"))
+    if state and out == 1:
+        ad.log.info("Mobile data is enabled and set to %d" % out)
+    elif not state and out == 0:
+        ad.log.info("Mobile data is disabled and set to %d" % out)
+    else:
+        ad.log.error("Mobile data is at unknown state and set to %d" % out)
+
+
+def gnss_trigger_modem_ssr_by_adb(ad, dwelltime=60):
+    """Trigger modem SSR crash by adb and verify if modem crash and recover
+    successfully.
+
+    Args:
+        ad: An AndroidDevice object.
+        dwelltime: Waiting time for modem reset. Default is 60 seconds.
+
+    Returns:
+        True if success.
+        False if failed.
+    """
+    begin_time = get_current_epoch_time()
+    ad.root_adb()
+    cmds = ("echo restart > /sys/kernel/debug/msm_subsys/modem",
+            r"echo 'at+cfun=1,1\r' > /dev/at_mdm0")
+    for cmd in cmds:
+        ad.log.info("Triggering modem SSR crash by %s" % cmd)
+        output = ad.adb.shell(cmd, ignore_status=True)
+        if "No such file or directory" in output:
+            continue
+        break
+    time.sleep(dwelltime)
+    ad.send_keycode("HOME")
+    logcat_results = ad.search_logcat("SSRObserver", begin_time)
+    if logcat_results:
+        for ssr in logcat_results:
+            if "mSubsystem='modem', mCrashReason" in ssr["log_message"]:
+                ad.log.debug(ssr["log_message"])
+                ad.log.info("Triggering modem SSR crash successfully.")
+                return True
+        raise signals.TestError("Failed to trigger modem SSR crash")
+    raise signals.TestError("No SSRObserver found in logcat")
+
+
+def gnss_trigger_modem_ssr_by_mds(ad, dwelltime=60):
+    """Trigger modem SSR crash by mds tool and verify if modem crash and recover
+    successfully.
+
+    Args:
+        ad: An AndroidDevice object.
+        dwelltime: Waiting time for modem reset. Default is 60 seconds.
+    """
+    mds_check = ad.adb.shell("pm path com.google.mdstest")
+    if not mds_check:
+        raise signals.TestError("MDS Tool is not properly installed.")
+    ad.root_adb()
+    cmd = ('am instrument -w -e request "4b 25 03 00" '
+           '"com.google.mdstest/com.google.mdstest.instrument'
+           '.ModemCommandInstrumentation"')
+    ad.log.info("Triggering modem SSR crash by MDS")
+    output = ad.adb.shell(cmd, ignore_status=True)
+    ad.log.debug(output)
+    time.sleep(dwelltime)
+    ad.send_keycode("HOME")
+    if "SUCCESS" in output:
+        ad.log.info("Triggering modem SSR crash by MDS successfully.")
+    else:
+        raise signals.TestError(
+            "Failed to trigger modem SSR crash by MDS. \n%s" % output)
+
+
+def check_xtra_download(ad, begin_time):
+    """Verify XTRA download success log message in logcat.
+
+    Args:
+        ad: An AndroidDevice object.
+        begin_time: test begin time
+
+    Returns:
+        True: xtra_download if XTRA downloaded and injected successfully
+        otherwise return False.
+    """
+    ad.send_keycode("HOME")
+    logcat_results = ad.search_logcat("XTRA download success. "
+                                      "inject data into modem", begin_time)
+    if logcat_results:
+        ad.log.debug("%s" % logcat_results[-1]["log_message"])
+        ad.log.info("XTRA downloaded and injected successfully.")
+        return True
+    ad.log.error("XTRA downloaded FAIL.")
+    return False
+
+
+def pull_package_apk(ad, package_name):
+    """Pull apk of given package_name from device.
+
+    Args:
+        ad: An AndroidDevice object.
+        package_name: Package name of apk to pull.
+
+    Returns:
+        The temp path of pulled apk.
+    """
+    apk_path = None
+    out = ad.adb.shell("pm path %s" % package_name)
+    result = re.search(r"package:(.*)", out)
+    if not result:
+        tutils.abort_all_tests(ad.log, "Couldn't find apk of %s" % package_name)
+    else:
+        apk_source = result.group(1)
+        ad.log.info("Get apk of %s from %s" % (package_name, apk_source))
+        apk_path = tempfile.mkdtemp()
+        ad.pull_files([apk_source], apk_path)
+    return apk_path
+
+
+def reinstall_package_apk(ad, package_name, apk_path):
+    """Reinstall apk of given package_name.
+
+    Args:
+        ad: An AndroidDevice object.
+        package_name: Package name of apk.
+        apk_path: The temp path of pulled apk.
+    """
+    for path_key in os.listdir(apk_path):
+        if fnmatch.fnmatch(path_key, "*.apk"):
+            apk_path = os.path.join(apk_path, path_key)
+            break
+    else:
+        raise signals.TestError("No apk is found in %s" % apk_path)
+    ad.log.info("Re-install %s with path: %s" % (package_name, apk_path))
+    ad.adb.shell("settings put global verifier_verify_adb_installs 0")
+    ad.adb.install("-r -d -g --user 0 %s" % apk_path)
+    package_check = ad.adb.shell("pm path %s" % package_name)
+    if not package_check:
+        tutils.abort_all_tests(
+            ad.log, "%s is not properly re-installed." % package_name)
+    ad.log.info("%s is re-installed successfully." % package_name)
+
+
+def init_gtw_gpstool(ad):
+    """Init GTW_GPSTool apk.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    remount_device(ad)
+    gpstool_path = pull_package_apk(ad, "com.android.gpstool")
+    reinstall_package_apk(ad, "com.android.gpstool", gpstool_path)
+
+
+def fastboot_factory_reset(ad):
+    """Factory reset the device in fastboot mode.
+       Pull sl4a apk from device. Terminate all sl4a sessions,
+       Reboot the device to bootloader,
+       factory reset the device by fastboot.
+       Reboot the device. wait for device to complete booting
+       Re-install and start an sl4a session.
+
+    Args:
+        ad: An AndroidDevice object.
+
+    Returns:
+        True if factory reset process complete.
+    """
+    status = True
+    skip_setup_wizard = True
+    sl4a_path = pull_package_apk(ad, SL4A_APK_NAME)
+    gpstool_path = pull_package_apk(ad, "com.android.gpstool")
+    mds_path = pull_package_apk(ad, "com.google.mdstest")
+    tutils.stop_qxdm_logger(ad)
+    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("Factory reset 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
+            reinstall_package_apk(ad, SL4A_APK_NAME, sl4a_path)
+            reinstall_package_apk(ad, "com.android.gpstool", gpstool_path)
+            reinstall_package_apk(ad, "com.google.mdstest", mds_path)
+            time.sleep(10)
+            break
+        except Exception as e:
+            ad.log.error(e)
+            if i == attempts:
+                tutils.abort_all_tests(ad.log, str(e))
+            time.sleep(5)
+    try:
+        ad.start_adb_logcat()
+    except Exception as e:
+        ad.log.error(e)
+    if skip_setup_wizard:
+        ad.exit_setup_wizard()
+    if ad.skip_sl4a:
+        return status
+    tutils.bring_up_sl4a(ad)
+    return status
+
+
+def clear_aiding_data_by_gtw_gpstool(ad):
+    """Launch GTW GPSTool and Clear all GNSS aiding data.
+       Wait 5 seconds for GTW GPStool to clear all GNSS aiding
+       data properly.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    ad.log.info("Launch GTW GPSTool and Clear all GNSS aiding data")
+    ad.adb.shell("am start -S -n com.android.gpstool/.GPSTool --es mode clear")
+    time.sleep(10)
+
+
+def start_gnss_by_gtw_gpstool(ad,
+                              state,
+                              type="gnss",
+                              bgdisplay=False,
+                              freq=0,
+                              lowpower=False,
+                              meas=False):
+    """Start or stop GNSS on GTW_GPSTool.
+
+    Args:
+        ad: An AndroidDevice object.
+        state: True to start GNSS. False to Stop GNSS.
+        type: Different API for location fix. Use gnss/flp/nmea
+        bgdisplay: true to run GTW when Display off. false to not run GTW when
+          Display off.
+        freq: An integer to set location update frequency.
+        meas: A Boolean to set GNSS measurement registration.
+        lowpower: A boolean to set GNSS LowPowerMode.
+    """
+    cmd = "am start -S -n com.android.gpstool/.GPSTool --es mode gps"
+    if not state:
+        ad.log.info("Stop %s on GTW_GPSTool." % type)
+        cmd = "am broadcast -a com.android.gpstool.stop_gps_action"
+    else:
+        options = ("--es type {} --ei freq {} --ez BG {} --ez meas {} --ez "
+                   "lowpower {}").format(type, freq, bgdisplay, meas, lowpower)
+        cmd = cmd + " " + options
+    ad.adb.shell(cmd)
+    time.sleep(3)
+
+
+def process_gnss_by_gtw_gpstool(ad,
+                                criteria,
+                                type="gnss",
+                                clear_data=True,
+                                meas_flag=False):
+    """Launch GTW GPSTool and Clear all GNSS aiding data
+       Start GNSS tracking on GTW_GPSTool.
+
+    Args:
+        ad: An AndroidDevice object.
+        criteria: Criteria for current test item.
+        type: Different API for location fix. Use gnss/flp/nmea
+        clear_data: True to clear GNSS aiding data. False is not to. Default
+        set to True.
+        meas_flag: True to enable GnssMeasurement. False is not to. Default
+        set to False.
+
+    Returns:
+        True: First fix TTFF are within criteria.
+        False: First fix TTFF exceed criteria.
+    """
+    retries = 3
+    for i in range(retries):
+        if not ad.is_adb_logcat_on:
+            ad.start_adb_logcat()
+        check_adblog_functionality(ad)
+        check_location_runtime_permissions(
+            ad, GNSSTOOL_PACKAGE_NAME, GNSSTOOL_PERMISSIONS)
+        begin_time = get_current_epoch_time()
+        if clear_data:
+            clear_aiding_data_by_gtw_gpstool(ad)
+        ad.log.info("Start %s on GTW_GPSTool - attempt %d" % (type.upper(),
+                                                              i+1))
+        start_gnss_by_gtw_gpstool(ad, state=True, type=type, meas=meas_flag)
+        for _ in range(10 + criteria):
+            logcat_results = ad.search_logcat("First fixed", begin_time)
+            if logcat_results:
+                ad.log.debug(logcat_results[-1]["log_message"])
+                first_fixed = int(logcat_results[-1]["log_message"].split()[-1])
+                ad.log.info("%s First fixed = %.3f seconds" %
+                            (type.upper(), first_fixed/1000))
+                if (first_fixed/1000) <= criteria:
+                    return True
+                start_gnss_by_gtw_gpstool(ad, state=False, type=type)
+                raise signals.TestFailure("Fail to get %s location fixed "
+                                          "within %d seconds criteria."
+                                          % (type.upper(), criteria))
+            time.sleep(1)
+        check_current_focus_app(ad)
+        start_gnss_by_gtw_gpstool(ad, state=False, type=type)
+    raise signals.TestFailure("Fail to get %s location fixed within %d "
+                              "attempts." % (type.upper(), retries))
+
+def start_ttff_by_gtw_gpstool(ad, ttff_mode, iteration, aid_data=False):
+    """Identify which TTFF mode for different test items.
+
+    Args:
+        ad: An AndroidDevice object.
+        ttff_mode: TTFF Test mode for current test item.
+        iteration: Iteration of TTFF cycles.
+        aid_data: Boolean for identify aid_data existed or not
+    """
+    begin_time = get_current_epoch_time()
+    if (ttff_mode == "hs" or ttff_mode == "ws") and not aid_data:
+        ad.log.info("Wait 5 minutes to start TTFF %s..." % ttff_mode.upper())
+        time.sleep(300)
+    if ttff_mode == "cs":
+        ad.log.info("Start TTFF Cold Start...")
+        time.sleep(3)
+    for i in range(1, 4):
+        ad.adb.shell("am broadcast -a com.android.gpstool.ttff_action "
+                     "--es ttff %s --es cycle %d" % (ttff_mode, iteration))
+        time.sleep(1)
+        if ad.search_logcat("act=com.android.gpstool.start_test_action",
+                            begin_time):
+            ad.log.info("Send TTFF start_test_action successfully.")
+            break
+    else:
+        check_current_focus_app(ad)
+        raise signals.TestError("Fail to send TTFF start_test_action.")
+
+
+def gnss_tracking_via_gtw_gpstool(ad,
+                                  criteria,
+                                  type="gnss",
+                                  testtime=60,
+                                  meas_flag=False):
+    """Start GNSS/FLP tracking tests for input testtime on GTW_GPSTool.
+
+    Args:
+        ad: An AndroidDevice object.
+        criteria: Criteria for current TTFF.
+        type: Different API for location fix. Use gnss/flp/nmea
+        testtime: Tracking test time for minutes. Default set to 60 minutes.
+        meas_flag: True to enable GnssMeasurement. False is not to. Default
+        set to False.
+    """
+    process_gnss_by_gtw_gpstool(
+        ad, criteria=criteria, type=type, meas_flag=meas_flag)
+    ad.log.info("Start %s tracking test for %d minutes" % (type.upper(),
+                                                           testtime))
+    begin_time = get_current_epoch_time()
+    while get_current_epoch_time() - begin_time < testtime * 60 * 1000:
+        if not ad.is_adb_logcat_on:
+            ad.start_adb_logcat()
+        crash_result = ad.search_logcat("Force finishing activity "
+                                        "com.android.gpstool/.GPSTool",
+                                        begin_time)
+        if crash_result:
+            raise signals.TestError("GPSTool crashed. Abort test.")
+    ad.log.info("Successfully tested for %d minutes" % testtime)
+    start_gnss_by_gtw_gpstool(ad, state=False, type=type)
+
+
+def parse_gtw_gpstool_log(ad, true_position, type="gnss"):
+    """Process GNSS/FLP API logs from GTW GPSTool and output track_data to
+    test_run_info for ACTS plugin to parse and display on MobileHarness as
+    Property.
+
+    Args:
+        ad: An AndroidDevice object.
+        true_position: Coordinate as [latitude, longitude] to calculate
+        position error.
+        type: Different API for location fix. Use gnss/flp/nmea
+    """
+    test_logfile = {}
+    track_data = {}
+    ant_top4_cn = 0
+    ant_cn = 0
+    base_top4_cn = 0
+    base_cn = 0
+    track_lat = 0
+    track_long = 0
+    l5flag = "false"
+    file_count = int(ad.adb.shell("find %s -type f -iname *.txt | wc -l"
+                                  % GNSSSTATUS_LOG_PATH))
+    if file_count != 1:
+        ad.log.error("%d API logs exist." % file_count)
+    dir_file = ad.adb.shell("ls %s" % GNSSSTATUS_LOG_PATH).split()
+    for path_key in dir_file:
+        if fnmatch.fnmatch(path_key, "*.txt"):
+            logpath = posixpath.join(GNSSSTATUS_LOG_PATH, path_key)
+            out = ad.adb.shell("wc -c %s" % logpath)
+            file_size = int(out.split(" ")[0])
+            if file_size < 2000:
+                ad.log.info("Skip log %s due to log size %d bytes" %
+                            (path_key, file_size))
+                continue
+            test_logfile = logpath
+    if not test_logfile:
+        raise signals.TestError("Failed to get test log file in device.")
+    lines = ad.adb.shell("cat %s" % test_logfile).split("\n")
+    for line in lines:
+        if "Antenna_History Avg Top4" in line:
+            ant_top4_cn = float(line.split(":")[-1].strip())
+        elif "Antenna_History Avg" in line:
+            ant_cn = float(line.split(":")[-1].strip())
+        elif "Baseband_History Avg Top4" in line:
+            base_top4_cn = float(line.split(":")[-1].strip())
+        elif "Baseband_History Avg" in line:
+            base_cn = float(line.split(":")[-1].strip())
+        elif "L5 used in fix" in line:
+            l5flag = line.split(":")[-1].strip()
+        elif "Latitude" in line:
+            track_lat = float(line.split(":")[-1].strip())
+        elif "Longitude" in line:
+            track_long = float(line.split(":")[-1].strip())
+        elif "Time" in line:
+            track_utc = line.split("Time:")[-1].strip()
+            if track_utc in track_data.keys():
+                continue
+            pe = calculate_position_error(track_lat, track_long, true_position)
+            track_data[track_utc] = TRACK_REPORT(l5flag=l5flag,
+                                                 pe=pe,
+                                                 ant_top4cn=ant_top4_cn,
+                                                 ant_cn=ant_cn,
+                                                 base_top4cn=base_top4_cn,
+                                                 base_cn=base_cn)
+    ad.log.debug(track_data)
+    prop_basename = "TestResult %s_tracking_" % type.upper()
+    time_list = sorted(track_data.keys())
+    l5flag_list = [track_data[key].l5flag for key in time_list]
+    pe_list = [float(track_data[key].pe) for key in time_list]
+    ant_top4cn_list = [float(track_data[key].ant_top4cn) for key in time_list]
+    ant_cn_list = [float(track_data[key].ant_cn) for key in time_list]
+    base_top4cn_list = [float(track_data[key].base_top4cn) for key in time_list]
+    base_cn_list = [float(track_data[key].base_cn) for key in time_list]
+    ad.log.info(prop_basename+"StartTime %s" % time_list[0].replace(" ", "-"))
+    ad.log.info(prop_basename+"EndTime %s" % time_list[-1].replace(" ", "-"))
+    ad.log.info(prop_basename+"TotalFixPoints %d" % len(time_list))
+    ad.log.info(prop_basename+"L5FixRate "+'{percent:.2%}'.format(
+        percent=l5flag_list.count("true")/len(l5flag_list)))
+    ad.log.info(prop_basename+"AvgDis %.1f" % (sum(pe_list)/len(pe_list)))
+    ad.log.info(prop_basename+"MaxDis %.1f" % max(pe_list))
+    ad.log.info(prop_basename+"Ant_AvgTop4Signal %.1f" % ant_top4cn_list[-1])
+    ad.log.info(prop_basename+"Ant_AvgSignal %.1f" % ant_cn_list[-1])
+    ad.log.info(prop_basename+"Base_AvgTop4Signal %.1f" % base_top4cn_list[-1])
+    ad.log.info(prop_basename+"Base_AvgSignal %.1f" % base_cn_list[-1])
+
+
+def process_ttff_by_gtw_gpstool(ad, begin_time, true_position, type="gnss"):
+    """Process TTFF and record results in ttff_data.
+
+    Args:
+        ad: An AndroidDevice object.
+        begin_time: test begin time.
+        true_position: Coordinate as [latitude, longitude] to calculate
+        position error.
+        type: Different API for location fix. Use gnss/flp/nmea
+
+    Returns:
+        ttff_data: A dict of all TTFF data.
+    """
+    ttff_lat = 0
+    ttff_lon = 0
+    utc_time = epoch_to_human_time(get_current_epoch_time())
+    ttff_data = {}
+    ttff_loop_time = get_current_epoch_time()
+    while True:
+        if get_current_epoch_time() - ttff_loop_time >= 120000:
+            raise signals.TestError("Fail to search specific GPSService "
+                                    "message in logcat. Abort test.")
+        if not ad.is_adb_logcat_on:
+            ad.start_adb_logcat()
+        logcat_results = ad.search_logcat("write TTFF log", ttff_loop_time)
+        if logcat_results:
+            ttff_loop_time = get_current_epoch_time()
+            ttff_log = logcat_results[-1]["log_message"].split()
+            ttff_loop = int(ttff_log[8].split(":")[-1])
+            ttff_sec = float(ttff_log[11])
+            if ttff_sec != 0.0:
+                ttff_ant_cn = float(ttff_log[18].strip("]"))
+                ttff_base_cn = float(ttff_log[25].strip("]"))
+                if type == "gnss":
+                    gnss_results = ad.search_logcat("GPSService: Check item",
+                                                    begin_time)
+                    if gnss_results:
+                        ad.log.debug(gnss_results[-1]["log_message"])
+                        gnss_location_log = \
+                            gnss_results[-1]["log_message"].split()
+                        ttff_lat = float(
+                            gnss_location_log[8].split("=")[-1].strip(","))
+                        ttff_lon = float(
+                            gnss_location_log[9].split("=")[-1].strip(","))
+                        loc_time = int(
+                            gnss_location_log[10].split("=")[-1].strip(","))
+                        utc_time = epoch_to_human_time(loc_time)
+                elif type == "flp":
+                    flp_results = ad.search_logcat("GPSService: FLP Location",
+                                                   begin_time)
+                    if flp_results:
+                        ad.log.debug(flp_results[-1]["log_message"])
+                        flp_location_log = flp_results[-1][
+                            "log_message"].split()
+                        ttff_lat = float(flp_location_log[8].split(",")[0])
+                        ttff_lon = float(flp_location_log[8].split(",")[1])
+                        utc_time = epoch_to_human_time(get_current_epoch_time())
+            else:
+                ttff_ant_cn = float(ttff_log[19].strip("]"))
+                ttff_base_cn = float(ttff_log[26].strip("]"))
+                ttff_lat = 0
+                ttff_lon = 0
+                utc_time = epoch_to_human_time(get_current_epoch_time())
+            ad.log.debug("TTFF Loop %d - (Lat, Lon) = (%s, %s)" % (ttff_loop,
+                                                                   ttff_lat,
+                                                                   ttff_lon))
+            ttff_pe = calculate_position_error(
+                ttff_lat, ttff_lon, true_position)
+            ttff_data[ttff_loop] = TTFF_REPORT(utc_time=utc_time,
+                                               ttff_loop=ttff_loop,
+                                               ttff_sec=ttff_sec,
+                                               ttff_pe=ttff_pe,
+                                               ttff_ant_cn=ttff_ant_cn,
+                                               ttff_base_cn=ttff_base_cn)
+            ad.log.info("UTC Time = %s, Loop %d = %.1f seconds, "
+                        "Position Error = %.1f meters, "
+                        "Antenna Average Signal = %.1f dbHz, "
+                        "Baseband Average Signal = %.1f dbHz" % (utc_time,
+                                                                 ttff_loop,
+                                                                 ttff_sec,
+                                                                 ttff_pe,
+                                                                 ttff_ant_cn,
+                                                                 ttff_base_cn))
+        stop_gps_results = ad.search_logcat("stop gps test", begin_time)
+        if stop_gps_results:
+            ad.send_keycode("HOME")
+            break
+        crash_result = ad.search_logcat("Force finishing activity "
+                                        "com.android.gpstool/.GPSTool",
+                                        begin_time)
+        if crash_result:
+            raise signals.TestError("GPSTool crashed. Abort test.")
+        # wait 10 seconds to avoid logs not writing into logcat yet
+        time.sleep(10)
+    return ttff_data
+
+
+def check_ttff_data(ad, ttff_data, ttff_mode, criteria):
+    """Verify all TTFF results from ttff_data.
+
+    Args:
+        ad: An AndroidDevice object.
+        ttff_data: TTFF data of secs, position error and signal strength.
+        ttff_mode: TTFF Test mode for current test item.
+        criteria: Criteria for current test item.
+
+    Returns:
+        True: All TTFF results are within criteria.
+        False: One or more TTFF results exceed criteria or Timeout.
+    """
+    ad.log.info("%d iterations of TTFF %s tests finished."
+                % (len(ttff_data.keys()), ttff_mode))
+    ad.log.info("%s PASS criteria is %d seconds" % (ttff_mode, criteria))
+    ad.log.debug("%s TTFF data: %s" % (ttff_mode, ttff_data))
+    ttff_property_key_and_value(ad, ttff_data, ttff_mode)
+    if len(ttff_data.keys()) == 0:
+        ad.log.error("GTW_GPSTool didn't process TTFF properly.")
+        return False
+    elif any(float(ttff_data[key].ttff_sec) == 0.0 for key in ttff_data.keys()):
+        ad.log.error("One or more TTFF %s Timeout" % ttff_mode)
+        return False
+    elif any(float(ttff_data[key].ttff_sec) >= criteria for key in
+             ttff_data.keys()):
+        ad.log.error("One or more TTFF %s are over test criteria %d seconds"
+                     % (ttff_mode, criteria))
+        return False
+    ad.log.info("All TTFF %s are within test criteria %d seconds."
+                % (ttff_mode, criteria))
+    return True
+
+
+def ttff_property_key_and_value(ad, ttff_data, ttff_mode):
+    """Output ttff_data to test_run_info for ACTS plugin to parse and display
+    on MobileHarness as Property.
+
+    Args:
+        ad: An AndroidDevice object.
+        ttff_data: TTFF data of secs, position error and signal strength.
+        ttff_mode: TTFF Test mode for current test item.
+    """
+    prop_basename = "TestResult "+ttff_mode.replace(" ", "_")+"_TTFF_"
+    sec_list = [float(ttff_data[key].ttff_sec) for key in ttff_data.keys()]
+    pe_list = [float(ttff_data[key].ttff_pe) for key in ttff_data.keys()]
+    ant_cn_list = [float(ttff_data[key].ttff_ant_cn) for key in
+                   ttff_data.keys()]
+    base_cn_list = [float(ttff_data[key].ttff_base_cn) for key in
+                    ttff_data.keys()]
+    timeoutcount = sec_list.count(0.0)
+    if len(sec_list) == timeoutcount:
+        avgttff = 9527
+    else:
+        avgttff = sum(sec_list)/(len(sec_list) - timeoutcount)
+    if timeoutcount != 0:
+        maxttff = 9527
+    else:
+        maxttff = max(sec_list)
+    avgdis = sum(pe_list)/len(pe_list)
+    maxdis = max(pe_list)
+    ant_avgcn = sum(ant_cn_list)/len(ant_cn_list)
+    base_avgcn = sum(base_cn_list)/len(base_cn_list)
+    ad.log.info(prop_basename+"AvgTime %.1f" % avgttff)
+    ad.log.info(prop_basename+"MaxTime %.1f" % maxttff)
+    ad.log.info(prop_basename+"TimeoutCount %d" % timeoutcount)
+    ad.log.info(prop_basename+"AvgDis %.1f" % avgdis)
+    ad.log.info(prop_basename+"MaxDis %.1f" % maxdis)
+    ad.log.info(prop_basename+"Ant_AvgSignal %.1f" % ant_avgcn)
+    ad.log.info(prop_basename+"Base_AvgSignal %.1f" % base_avgcn)
+
+
+def calculate_position_error(latitude, longitude, true_position):
+    """Use haversine formula to calculate position error base on true location
+    coordinate.
+
+    Args:
+        latitude: latitude of location fixed in the present.
+        longitude: longitude of location fixed in the present.
+        true_position: [latitude, longitude] of true location coordinate.
+
+    Returns:
+        position_error of location fixed in the present.
+    """
+    radius = 6371009
+    dlat = math.radians(latitude - true_position[0])
+    dlon = math.radians(longitude - true_position[1])
+    a = math.sin(dlat/2) * math.sin(dlat/2) + \
+        math.cos(math.radians(true_position[0])) * \
+        math.cos(math.radians(latitude)) * math.sin(dlon/2) * math.sin(dlon/2)
+    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
+    return radius * c
+
+
+def launch_google_map(ad):
+    """Launch Google Map via intent.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    ad.log.info("Launch Google Map.")
+    try:
+        ad.adb.shell("am start -S -n com.google.android.apps.maps/"
+                     "com.google.android.maps.MapsActivity")
+        ad.send_keycode("BACK")
+        ad.force_stop_apk("com.google.android.apps.maps")
+        ad.adb.shell("am start -S -n com.google.android.apps.maps/"
+                     "com.google.android.maps.MapsActivity")
+    except Exception as e:
+        ad.log.error(e)
+        raise signals.TestError("Failed to launch google map.")
+    check_current_focus_app(ad)
+
+
+def check_current_focus_app(ad):
+    """Check to see current focused window and app.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    time.sleep(1)
+    current = ad.adb.shell(
+        "dumpsys window | grep -E 'mCurrentFocus|mFocusedApp'")
+    ad.log.debug("\n"+current)
+
+
+def check_location_api(ad, retries):
+    """Verify if GnssLocationProvider API reports location.
+
+    Args:
+        ad: An AndroidDevice object.
+        retries: Retry time.
+
+    Returns:
+        True: GnssLocationProvider API reports location.
+        otherwise return False.
+    """
+    for i in range(retries):
+        begin_time = get_current_epoch_time()
+        ad.log.info("Try to get location report from GnssLocationProvider API "
+                    "- attempt %d" % (i+1))
+        while get_current_epoch_time() - begin_time <= 30000:
+            logcat_results = ad.search_logcat("REPORT_LOCATION", begin_time)
+            if logcat_results:
+                ad.log.info("%s" % logcat_results[-1]["log_message"])
+                ad.log.info("GnssLocationProvider reports location "
+                            "successfully.")
+                return True
+        if not ad.is_adb_logcat_on:
+            ad.start_adb_logcat()
+    ad.log.error("GnssLocationProvider is unable to report location.")
+    return False
+
+def check_network_location(ad, retries, location_type, criteria=30):
+    """Verify if NLP reports location after requesting via GPSTool.
+
+    Args:
+        ad: An AndroidDevice object.
+        retries: Retry time.
+        location_type: cell or wifi.
+        criteria: expected nlp return time, default 30 seconds
+
+    Returns:
+        True: NLP reports location.
+        otherwise return False.
+    """
+    criteria = criteria * 1000
+    search_pattern = ("GPSTool : networkLocationType = %s" % location_type)
+    for i in range(retries):
+        begin_time = get_current_epoch_time()
+        ad.log.info("Try to get NLP status - attempt %d" % (i+1))
+        ad.adb.shell(
+            "am start -S -n com.android.gpstool/.GPSTool --es mode nlp")
+        while get_current_epoch_time() - begin_time <= criteria:
+            # Search pattern in 1 second interval
+            time.sleep(1)
+            result = ad.search_logcat(search_pattern, begin_time)
+            if result:
+                ad.log.info("Pattern Found: %s." % result[-1]["log_message"])
+                ad.send_keycode("BACK")
+                return True
+        if not ad.is_adb_logcat_on:
+            ad.start_adb_logcat()
+        ad.send_keycode("BACK")
+    ad.log.error("Unable to report network location \"%s\"." % location_type)
+    return False
+
+
+def set_attenuator_gnss_signal(ad, attenuator, atten_value):
+    """Set attenuation value for different GNSS signal.
+
+    Args:
+        ad: An AndroidDevice object.
+        attenuator: The attenuator object.
+        atten_value: attenuation value
+    """
+    try:
+        ad.log.info(
+            "Set attenuation value to \"%d\" for GNSS signal." % atten_value)
+        attenuator[0].set_atten(atten_value)
+    except Exception as e:
+        ad.log.error(e)
+
+
+def set_battery_saver_mode(ad, state):
+    """Enable or disable battery saver mode via adb.
+
+    Args:
+        ad: An AndroidDevice object.
+        state: True is enable Battery Saver mode. False is disable.
+    """
+    ad.root_adb()
+    if state:
+        ad.log.info("Enable Battery Saver mode.")
+        ad.adb.shell("cmd battery unplug")
+        ad.adb.shell("settings put global low_power 1")
+    else:
+        ad.log.info("Disable Battery Saver mode.")
+        ad.adb.shell("settings put global low_power 0")
+        ad.adb.shell("cmd battery reset")
+
+
+def set_gnss_qxdm_mask(ad, masks):
+    """Find defined gnss qxdm mask and set as default logging mask.
+
+    Args:
+        ad: An AndroidDevice object.
+        masks: Defined gnss qxdm mask.
+    """
+    try:
+        for mask in masks:
+            if not tutils.find_qxdm_log_mask(ad, mask):
+                continue
+            tutils.set_qxdm_logger_command(ad, mask)
+            break
+    except Exception as e:
+        ad.log.error(e)
+        raise signals.TestError("Failed to set any QXDM masks.")
+
+
+def start_youtube_video(ad, url=None, retries=0):
+    """Start youtube video and verify if audio is in music state.
+
+    Args:
+        ad: An AndroidDevice object.
+        url: Youtube video url.
+        retries: Retry times if audio is not in music state.
+
+    Returns:
+        True if youtube video is playing normally.
+        False if youtube video is not playing properly.
+    """
+    for i in range(retries):
+        ad.log.info("Open an youtube video - attempt %d" % (i+1))
+        ad.adb.shell("am start -a android.intent.action.VIEW -d \"%s\"" % url)
+        time.sleep(2)
+        out = ad.adb.shell(
+            "dumpsys activity | grep NewVersionAvailableActivity")
+        if out:
+            ad.log.info("Skip Youtube New Version Update.")
+            ad.send_keycode("BACK")
+        if tutils.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("Force-Stop youtube and reopen youtube again.")
+        ad.force_stop_apk("com.google.android.youtube")
+    check_current_focus_app(ad)
+    raise signals.TestError("Started a video in youtube, "
+                            "but audio is not in MUSIC state")
+
+
+def get_baseband_and_gms_version(ad, extra_msg=""):
+    """Get current radio baseband and GMSCore version of AndroidDevice object.
+
+    Args:
+        ad: An AndroidDevice object.
+        extra_msg: Extra message before or after the change.
+    """
+    try:
+        build_version = ad.adb.getprop("ro.build.id")
+        baseband_version = ad.adb.getprop("gsm.version.baseband")
+        gms_version = ad.adb.shell(
+            "dumpsys package com.google.android.gms | grep versionName"
+        ).split("\n")[0].split("=")[1]
+        mpss_version = ad.adb.shell("cat /sys/devices/soc0/images | grep MPSS "
+                                    "| cut -d ':' -f 3")
+        if not extra_msg:
+            ad.log.info("TestResult Build_Version %s" % build_version)
+            ad.log.info("TestResult Baseband_Version %s" % baseband_version)
+            ad.log.info(
+                "TestResult GMS_Version %s" % gms_version.replace(" ", ""))
+            ad.log.info("TestResult MPSS_Version %s" % mpss_version)
+        else:
+            ad.log.info(
+                "%s, Baseband_Version = %s" % (extra_msg, baseband_version))
+    except Exception as e:
+        ad.log.error(e)
+
+
+def start_toggle_gnss_by_gtw_gpstool(ad, iteration):
+    """Send toggle gnss off/on start_test_action
+
+    Args:
+        ad: An AndroidDevice object.
+        iteration: Iteration of toggle gnss off/on cycles.
+    """
+    msg_list = []
+    begin_time = get_current_epoch_time()
+    try:
+        for i in range(1, 4):
+            ad.adb.shell("am start -S -n com.android.gpstool/.GPSTool "
+                         "--es mode toggle --es cycle %d" % iteration)
+            time.sleep(1)
+            if ad.search_logcat("cmp=com.android.gpstool/.ToggleGPS",
+                                begin_time):
+                ad.log.info("Send ToggleGPS start_test_action successfully.")
+                break
+        else:
+            check_current_focus_app(ad)
+            raise signals.TestError("Fail to send ToggleGPS "
+                                    "start_test_action within 3 attempts.")
+        time.sleep(2)
+        test_start = ad.search_logcat("GPSTool_ToggleGPS: startService",
+                                      begin_time)
+        if test_start:
+            ad.log.info(test_start[-1]["log_message"].split(":")[-1].strip())
+        else:
+            raise signals.TestError("Fail to start toggle GPS off/on test.")
+        # Every iteration is expected to finish within 4 minutes.
+        while get_current_epoch_time() - begin_time <= iteration * 240000:
+            crash_end = ad.search_logcat("Force finishing activity "
+                                         "com.android.gpstool/.GPSTool",
+                                         begin_time)
+            if crash_end:
+                raise signals.TestError("GPSTool crashed. Abort test.")
+            toggle_results = ad.search_logcat("GPSTool : msg", begin_time)
+            if toggle_results:
+                for toggle_result in toggle_results:
+                    msg = toggle_result["log_message"]
+                    if not msg in msg_list:
+                        ad.log.info(msg.split(":")[-1].strip())
+                        msg_list.append(msg)
+                    if "timeout" in msg:
+                        raise signals.TestFailure("Fail to get location fixed "
+                                                  "within 60 seconds.")
+                    if "Test end" in msg:
+                        raise signals.TestPass("Completed quick toggle GNSS "
+                                               "off/on test.")
+        raise signals.TestFailure("Fail to finish toggle GPS off/on test "
+                                  "within %d minutes" % (iteration * 4))
+    finally:
+        ad.send_keycode("HOME")
+
+
+def grant_location_permission(ad, option):
+    """Grant or revoke location related permission.
+
+    Args:
+        ad: An AndroidDevice object.
+        option: Boolean to grant or revoke location related permissions.
+    """
+    action = "grant" if option else "revoke"
+    for permission in LOCATION_PERMISSIONS:
+        ad.log.info(
+            "%s permission:%s on %s" % (action, permission, TEST_PACKAGE_NAME))
+        ad.adb.shell("pm %s %s %s" % (action, TEST_PACKAGE_NAME, permission))
+
+
+def check_location_runtime_permissions(ad, package, permissions):
+    """Check if runtime permissions are granted on selected package.
+
+    Args:
+        ad: An AndroidDevice object.
+        package: Apk package name to check.
+        permissions: A list of permissions to be granted.
+    """
+    for _ in range(3):
+        location_runtime_permission = ad.adb.shell(
+            "dumpsys package %s | grep ACCESS_FINE_LOCATION" % package)
+        if "true" not in location_runtime_permission:
+            ad.log.info("ACCESS_FINE_LOCATION is NOT granted on %s" % package)
+            for permission in permissions:
+                ad.log.debug("Grant %s on %s" % (permission, package))
+                ad.adb.shell("pm grant %s %s" % (package, permission))
+        else:
+            ad.log.info("ACCESS_FINE_LOCATION is granted on %s" % package)
+            break
+    else:
+        raise signals.TestError(
+            "Fail to grant ACCESS_FINE_LOCATION on %s" % package)
+
+
+def install_mdstest_app(ad, mdsapp):
+    """
+        Install MDS test app in DUT
+
+        Args:
+            ad: An Android Device Object
+            mdsapp: Installation path of MDSTest app
+    """
+    if not ad.is_apk_installed("com.google.mdstest"):
+        ad.adb.install("-r %s" % mdsapp, timeout=300, ignore_status=True)
+
+
+def write_modemconfig(ad, mdsapp, nvitem_dict, modemparfile):
+    """
+        Modify the NV items using modem_tool.par
+        Note: modem_tool.par
+
+        Args:
+            ad:  An Android Device Object
+            mdsapp: Installation path of MDSTest app
+            nvitem_dict: dictionary of NV items and values.
+            modemparfile: modem_tool.par path.
+    """
+    ad.log.info("Verify MDSTest app installed in DUT")
+    install_mdstest_app(ad, mdsapp)
+    os.system("chmod 777 %s" % modemparfile)
+    for key, value in nvitem_dict.items():
+        if key.isdigit():
+            op_name = "WriteEFS"
+        else:
+            op_name = "WriteNV"
+        ad.log.info("Modifying the NV{!r} using {}".format(key, op_name))
+        job.run("{} --op {} --item {} --data '{}'".
+                format(modemparfile, op_name, key, value))
+        time.sleep(2)
+
+
+def verify_modemconfig(ad, nvitem_dict, modemparfile):
+    """
+        Verify the NV items using modem_tool.par
+        Note: modem_tool.par
+
+        Args:
+            ad:  An Android Device Object
+            nvitem_dict: dictionary of NV items and values
+            modemparfile: modem_tool.par path.
+    """
+    os.system("chmod 777 %s" % modemparfile)
+    for key, value in nvitem_dict.items():
+        if key.isdigit():
+            op_name = "ReadEFS"
+        else:
+            op_name = "ReadNV"
+        # Sleeptime to avoid Modem communication error
+        time.sleep(5)
+        result = job.run(
+            "{} --op {} --item {}".format(modemparfile, op_name, key))
+        output = str(result.stdout)
+        ad.log.info("Actual Value for NV{!r} is {!r}".format(key, output))
+        if not value.casefold() in output:
+            ad.log.error("NV Value is wrong {!r} in {!r}".format(value, result))
+            raise ValueError(
+                "could not find {!r} in {!r}".format(value, result))
+
+
+def check_ttff_pe(ad, ttff_data, ttff_mode, pe_criteria):
+    """Verify all TTFF results from ttff_data.
+
+    Args:
+        ad: An AndroidDevice object.
+        ttff_data: TTFF data of secs, position error and signal strength.
+        ttff_mode: TTFF Test mode for current test item.
+        pe_criteria: Criteria for current test item.
+
+    """
+    ad.log.info("%d iterations of TTFF %s tests finished.",
+                (len(ttff_data.keys()), ttff_mode))
+    ad.log.info("%s PASS criteria is %f meters", (ttff_mode, pe_criteria))
+    ad.log.debug("%s TTFF data: %s", (ttff_mode, ttff_data))
+
+    if len(ttff_data.keys()) == 0:
+        ad.log.error("GTW_GPSTool didn't process TTFF properly.")
+        raise signals.TestFailure("GTW_GPSTool didn't process TTFF properly.")
+
+    elif any(float(ttff_data[key].ttff_pe) >= pe_criteria for key in
+             ttff_data.keys()):
+        ad.log.error("One or more TTFF %s are over test criteria %f meters",
+                     (ttff_mode, pe_criteria))
+        raise signals.TestFailure("GTW_GPSTool didn't process TTFF properly.")
+    ad.log.info("All TTFF %s are within test criteria %f meters.",
+                (ttff_mode, pe_criteria))
+
+
+def check_adblog_functionality(ad):
+    """Restart adb logcat if system can't write logs into file after checking
+    adblog file size.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    logcat_path = os.path.join(ad.device_log_path, "adblog_%s_debug.txt" %
+                               ad.serial)
+    if not os.path.exists(logcat_path):
+        raise signals.TestError("Logcat file %s does not exist." % logcat_path)
+    original_log_size = os.path.getsize(logcat_path)
+    ad.log.debug("Original adblog size is %d" % original_log_size)
+    time.sleep(.5)
+    current_log_size = os.path.getsize(logcat_path)
+    ad.log.debug("Current adblog size is %d" % current_log_size)
+    if current_log_size == original_log_size:
+        ad.log.warn("System can't write logs into file. Restart adb "
+                    "logcat process now.")
+        ad.stop_adb_logcat()
+        ad.start_adb_logcat()
+
+def build_instrumentation_call(package,
+                               runner,
+                               test_methods=None,
+                               options=None):
+    """Build an instrumentation call for the tests
+
+    Args:
+        package: A string to identify test package.
+        runner: A string to identify test runner.
+        test_methods: A dictionary contains {class_name, test_method}.
+        options: A dictionary constant {key, value} param for test.
+
+    Returns:
+        An instrumentation call command.
+    """
+    if test_methods is None:
+        test_methods = {}
+        cmd_builder = InstrumentationCommandBuilder()
+    else:
+        cmd_builder = InstrumentationTestCommandBuilder()
+    if options is None:
+        options = {}
+    cmd_builder.set_manifest_package(package)
+    cmd_builder.set_runner(runner)
+    cmd_builder.add_flag("-w")
+    for class_name, test_method in test_methods.items():
+        cmd_builder.add_test_method(class_name, test_method)
+    for option_key, option_value in options.items():
+        cmd_builder.add_key_value_param(option_key, option_value)
+    return cmd_builder.build()
diff --git a/acts_tests/acts_contrib/test_utils/gnss/gnss_testlog_utils.py b/acts_tests/acts_contrib/test_utils/gnss/gnss_testlog_utils.py
new file mode 100644
index 0000000..54d47d7
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/gnss_testlog_utils.py
@@ -0,0 +1,465 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+'''Python Module for GNSS test log utilities.'''
+
+import re as regex
+import datetime
+import functools as fts
+import numpy as npy
+import pandas as pds
+from acts import logger
+
+# GPS API Log Reading Config
+CONFIG_GPSAPILOG = {
+    'phone_time':
+    r'^(?P<date>\d+\/\d+\/\d+)\s+(?P<time>\d+:\d+:\d+)\s+'
+    r'Read:\s+(?P<logsize>\d+)\s+bytes',
+    'SpaceVehicle':
+    r'^Fix:\s+(?P<Fix>\w+)\s+Type:\s+(?P<Type>\w+)\s+'
+    r'SV:\s+(?P<SV>\d+)\s+C\/No:\s+(?P<CNo>\d+\.\d+)\s+'
+    r'Elevation:\s+(?P<Elevation>\d+\.\d+)\s+'
+    r'Azimuth:\s+(?P<Azimuth>\d+\.\d+)\s+'
+    r'Signal:\s+(?P<Signal>\w+)\s+'
+    r'Frequency:\s+(?P<Frequency>\d+\.\d+)\s+'
+    r'EPH:\s+(?P<EPH>\w+)\s+ALM:\s+(?P<ALM>\w+)',
+    'SpaceVehicle_wBB':
+    r'^Fix:\s+(?P<Fix>\w+)\s+Type:\s+(?P<Type>\w+)\s+'
+    r'SV:\s+(?P<SV>\d+)\s+C\/No:\s+(?P<AntCNo>\d+\.\d+),\s+'
+    r'(?P<BbCNo>\d+\.\d+)\s+'
+    r'Elevation:\s+(?P<Elevation>\d+\.\d+)\s+'
+    r'Azimuth:\s+(?P<Azimuth>\d+\.\d+)\s+'
+    r'Signal:\s+(?P<Signal>\w+)\s+'
+    r'Frequency:\s+(?P<Frequency>\d+\.\d+)\s+'
+    r'EPH:\s+(?P<EPH>\w+)\s+ALM:\s+(?P<ALM>\w+)',
+    'HistoryAvgTop4CNo':
+    r'^History\s+Avg\s+Top4\s+:\s+(?P<HistoryAvgTop4CNo>\d+\.\d+)',
+    'CurrentAvgTop4CNo':
+    r'^Current\s+Avg\s+Top4\s+:\s+(?P<CurrentAvgTop4CNo>\d+\.\d+)',
+    'HistoryAvgCNo':
+    r'^History\s+Avg\s+:\s+(?P<HistoryAvgCNo>\d+\.\d+)',
+    'CurrentAvgCNo':
+    r'^Current\s+Avg\s+:\s+(?P<CurrentAvgCNo>\d+\.\d+)',
+    'AntennaHistoryAvgTop4CNo':
+    r'^Antenna_History\s+Avg\s+Top4\s+:\s+(?P<AntennaHistoryAvgTop4CNo>\d+\.\d+)',
+    'AntennaCurrentAvgTop4CNo':
+    r'^Antenna_Current\s+Avg\s+Top4\s+:\s+(?P<AntennaCurrentAvgTop4CNo>\d+\.\d+)',
+    'AntennaHistoryAvgCNo':
+    r'^Antenna_History\s+Avg\s+:\s+(?P<AntennaHistoryAvgCNo>\d+\.\d+)',
+    'AntennaCurrentAvgCNo':
+    r'^Antenna_Current\s+Avg\s+:\s+(?P<AntennaCurrentAvgCNo>\d+\.\d+)',
+    'BasebandHistoryAvgTop4CNo':
+    r'^Baseband_History\s+Avg\s+Top4\s+:\s+(?P<BasebandHistoryAvgTop4CNo>\d+\.\d+)',
+    'BasebandCurrentAvgTop4CNo':
+    r'^Baseband_Current\s+Avg\s+Top4\s+:\s+(?P<BasebandCurrentAvgTop4CNo>\d+\.\d+)',
+    'BasebandHistoryAvgCNo':
+    r'^Baseband_History\s+Avg\s+:\s+(?P<BasebandHistoryAvgCNo>\d+\.\d+)',
+    'BasebandCurrentAvgCNo':
+    r'^Baseband_Current\s+Avg\s+:\s+(?P<BasebandCurrentAvgCNo>\d+\.\d+)',
+    'L5inFix':
+    r'^L5\s+used\s+in\s+fix:\s+(?P<L5inFix>\w+)',
+    'L5EngagingRate':
+    r'^L5\s+engaging\s+rate:\s+(?P<L5EngagingRate>\d+.\d+)%',
+    'Provider':
+    r'^Provider:\s+(?P<Provider>\w+)',
+    'Latitude':
+    r'^Latitude:\s+(?P<Latitude>-?\d+.\d+)',
+    'Longitude':
+    r'^Longitude:\s+(?P<Longitude>-?\d+.\d+)',
+    'Altitude':
+    r'^Altitude:\s+(?P<Altitude>-?\d+.\d+)',
+    'GNSSTime':
+    r'^Time:\s+(?P<Date>\d+\/\d+\/\d+)\s+'
+    r'(?P<Time>\d+:\d+:\d+)',
+    'Speed':
+    r'^Speed:\s+(?P<Speed>\d+.\d+)',
+    'Bearing':
+    r'^Bearing:\s+(?P<Bearing>\d+.\d+)',
+}
+
+# Space Vehicle Statistics Dataframe List
+# Handle the pre GPSTool 2.12.24 case
+LIST_SVSTAT = [
+    'HistoryAvgTop4CNo', 'CurrentAvgTop4CNo', 'HistoryAvgCNo', 'CurrentAvgCNo',
+    'L5inFix', 'L5EngagingRate'
+]
+# Handle the post GPSTool 2.12.24 case with baseband CNo
+LIST_SVSTAT_WBB = [
+    'AntennaHistoryAvgTop4CNo', 'AntennaCurrentAvgTop4CNo',
+    'AntennaHistoryAvgCNo', 'AntennaCurrentAvgCNo',
+    'BasebandHistoryAvgTop4CNo', 'BasebandCurrentAvgTop4CNo',
+    'BasebandHistoryAvgCNo', 'BasebandCurrentAvgCNo', 'L5inFix',
+    'L5EngagingRate'
+]
+
+# Location Fix Info Dataframe List
+LIST_LOCINFO = [
+    'Provider', 'Latitude', 'Longitude', 'Altitude', 'GNSSTime', 'Speed',
+    'Bearing'
+]
+
+# GPS TTFF Log Reading Config
+CONFIG_GPSTTFFLOG = {
+    'ttff_info':
+    r'Loop:(?P<loop>\d+)\s+'
+    r'(?P<start_datetime>\d+\/\d+\/\d+-\d+:\d+:\d+.\d+)\s+'
+    r'(?P<stop_datetime>\d+\/\d+\/\d+-\d+:\d+:\d+.\d+)\s+'
+    r'(?P<ttff>\d+.\d+)\s+'
+    r'\[Antenna_Avg Top4 : (?P<ant_avg_top4_cn0>\d+.\d+)\]\s'
+    r'\[Antenna_Avg : (?P<ant_avg_cn0>\d+.\d+)\]\s'
+    r'\[Baseband_Avg Top4 : (?P<bb_avg_top4_cn0>\d+.\d+)\]\s'
+    r'\[Baseband_Avg : (?P<<bb_avg_cn0>\d+.\d+)\]\s+\[(?P<fix_type>\d+\w+ fix)\]\s+'
+    r'\[Satellites used for fix : (?P<satnum_for_fix>\d+)\]'
+}
+LOGPARSE_UTIL_LOGGER = logger.create_logger()
+
+
+def parse_log_to_df(filename, configs, index_rownum=True):
+    r"""Parse log to a dictionary of Pandas dataframes.
+
+    Args:
+      filename: log file name.
+        Type String.
+      configs: configs dictionary of parsed Pandas dataframes.
+        Type dictionary.
+        dict key, the parsed pattern name, such as 'Speed',
+        dict value, regex of the config pattern,
+          Type Raw String.
+      index_rownum: index row number from raw data.
+        Type Boolean.
+        Default, True.
+
+    Returns:
+      parsed_data: dictionary of parsed data.
+        Type dictionary.
+        dict key, the parsed pattern name, such as 'Speed',
+        dict value, the corresponding parsed dataframe.
+
+    Examples:
+      configs = {
+          'GNSSTime':
+          r'Time:\s+(?P<Date>\d+\/\d+\/\d+)\s+
+          r(?P<Time>\d+:\d+:\d+)')},
+          'Speed': r'Speed:\s+(?P<Speed>\d+.\d+)',
+      }
+    """
+    # Init a local config dictionary to hold compiled regex and match dict.
+    configs_local = {}
+    # Construct parsed data dictionary
+    parsed_data = {}
+
+    # Loop the config dictionary to compile regex and init data list
+    for key, regex_string in configs.items():
+        configs_local[key] = {
+            'cregex': regex.compile(regex_string),
+            'datalist': [],
+        }
+
+    # Open the file, loop and parse
+    with open(filename, 'r') as fid:
+
+        for idx_line, current_line in enumerate(fid):
+            for _, config in configs_local.items():
+                matched_log_object = config['cregex'].search(current_line)
+
+                if matched_log_object:
+                    matched_data = matched_log_object.groupdict()
+                    matched_data['rownumber'] = idx_line + 1
+                    config['datalist'].append(matched_data)
+
+    # Loop to generate parsed data from configs list
+    for key, config in configs_local.items():
+        parsed_data[key] = pds.DataFrame(config['datalist'])
+        if index_rownum and not parsed_data[key].empty:
+            parsed_data[key].set_index('rownumber', inplace=True)
+        elif parsed_data[key].empty:
+            LOGPARSE_UTIL_LOGGER.debug(
+                'The parsed dataframe of "%s" is empty.', key)
+
+    # Return parsed data list
+    return parsed_data
+
+
+def parse_gpstool_ttfflog_to_df(filename):
+    """Parse GPSTool ttff log to Pandas dataframes.
+
+    Args:
+      filename: full log file name.
+        Type, String.
+
+    Returns:
+      ttff_df: TTFF Data Frame.
+        Type, Pandas DataFrame.
+    """
+    # Get parsed dataframe list
+    parsed_data = parse_log_to_df(
+        filename=filename,
+        configs=CONFIG_GPSTTFFLOG,
+    )
+    ttff_df = parsed_data['ttff_info']
+
+    # Data Conversion
+    ttff_df['loop'] = ttff_df['loop'].astype(int)
+    ttff_df['start_datetime'] = pds.to_datetime(ttff_df['start_datetime'])
+    ttff_df['stop_datetime'] = pds.to_datetime(ttff_df['stop_datetime'])
+    ttff_df['ttff'] = ttff_df['ttff'].astype(float)
+    ttff_df['avg_top4_cn0'] = ttff_df['avg_top4_cn0'].astype(float)
+    ttff_df['avg_cn0'] = ttff_df['avg_cn0'].astype(float)
+    ttff_df['satnum_for_fix'] = ttff_df['satnum_for_fix'].astype(int)
+
+    # return ttff dataframe
+    return ttff_df
+
+
+def parse_gpsapilog_to_df(filename):
+    """Parse GPS API log to Pandas dataframes.
+
+    Args:
+      filename: full log file name.
+        Type, String.
+
+    Returns:
+      timestamp_df: Timestamp Data Frame.
+        Type, Pandas DataFrame.
+      sv_info_df: GNSS SV info Data Frame.
+        Type, Pandas DataFrame.
+      sv_stat_df: GNSS SV statistic Data Frame.
+        Type, Pandas DataFrame
+      loc_info_df: Location Information Data Frame.
+        Type, Pandas DataFrame.
+        include Provider, Latitude, Longitude, Altitude, GNSSTime, Speed, Bearing
+    """
+    def get_phone_time(target_df_row, timestamp_df):
+        """subfunction to get the phone_time."""
+
+        try:
+            row_num = timestamp_df[
+                timestamp_df.index < target_df_row.name].iloc[-1].name
+            phone_time = timestamp_df.loc[row_num]['phone_time']
+        except IndexError:
+            row_num = npy.NaN
+            phone_time = npy.NaN
+
+        return phone_time, row_num
+
+    # Get parsed dataframe list
+    parsed_data = parse_log_to_df(
+        filename=filename,
+        configs=CONFIG_GPSAPILOG,
+    )
+
+    # get DUT Timestamp
+    timestamp_df = parsed_data['phone_time']
+    timestamp_df['phone_time'] = timestamp_df.apply(
+        lambda row: datetime.datetime.strptime(row.date + '-' + row.time,
+                                               '%Y/%m/%d-%H:%M:%S'),
+        axis=1)
+
+    # Add phone_time from timestamp_df dataframe by row number
+    for key in parsed_data:
+        if key != 'phone_time':
+            current_df = parsed_data[key]
+            time_n_row_num = current_df.apply(get_phone_time,
+                                              axis=1,
+                                              timestamp_df=timestamp_df)
+            current_df[['phone_time', 'time_row_num'
+                        ]] = pds.DataFrame(time_n_row_num.apply(pds.Series))
+
+    # Get space vehicle info dataframe
+    sv_info_df = parsed_data['SpaceVehicle']
+
+    # Get space vehicle statistics dataframe
+    # First merge all dataframe from LIST_SVSTAT[1:],
+    # Drop duplicated 'phone_time', based on time_row_num
+    sv_stat_df = fts.reduce(
+        lambda item1, item2: pds.merge(item1, item2, on='time_row_num'), [
+            parsed_data[key].drop(['phone_time'], axis=1)
+            for key in LIST_SVSTAT[1:]
+        ])
+    # Then merge with LIST_SVSTAT[0]
+    sv_stat_df = pds.merge(sv_stat_df,
+                           parsed_data[LIST_SVSTAT[0]],
+                           on='time_row_num')
+
+    # Get location fix information dataframe
+    # First merge all dataframe from LIST_LOCINFO[1:],
+    # Drop duplicated 'phone_time', based on time_row_num
+    loc_info_df = fts.reduce(
+        lambda item1, item2: pds.merge(item1, item2, on='time_row_num'), [
+            parsed_data[key].drop(['phone_time'], axis=1)
+            for key in LIST_LOCINFO[1:]
+        ])
+    # Then merge with LIST_LOCINFO[8]
+    loc_info_df = pds.merge(loc_info_df,
+                            parsed_data[LIST_LOCINFO[0]],
+                            on='time_row_num')
+    # Convert GNSS Time
+    loc_info_df['gnsstime'] = loc_info_df.apply(
+        lambda row: datetime.datetime.strptime(row.Date + '-' + row.Time,
+                                               '%Y/%m/%d-%H:%M:%S'),
+        axis=1)
+
+    return timestamp_df, sv_info_df, sv_stat_df, loc_info_df
+
+
+def parse_gpsapilog_to_df_v2(filename):
+    """Parse GPS API log to Pandas dataframes, by using merge_asof.
+
+    Args:
+      filename: full log file name.
+        Type, String.
+
+    Returns:
+      timestamp_df: Timestamp Data Frame.
+        Type, Pandas DataFrame.
+      sv_info_df: GNSS SV info Data Frame.
+        Type, Pandas DataFrame.
+      sv_stat_df: GNSS SV statistic Data Frame.
+        Type, Pandas DataFrame
+      loc_info_df: Location Information Data Frame.
+        Type, Pandas DataFrame.
+        include Provider, Latitude, Longitude, Altitude, GNSSTime, Speed, Bearing
+    """
+    # Get parsed dataframe list
+    parsed_data = parse_log_to_df(
+        filename=filename,
+        configs=CONFIG_GPSAPILOG,
+    )
+
+    # get DUT Timestamp
+    timestamp_df = parsed_data['phone_time']
+    timestamp_df['phone_time'] = timestamp_df.apply(
+        lambda row: datetime.datetime.strptime(row.date + '-' + row.time,
+                                               '%Y/%m/%d-%H:%M:%S'),
+        axis=1)
+    # drop logsize, date, time
+    parsed_data['phone_time'] = timestamp_df.drop(['logsize', 'date', 'time'],
+                                                  axis=1)
+
+    # Add phone_time from timestamp dataframe by row number
+    for key in parsed_data:
+        if (key != 'phone_time') and (not parsed_data[key].empty):
+            parsed_data[key] = pds.merge_asof(parsed_data[key],
+                                              parsed_data['phone_time'],
+                                              left_index=True,
+                                              right_index=True)
+
+    # Get space vehicle info dataframe
+    # Handle the pre GPSTool 2.12.24 case
+    if not parsed_data['SpaceVehicle'].empty:
+        sv_info_df = parsed_data['SpaceVehicle']
+
+    # Handle the post GPSTool 2.12.24 case with baseband CNo
+    elif not parsed_data['SpaceVehicle_wBB'].empty:
+        sv_info_df = parsed_data['SpaceVehicle_wBB']
+
+    # Get space vehicle statistics dataframe
+    # Handle the pre GPSTool 2.12.24 case
+    if not parsed_data['HistoryAvgTop4CNo'].empty:
+        # First merge all dataframe from LIST_SVSTAT[1:],
+        sv_stat_df = fts.reduce(
+            lambda item1, item2: pds.merge(item1, item2, on='phone_time'),
+            [parsed_data[key] for key in LIST_SVSTAT[1:]])
+        # Then merge with LIST_SVSTAT[0]
+        sv_stat_df = pds.merge(sv_stat_df,
+                               parsed_data[LIST_SVSTAT[0]],
+                               on='phone_time')
+
+    # Handle the post GPSTool 2.12.24 case with baseband CNo
+    elif not parsed_data['AntennaHistoryAvgTop4CNo'].empty:
+        # First merge all dataframe from LIST_SVSTAT[1:],
+        sv_stat_df = fts.reduce(
+            lambda item1, item2: pds.merge(item1, item2, on='phone_time'),
+            [parsed_data[key] for key in LIST_SVSTAT_WBB[1:]])
+        # Then merge with LIST_SVSTAT[0]
+        sv_stat_df = pds.merge(sv_stat_df,
+                               parsed_data[LIST_SVSTAT_WBB[0]],
+                               on='phone_time')
+
+    # Get location fix information dataframe
+    # First merge all dataframe from LIST_LOCINFO[1:],
+    loc_info_df = fts.reduce(
+        lambda item1, item2: pds.merge(item1, item2, on='phone_time'),
+        [parsed_data[key] for key in LIST_LOCINFO[1:]])
+    # Then merge with LIST_LOCINFO[8]
+    loc_info_df = pds.merge(loc_info_df,
+                            parsed_data[LIST_LOCINFO[0]],
+                            on='phone_time')
+    # Convert GNSS Time
+    loc_info_df['gnsstime'] = loc_info_df.apply(
+        lambda row: datetime.datetime.strptime(row.Date + '-' + row.Time,
+                                               '%Y/%m/%d-%H:%M:%S'),
+        axis=1)
+
+    # Data Conversion
+    timestamp_df['logsize'] = timestamp_df['logsize'].astype(int)
+
+    sv_info_df['SV'] = sv_info_df['SV'].astype(int)
+    sv_info_df['Elevation'] = sv_info_df['Elevation'].astype(float)
+    sv_info_df['Azimuth'] = sv_info_df['Azimuth'].astype(float)
+    sv_info_df['Frequency'] = sv_info_df['Frequency'].astype(float)
+
+    if 'CNo' in list(sv_info_df.columns):
+        sv_info_df['CNo'] = sv_info_df['CNo'].astype(float)
+        sv_info_df['AntCNo'] = sv_info_df['CNo']
+    elif 'AntCNo' in list(sv_info_df.columns):
+        sv_info_df['AntCNo'] = sv_info_df['AntCNo'].astype(float)
+        sv_info_df['BbCNo'] = sv_info_df['BbCNo'].astype(float)
+
+    if 'CurrentAvgTop4CNo' in list(sv_stat_df.columns):
+        sv_stat_df['CurrentAvgTop4CNo'] = sv_stat_df[
+            'CurrentAvgTop4CNo'].astype(float)
+        sv_stat_df['CurrentAvgCNo'] = sv_stat_df['CurrentAvgCNo'].astype(float)
+        sv_stat_df['HistoryAvgTop4CNo'] = sv_stat_df[
+            'HistoryAvgTop4CNo'].astype(float)
+        sv_stat_df['HistoryAvgCNo'] = sv_stat_df['HistoryAvgCNo'].astype(float)
+        sv_stat_df['AntennaCurrentAvgTop4CNo'] = sv_stat_df[
+            'CurrentAvgTop4CNo']
+        sv_stat_df['AntennaCurrentAvgCNo'] = sv_stat_df['CurrentAvgCNo']
+        sv_stat_df['AntennaHistoryAvgTop4CNo'] = sv_stat_df[
+            'HistoryAvgTop4CNo']
+        sv_stat_df['AntennaHistoryAvgCNo'] = sv_stat_df['HistoryAvgCNo']
+        sv_stat_df['BasebandCurrentAvgTop4CNo'] = npy.nan
+        sv_stat_df['BasebandCurrentAvgCNo'] = npy.nan
+        sv_stat_df['BasebandHistoryAvgTop4CNo'] = npy.nan
+        sv_stat_df['BasebandHistoryAvgCNo'] = npy.nan
+
+    elif 'AntennaCurrentAvgTop4CNo' in list(sv_stat_df.columns):
+        sv_stat_df['AntennaCurrentAvgTop4CNo'] = sv_stat_df[
+            'AntennaCurrentAvgTop4CNo'].astype(float)
+        sv_stat_df['AntennaCurrentAvgCNo'] = sv_stat_df[
+            'AntennaCurrentAvgCNo'].astype(float)
+        sv_stat_df['AntennaHistoryAvgTop4CNo'] = sv_stat_df[
+            'AntennaHistoryAvgTop4CNo'].astype(float)
+        sv_stat_df['AntennaHistoryAvgCNo'] = sv_stat_df[
+            'AntennaHistoryAvgCNo'].astype(float)
+        sv_stat_df['BasebandCurrentAvgTop4CNo'] = sv_stat_df[
+            'BasebandCurrentAvgTop4CNo'].astype(float)
+        sv_stat_df['BasebandCurrentAvgCNo'] = sv_stat_df[
+            'BasebandCurrentAvgCNo'].astype(float)
+        sv_stat_df['BasebandHistoryAvgTop4CNo'] = sv_stat_df[
+            'BasebandHistoryAvgTop4CNo'].astype(float)
+        sv_stat_df['BasebandHistoryAvgCNo'] = sv_stat_df[
+            'BasebandHistoryAvgCNo'].astype(float)
+
+    sv_stat_df['L5EngagingRate'] = sv_stat_df['L5EngagingRate'].astype(float)
+
+    loc_info_df['Latitude'] = loc_info_df['Latitude'].astype(float)
+    loc_info_df['Longitude'] = loc_info_df['Longitude'].astype(float)
+    loc_info_df['Altitude'] = loc_info_df['Altitude'].astype(float)
+    loc_info_df['Speed'] = loc_info_df['Speed'].astype(float)
+    loc_info_df['Bearing'] = loc_info_df['Bearing'].astype(float)
+
+    return timestamp_df, sv_info_df, sv_stat_df, loc_info_df
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/command/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/command/instrumentation_command_builder.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/instrumentation_command_builder.py
new file mode 100644
index 0000000..8fa2c72
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/instrumentation_command_builder.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import shlex
+
+DEFAULT_INSTRUMENTATION_LOG_OUTPUT = 'instrumentation_output.txt'
+
+
+class InstrumentationCommandBuilder(object):
+    """Helper class to build instrumentation commands."""
+
+    def __init__(self):
+        self._manifest_package_name = None
+        self._flags = []
+        self._key_value_params = {}
+        self._runner = None
+        self._nohup = False
+        self._proto_path = None
+        self._nohup_log_path = None
+        self._output_as_proto = False
+
+    def set_manifest_package(self, test_package):
+        self._manifest_package_name = test_package
+
+    def set_runner(self, runner):
+        self._runner = runner
+
+    def add_flag(self, param):
+        self._flags.append(param)
+
+    def remove_flag(self, param):
+        while self._flags.count(param):
+            self._flags.remove(param)
+
+    def add_key_value_param(self, key, value):
+        if isinstance(value, bool):
+            value = str(value).lower()
+        self._key_value_params[key] = str(value)
+
+    def set_proto_path(self, path=None):
+        """Sets a custom path to store result proto. Note that this path will
+        be relative to $EXTERNAL_STORAGE on device. Calling this function
+        automatically enables output as proto.
+
+        Args:
+            path: The $EXTERNAL_STORAGE subdirectory to write the result proto
+            to. If left as None, the default location will be used.
+        """
+        self._output_as_proto = True
+        self._proto_path = path
+
+    def set_output_as_text(self):
+        """This is the default behaviour. It will simply output the
+        instrumentation output to the devices stdout. If the nohup option is
+        enabled the instrumentation output will be redirected to the defined
+        path or its default.
+        """
+        self._output_as_proto = False
+        self._proto_path = None
+
+    def set_nohup(self, log_path=None):
+        """Enables nohup mode. This enables the instrumentation command to
+        continue running after a USB disconnect.
+
+        Args:
+            log_path: Path to store stdout of the process. Default is:
+            $EXTERNAL_STORAGE/instrumentation_output.txt
+        """
+        if log_path is None:
+            log_path = os.path.join('$EXTERNAL_STORAGE',
+                                    DEFAULT_INSTRUMENTATION_LOG_OUTPUT)
+        self._nohup = True
+        self._nohup_log_path = log_path
+
+    def build(self):
+        call = self._instrument_call_with_arguments()
+        call.append('{}/{}'.format(self._manifest_package_name, self._runner))
+        if self._nohup:
+            call = ['nohup'] + call
+            call.append('>>')
+            call.append(self._nohup_log_path)
+            call.append('2>&1')
+        return " ".join(call)
+
+    def _instrument_call_with_arguments(self):
+        errors = []
+        if self._manifest_package_name is None:
+            errors.append('manifest package cannot be none')
+        if self._runner is None:
+            errors.append('instrumentation runner cannot be none')
+        if len(errors) > 0:
+            raise Exception('instrumentation call build errors: {}'
+                            .format(','.join(errors)))
+        call = ['am instrument']
+
+        for flag in self._flags:
+            call.append(flag)
+
+        if self._output_as_proto:
+            call.append('-f')
+        if self._proto_path:
+            call.append(self._proto_path)
+        for key, value in self._key_value_params.items():
+            call.append('-e')
+            call.append(key)
+            call.append(shlex.quote(value))
+        return call
+
+
+class InstrumentationTestCommandBuilder(InstrumentationCommandBuilder):
+
+    def __init__(self):
+        super().__init__()
+        self._packages = []
+        self._classes = []
+
+    @staticmethod
+    def default():
+        """Default instrumentation call builder.
+
+        The flags -w, -r are enabled.
+
+           -w  Forces am instrument to wait until the instrumentation terminates
+           (needed for logging)
+           -r  Outputs results in raw format.
+           https://developer.android.com/studio/test/command-line#AMSyntax
+
+        The default test runner is androidx.test.runner.AndroidJUnitRunner.
+        """
+        builder = InstrumentationTestCommandBuilder()
+        builder.add_flag('-w')
+        builder.add_flag('-r')
+        builder.set_runner('androidx.test.runner.AndroidJUnitRunner')
+        return builder
+
+    CONFLICTING_PARAMS_MESSAGE = ('only a list of classes and test methods or '
+                                  'a list of test packages are allowed.')
+
+    def add_test_package(self, package):
+        if len(self._classes) != 0:
+            raise Exception(self.CONFLICTING_PARAMS_MESSAGE)
+        self._packages.append(package)
+
+    def add_test_method(self, class_name, test_method):
+        if len(self._packages) != 0:
+            raise Exception(self.CONFLICTING_PARAMS_MESSAGE)
+        self._classes.append('{}#{}'.format(class_name, test_method))
+
+    def add_test_class(self, class_name):
+        if len(self._packages) != 0:
+            raise Exception(self.CONFLICTING_PARAMS_MESSAGE)
+        self._classes.append(class_name)
+
+    def build(self):
+        errors = []
+        if len(self._packages) == 0 and len(self._classes) == 0:
+            errors.append('at least one of package, class or test method need '
+                          'to be defined')
+
+        if len(errors) > 0:
+            raise Exception('instrumentation call build errors: {}'
+                            .format(','.join(errors)))
+
+        call = self._instrument_call_with_arguments()
+
+        if len(self._packages) > 0:
+            call.append('-e')
+            call.append('package')
+            call.append(','.join(self._packages))
+        elif len(self._classes) > 0:
+            call.append('-e')
+            call.append('class')
+            call.append(','.join(self._classes))
+
+        call.append('{}/{}'.format(self._manifest_package_name, self._runner))
+        if self._nohup:
+            call = ['nohup'] + call
+            call.append('>>')
+            call.append(self._nohup_log_path)
+            call.append('2>&1')
+        return ' '.join(call)
diff --git a/acts_tests/acts_contrib/test_utils/net/NetstackBaseTest.py b/acts_tests/acts_contrib/test_utils/net/NetstackBaseTest.py
new file mode 100755
index 0000000..a59a2e0
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/NetstackBaseTest.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+from acts.base_test import BaseTestClass
+from acts import asserts
+
+
+class NetstackBaseTest(BaseTestClass):
+    def __init__(self, controllers):
+        BaseTestClass.__init__(self, controllers)
diff --git a/acts_tests/acts_contrib/test_utils/net/__init__.py b/acts_tests/acts_contrib/test_utils/net/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/net/arduino_test_utils.py b/acts_tests/acts_contrib/test_utils/net/arduino_test_utils.py
new file mode 100644
index 0000000..595512c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/arduino_test_utils.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3.4
+#
+# Copyright 2018 Google, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import time
+import pprint
+
+from enum import IntEnum
+from queue import Empty
+
+from acts import asserts
+from acts import signals
+from acts import utils
+from acts.controllers import attenuator
+from acts_contrib.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.tel import tel_defines
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+
+ARDUINO = "/root/arduino/arduino-1.8.5/arduino "
+CONNECT_WIFI = "/arduino/connect_wifi/connect_wifi.ino"
+DISCONNECT_WIFI = "/arduino/disconnect_wifi/disconnect_wifi.ino"
+SSID = wutils.WifiEnums.SSID_KEY
+PWD = wutils.WifiEnums.PWD_KEY
+
+def connect_wifi(wd, network=None):
+    """Connect wifi on arduino wifi dongle
+
+    Args:
+        wd - wifi dongle object
+        network - wifi network to connect to
+    """
+    wd.log.info("Flashing connect_wifi.ino onto dongle")
+    cmd = "locate %s" % CONNECT_WIFI
+    file_path = utils.exe_cmd(cmd).decode("utf-8", "ignore").split()[-1]
+    write_status = wd.write(ARDUINO, file_path, network)
+    asserts.assert_true(write_status, "Failed to flash connect wifi")
+    wd.log.info("Flashing complete")
+    wifi_status = wd.wifi_status()
+    asserts.assert_true(wifi_status, "Failed to connect to %s" % network)
+    ping_status = wd.ping_status()
+    asserts.assert_true(ping_status, "Failed to connect to internet")
+
+def disconnect_wifi(wd):
+    """Disconnect wifi on arduino wifi dongle
+
+    Args:
+        wd - wifi dongle object
+
+    Returns:
+        True - if wifi is disconnected
+        False - if not
+    """
+    wd.log.info("Flashing disconnect_wifi.ino onto dongle")
+    cmd = "locate %s" % DISCONNECT_WIFI
+    file_path = utils.exe_cmd(cmd).decode("utf-8", "ignore").rstrip()
+    write_status = wd.write(ARDUINO, file_path)
+    asserts.assert_true(write_status, "Failed to flash disconnect wifi")
+    wd.log.info("Flashing complete")
+    wifi_status = wd.wifi_status(False)
+    asserts.assert_true(not wifi_status, "Failed to disconnect wifi")
diff --git a/acts_tests/acts_contrib/test_utils/net/connectivity_const.py b/acts_tests/acts_contrib/test_utils/net/connectivity_const.py
new file mode 100644
index 0000000..60d4f67
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/connectivity_const.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+import enum
+
+######################################################
+# ConnectivityManager.NetworkCallback events
+######################################################
+EVENT_NETWORK_CALLBACK = "NetworkCallback"
+
+# event types
+NETWORK_CB_PRE_CHECK = "PreCheck"
+NETWORK_CB_AVAILABLE = "Available"
+NETWORK_CB_LOSING = "Losing"
+NETWORK_CB_LOST = "Lost"
+NETWORK_CB_UNAVAILABLE = "Unavailable"
+NETWORK_CB_CAPABILITIES_CHANGED = "CapabilitiesChanged"
+NETWORK_CB_SUSPENDED = "Suspended"
+NETWORK_CB_RESUMED = "Resumed"
+NETWORK_CB_LINK_PROPERTIES_CHANGED = "LinkPropertiesChanged"
+NETWORK_CB_INVALID = "Invalid"
+
+# event data keys
+NETWORK_CB_KEY_ID = "id"
+NETWORK_CB_KEY_EVENT = "networkCallbackEvent"
+NETWORK_CB_KEY_MAX_MS_TO_LIVE = "maxMsToLive"
+NETWORK_CB_KEY_RSSI = "rssi"
+NETWORK_CB_KEY_INTERFACE_NAME = "interfaceName"
+NETWORK_CB_KEY_CREATE_TS = "creation_timestamp"
+NETWORK_CB_KEY_CURRENT_TS = "current_timestamp"
+NETWORK_CB_KEY_NETWORK_SPECIFIER = "network_specifier"
+
+# Constants for VPN connection status
+VPN_STATE_DISCONNECTED = 0
+VPN_STATE_INITIALIZING = 1
+VPN_STATE_CONNECTING = 2
+VPN_STATE_CONNECTED = 3
+VPN_STATE_TIMEOUT = 4
+VPN_STATE_FAILED = 5
+# TODO gmoturu: determine the exact timeout value
+# This is a random value as of now
+VPN_TIMEOUT = 30
+
+# Connectiivty Manager constants
+TYPE_MOBILE = 0
+TYPE_WIFI = 1
+
+# Multipath preference constants
+MULTIPATH_PREFERENCE_NONE = 0
+MULTIPATH_PREFERENCE_HANDOVER = 1 << 0
+MULTIPATH_PREFERENCE_RELIABILITY = 1 << 1
+MULTIPATH_PREFERENCE_PERFORMANCE = 1 << 2
+
+# Private DNS constants
+DNS_GOOGLE = "dns.google"
+DNS_QUAD9 = "dns.quad9.net"
+DNS_CLOUDFLARE = "1dot1dot1dot1.cloudflare-dns.com"
+PRIVATE_DNS_MODE_OFF = "off"
+PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic"
+PRIVATE_DNS_MODE_STRICT = "hostname"
+
+# IpSec constants
+SOCK_STREAM = 1
+SOCK_DGRAM = 2
+AF_INET = 2
+AF_INET6 = 10
+DIRECTION_IN = 0
+DIRECTION_OUT = 1
+MODE_TRANSPORT = 0
+MODE_TUNNEL = 1
+CRYPT_NULL = "ecb(cipher_null)"
+CRYPT_AES_CBC = "cbc(aes)"
+AUTH_HMAC_MD5 = "hmac(md5)"
+AUTH_HMAC_SHA1 = "hmac(sha1)"
+AUTH_HMAC_SHA256 = "hmac(sha256)"
+AUTH_HMAC_SHA384 = "hmac(sha384)"
+AUTH_HMAC_SHA512 = "hmac(sha512)"
+AUTH_CRYPT_AES_GCM = "rfc4106(gcm(aes))"
+
+
+# Constants for VpnProfile
+class VpnProfile(object):
+    """ This class contains all the possible
+        parameters required for VPN connection
+    """
+    NAME = "name"
+    TYPE = "type"
+    SERVER = "server"
+    USER = "username"
+    PWD = "password"
+    DNS = "dnsServers"
+    SEARCH_DOMAINS = "searchDomains"
+    ROUTES = "routes"
+    MPPE = "mppe"
+    L2TP_SECRET = "l2tpSecret"
+    IPSEC_ID = "ipsecIdentifier"
+    IPSEC_SECRET = "ipsecSecret"
+    IPSEC_USER_CERT = "ipsecUserCert"
+    IPSEC_CA_CERT = "ipsecCaCert"
+    IPSEC_SERVER_CERT = "ipsecServerCert"
+
+
+# Enums for VPN profile types
+class VpnProfileType(enum.Enum):
+    """ Integer constant for each type of VPN
+    """
+    PPTP = 0
+    L2TP_IPSEC_PSK = 1
+    L2TP_IPSEC_RSA = 2
+    IPSEC_XAUTH_PSK = 3
+    IPSEC_XAUTH_RSA = 4
+    IPSEC_HYBRID_RSA = 5
+    IKEV2_IPSEC_USER_PASS = 6
+    IKEV2_IPSEC_PSK = 7
+    IKEV2_IPSEC_RSA = 8
+
+
+# Constants for config file
+class VpnReqParams(object):
+    """ Config file parameters required for
+        VPN connection
+    """
+    vpn_server_addresses = "vpn_server_addresses"
+    vpn_verify_addresses = "vpn_verify_addresses"
+    vpn_username = "vpn_username"
+    vpn_password = "vpn_password"
+    psk_secret = "psk_secret"
+    client_pkcs_file_name = "client_pkcs_file_name"
+    cert_path_vpnserver = "cert_path_vpnserver"
+    cert_password = "cert_password"
+    pptp_mppe = "pptp_mppe"
+    ipsec_server_type = "ipsec_server_type"
+    wifi_network = "wifi_network"
+    vpn_identity = "vpn_identity"
+    vpn_server_hostname = "vpn_server_hostname"
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
new file mode 100644
index 0000000..d35fe04
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/connectivity_test_utils.py
@@ -0,0 +1,119 @@
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts import asserts
+from acts_contrib.test_utils.net import connectivity_const as cconst
+from queue import Empty
+
+def _listen_for_keepalive_event(ad, key, msg, ka_event):
+    """Listen for keepalive event and return status
+
+    Args:
+        ad: DUT object
+        key: keepalive key
+        msg: Error message
+        event: Keepalive event type
+    """
+    ad.droid.socketKeepaliveStartListeningForEvent(key, ka_event)
+    try:
+        event = ad.ed.pop_event("SocketKeepaliveCallback")
+        status = event["data"]["socketKeepaliveEvent"] == ka_event
+    except Empty:
+        asserts.fail(msg)
+    finally:
+        ad.droid.socketKeepaliveStopListeningForEvent(key, ka_event)
+    if ka_event != "Started":
+        ad.droid.removeSocketKeepaliveReceiverKey(key)
+    if status:
+        ad.log.info("'%s' keepalive event successful" % ka_event)
+    return status
+
+def start_natt_socket_keepalive(ad, udp_encap, src_ip, dst_ip, interval = 10):
+    """Start NATT SocketKeepalive on DUT
+
+    Args:
+        ad: DUT object
+        udp_encap: udp_encap socket key
+        src_ip: IP addr of the client
+        dst_ip: IP addr of the keepalive server
+        interval: keepalive time interval
+    """
+    ad.log.info("Starting Natt Socket Keepalive")
+    key = ad.droid.startNattSocketKeepalive(udp_encap, src_ip, dst_ip, interval)
+    msg = "Failed to receive confirmation of starting natt socket keeaplive"
+    status = _listen_for_keepalive_event(ad, key, msg, "Started")
+    return key if status else None
+
+def start_tcp_socket_keepalive(ad, socket, time_interval = 10):
+    """Start TCP socket keepalive on DUT
+
+    Args:
+        ad: DUT object
+        socket: TCP socket key
+        time_interval: Keepalive time interval
+    """
+    ad.log.info("Starting TCP Socket Keepalive")
+    key = ad.droid.startTcpSocketKeepalive(socket, time_interval)
+    msg = "Failed to receive confirmation of starting tcp socket keeaplive"
+    status = _listen_for_keepalive_event(ad, key, msg, "Started")
+    return key if status else None
+
+def socket_keepalive_error(ad, key):
+    """Verify Error callback
+
+    Args:
+        ad: DUT object
+        key: Keepalive key
+    """
+    ad.log.info("Verify Error callback on keepalive: %s" % key)
+    msg = "Failed to receive confirmation of Error callback"
+    return _listen_for_keepalive_event(ad, key, msg, "Error")
+
+def socket_keepalive_data_received(ad, key):
+    """Verify OnDataReceived callback
+
+    Args:
+        ad: DUT object
+        key: Keepalive key
+    """
+    ad.log.info("Verify OnDataReceived callback on keepalive: %s" % key)
+    msg = "Failed to receive confirmation of OnDataReceived callback"
+    return _listen_for_keepalive_event(ad, key, msg, "OnDataReceived")
+
+def stop_socket_keepalive(ad, key):
+    """Stop SocketKeepalive on DUT
+
+    Args:
+        ad: DUT object
+        key: Keepalive key
+    """
+    ad.log.info("Stopping Socket keepalive: %s" % key)
+    ad.droid.stopSocketKeepalive(key)
+    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 on dut """
+    if dns_mode == cconst.PRIVATE_DNS_MODE_OFF:
+        ad.droid.setPrivateDnsMode(False)
+    else:
+        ad.droid.setPrivateDnsMode(True, hostname)
+
+    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/ipsec_test_utils.py b/acts_tests/acts_contrib/test_utils/net/ipsec_test_utils.py
new file mode 100644
index 0000000..5f383ee
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/ipsec_test_utils.py
@@ -0,0 +1,249 @@
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import binascii
+import os
+import random
+import re
+import threading
+import time
+
+from acts_contrib.test_utils.net import connectivity_const as cconst
+from acts import asserts
+
+PKTS = 5
+
+def make_key(len_bits):
+    asserts.assert_true(
+        len_bits % 8 == 0, "Unexpected key length. Should be a multiple "
+        "of 8, got %s" % len_bits)
+    return binascii.hexlify(os.urandom(int(len_bits/8))).decode()
+
+def allocate_spis(ad, ip_a, ip_b, in_spi = None, out_spi = None):
+    """ Allocate in and out SPIs for android device
+
+    Args:
+      1. ad : android device object
+      2. ip_a : local IP address for In SPI
+      3. ip_b : remote IP address for Out SPI
+      4. in_spi : Generate In SPI with this value
+      5. out_spi : Generate Out SPI with this value
+
+    Returns:
+      List of In and Out SPI
+    """
+    in_spi_key = ad.droid.ipSecAllocateSecurityParameterIndex(ip_a, in_spi)
+    in_spi = ad.droid.ipSecGetSecurityParameterIndex(in_spi_key)
+    ad.log.info("In SPI: %s" % hex(in_spi))
+
+    out_spi_key = ad.droid.ipSecAllocateSecurityParameterIndex(ip_b, out_spi)
+    out_spi = ad.droid.ipSecGetSecurityParameterIndex(out_spi_key)
+    ad.log.info("Out SPI: %s" % hex(out_spi))
+
+    asserts.assert_true(in_spi and out_spi, "Failed to allocate SPIs")
+    return [in_spi_key, out_spi_key]
+
+def release_spis(ad, spis):
+    """ Destroy SPIs
+
+    Args:
+      1. ad : android device object
+      2. spis : list of SPI keys to destroy
+    """
+    for spi_key in spis:
+        ad.droid.ipSecReleaseSecurityParameterIndex(spi_key)
+        spi = ad.droid.ipSecGetSecurityParameterIndex(spi_key)
+        asserts.assert_true(not spi, "Failed to release SPI")
+
+def create_transport_mode_transforms(ad,
+                                     spis,
+                                     ip_a,
+                                     ip_b,
+                                     crypt_algo,
+                                     crypt_key,
+                                     auth_algo,
+                                     auth_key,
+                                     trunc_bit,
+                                     udp_encap_sock=None):
+    """ Create transport mode transforms on the device
+
+    Args:
+      1. ad : android device object
+      2. spis : spi keys of the SPIs created
+      3. ip_a : local IP addr
+      4. ip_b : remote IP addr
+      5. crypt_key : encryption key
+      6. auth_key : authentication key
+      7. udp_encap_sock : set udp encapsulation for ESP packets
+
+    Returns:
+      List of In and Out Transforms
+    """
+    in_transform = ad.droid.ipSecCreateTransportModeTransform(
+        crypt_algo, crypt_key, auth_algo, auth_key, trunc_bit, spis[0],
+        ip_b, udp_encap_sock)
+    ad.log.info("In Transform: %s" % in_transform)
+    out_transform = ad.droid.ipSecCreateTransportModeTransform(
+        crypt_algo, crypt_key, auth_algo, auth_key, trunc_bit, spis[1],
+        ip_a, udp_encap_sock)
+    ad.log.info("Out Transform: %s" % out_transform)
+    asserts.assert_true(in_transform and out_transform,
+                        "Failed to create transforms")
+    return [in_transform, out_transform]
+
+def destroy_transport_mode_transforms(ad, transforms):
+    """ Destroy transforms on the device
+
+    Args:
+      1. ad : android device object
+      2. transforms : list to transform keys to destroy
+    """
+    for transform in transforms:
+        ad.droid.ipSecDestroyTransportModeTransform(transform)
+        status = ad.droid.ipSecGetTransformStatus(transform)
+        ad.log.info("Transform status: %s" % status)
+        asserts.assert_true(not status, "Failed to destroy transform")
+
+def apply_transport_mode_transforms_file_descriptors(ad, fd, transforms):
+    """ Apply transpot mode transform to FileDescriptor object
+
+    Args:
+      1. ad - android device object
+      2. fd - FileDescriptor key
+      3. transforms - list of in and out transforms
+    """
+    in_transform = ad.droid.ipSecApplyTransportModeTransformFileDescriptor(
+        fd, cconst.DIRECTION_IN, transforms[0])
+    out_transform = ad.droid.ipSecApplyTransportModeTransformFileDescriptor(
+        fd, cconst.DIRECTION_OUT, transforms[1])
+    asserts.assert_true(in_transform and out_transform,
+                        "Failed to apply transform")
+    ip_xfrm_state = ad.adb.shell("ip -s xfrm state")
+    ad.log.info("XFRM STATE:\n%s\n" % ip_xfrm_state)
+    ip_xfrm_policy = ad.adb.shell("ip -s xfrm policy")
+    ad.log.info("XFRM POLICY:\n%s\n" % ip_xfrm_policy)
+
+def remove_transport_mode_transforms_file_descriptors(ad, fd):
+    """ Remove transport mode transform from FileDescriptor object
+
+    Args:
+      1. ad - android device object
+      2. socket - FileDescriptor key
+    """
+    status = ad.droid.ipSecRemoveTransportModeTransformsFileDescriptor(fd)
+    asserts.assert_true(status, "Failed to remove transform")
+
+def apply_transport_mode_transforms_datagram_socket(ad, socket, transforms):
+    """ Apply transport mode transform to DatagramSocket object
+
+    Args:
+      1. ad - android device object
+      2. socket - DatagramSocket object key
+      3. transforms - list of in and out transforms
+    """
+    in_tfrm_status = ad.droid.ipSecApplyTransportModeTransformDatagramSocket(
+        socket, cconst.DIRECTION_IN, transforms[0])
+    out_tfrm_status = ad.droid.ipSecApplyTransportModeTransformDatagramSocket(
+        socket, cconst.DIRECTION_OUT, transforms[1])
+    asserts.assert_true(in_tfrm_status and out_tfrm_status,
+                        "Failed to apply transform")
+
+    ip_xfrm_state = ad.adb.shell("ip -s xfrm state")
+    ad.log.info("XFRM STATE:\n%s\n" % ip_xfrm_state)
+
+def remove_transport_mode_transforms_datagram_socket(ad, socket):
+    """ Remove transport mode transform from DatagramSocket object
+
+    Args:
+      1. ad - android device object
+      2. socket - DatagramSocket object key
+    """
+    status = ad.droid.ipSecRemoveTransportModeTransformsDatagramSocket(socket)
+    asserts.assert_true(status, "Failed to remove transform")
+
+def apply_transport_mode_transforms_socket(ad, socket, transforms):
+    """ Apply transport mode transform to Socket object
+
+    Args:
+      1. ad - android device object
+      2. socket - Socket object key
+      3. transforms - list of in and out transforms
+    """
+    in_tfrm_status = ad.droid.ipSecApplyTransportModeTransformSocket(
+        socket, cconst.DIRECTION_IN, transforms[0])
+    out_tfrm_status = ad.droid.ipSecApplyTransportModeTransformSocket(
+        socket, cconst.DIRECTION_OUT, transforms[1])
+    asserts.assert_true(in_tfrm_status and out_tfrm_status,
+                        "Failed to apply transform")
+
+    ip_xfrm_state = ad.adb.shell("ip -s xfrm state")
+    ad.log.info("XFRM STATE:\n%s\n" % ip_xfrm_state)
+
+def remove_transport_mode_transforms_socket(ad, socket):
+    """ Remove transport mode transform from Socket object
+
+    Args:
+      1. ad - android device object
+      2. socket - Socket object key
+    """
+    status = ad.droid.ipSecRemoveTransportModeTransformsSocket(socket)
+    asserts.assert_true(status, "Failed to remove transform")
+
+def verify_esp_packets(ads):
+    """ Verify that encrypted ESP packets are sent
+
+    Args:
+      1. ads - Verify ESP packets on all devices
+    """
+    for ad in ads:
+        ip_xfrm_state = ad.adb.shell("ip -s xfrm state")
+        ad.log.info("XFRM STATE on %s:\n%s\n" % (ad.serial, ip_xfrm_state))
+        pattern = re.findall(r'\d+\(packets\)', ip_xfrm_state)
+        esp_pkts = False
+        for _ in pattern:
+            if int(_.split('(')[0]) >= PKTS:
+                esp_pkts = True
+                break
+        asserts.assert_true(esp_pkts, "Could not find ESP pkts")
+
+def generate_random_crypt_auth_combo():
+    """ Generate every possible combination of crypt and auth keys,
+        auth algo, trunc bits supported by IpSecManager
+    """
+    crypt_key_length = [128, 192, 256]
+    auth_method_key = { cconst.AUTH_HMAC_MD5 : 128,
+                        cconst.AUTH_HMAC_SHA1 : 160,
+                        cconst.AUTH_HMAC_SHA256 : 256,
+                        cconst.AUTH_HMAC_SHA384 : 384,
+                        cconst.AUTH_HMAC_SHA512 : 512 }
+    auth_method_trunc = { cconst.AUTH_HMAC_MD5 : list(range(96, 136, 8)),
+                          cconst.AUTH_HMAC_SHA1 : list(range(96, 168, 8)),
+                          cconst.AUTH_HMAC_SHA256 : list(range(96, 264, 8)),
+                          cconst.AUTH_HMAC_SHA384 : list(range(192, 392, 8)),
+                          cconst.AUTH_HMAC_SHA512 : list(range(256, 520, 8)) }
+    return_list = []
+    for c in crypt_key_length:
+        for k in auth_method_key.keys():
+            auth_key = auth_method_key[k]
+            lst = auth_method_trunc[k]
+            for t in lst:
+                combo = []
+                combo.append(c)
+                combo.append(k)
+                combo.append(auth_key)
+                combo.append(t)
+                return_list.append(combo)
+
+    return return_list
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
new file mode 100644
index 0000000..3d884ca
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/net_test_utils.py
@@ -0,0 +1,512 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 Google, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+import logging
+import os
+
+from acts import asserts
+from acts import signals
+from acts import utils
+from acts.controllers import adb
+from acts.controllers.adb_lib.error import AdbError
+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 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.tel_test_utils import get_operator_name
+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.wifi import wifi_test_utils as wutils
+from scapy.all import get_if_list
+
+import os
+import re
+import time
+import urllib.request
+
+VPN_CONST = cconst.VpnProfile
+VPN_TYPE = cconst.VpnProfileType
+VPN_PARAMS = cconst.VpnReqParams
+TCPDUMP_PATH = "/data/local/tmp/"
+USB_CHARGE_MODE = "svc usb setFunctions"
+USB_TETHERING_MODE = "svc usb setFunctions rndis"
+DEVICE_IP_ADDRESS = "ip address"
+
+GCE_SSH = "gcloud compute ssh "
+GCE_SCP = "gcloud compute scp "
+
+
+def verify_lte_data_and_tethering_supported(ad):
+    """Verify if LTE data is enabled and tethering supported"""
+    wutils.wifi_toggle_state(ad, False)
+    ad.droid.telephonyToggleDataConnection(True)
+    wait_for_cell_data_connection(ad.log, ad, True)
+    asserts.assert_true(
+        verify_http_connection(ad.log, ad),
+        "HTTP verification failed on cell data connection")
+    asserts.assert_true(
+        ad.droid.connectivityIsTetheringSupported(),
+        "Tethering is not supported for the provider")
+    wutils.wifi_toggle_state(ad, True)
+
+
+def set_chrome_browser_permissions(ad):
+    """Set chrome browser start with no-first-run verification.
+    Give permission to read from and write to storage
+    """
+    commands = ["pm grant com.android.chrome "
+                "android.permission.READ_EXTERNAL_STORAGE",
+                "pm grant com.android.chrome "
+                "android.permission.WRITE_EXTERNAL_STORAGE",
+                "rm /data/local/chrome-command-line",
+                "am set-debug-app --persistent com.android.chrome",
+                'echo "chrome --no-default-browser-check --no-first-run '
+                '--disable-fre" > /data/local/tmp/chrome-command-line']
+    for cmd in commands:
+        try:
+            ad.adb.shell(cmd)
+        except AdbError:
+            logging.warning("adb command %s failed on %s" % (cmd, ad.serial))
+
+
+def verify_ping_to_vpn_ip(ad, vpn_ping_addr):
+    """ Verify if IP behind VPN server is pingable.
+    Ping should pass, if VPN is connected.
+    Ping should fail, if VPN is disconnected.
+
+    Args:
+      ad: android device object
+    """
+    ping_result = None
+    pkt_loss = "100% packet loss"
+    logging.info("Pinging: %s" % vpn_ping_addr)
+    try:
+        ping_result = ad.adb.shell("ping -c 3 -W 2 %s" % vpn_ping_addr)
+    except AdbError:
+        pass
+    return ping_result and pkt_loss not in ping_result
+
+
+def legacy_vpn_connection_test_logic(ad, vpn_profile, vpn_ping_addr):
+    """ Test logic for each legacy VPN connection
+
+    Steps:
+      1. Generate profile for the VPN type
+      2. Establish connection to the server
+      3. Verify that connection is established using LegacyVpnInfo
+      4. Verify the connection by pinging the IP behind VPN
+      5. Stop the VPN connection
+      6. Check the connection status
+      7. Verify that ping to IP behind VPN fails
+
+    Args:
+      1. ad: Android device object
+      2. VpnProfileType (1 of the 6 types supported by Android)
+    """
+    # Wait for sometime so that VPN server flushes all interfaces and
+    # connections after graceful termination
+    time.sleep(10)
+
+    ad.adb.shell("ip xfrm state flush")
+    ad.log.info("Connecting to: %s", vpn_profile)
+    ad.droid.vpnStartLegacyVpn(vpn_profile)
+    time.sleep(cconst.VPN_TIMEOUT)
+
+    connected_vpn_info = ad.droid.vpnGetLegacyVpnInfo()
+    asserts.assert_equal(connected_vpn_info["state"],
+                         cconst.VPN_STATE_CONNECTED,
+                         "Unable to establish VPN connection for %s"
+                         % vpn_profile)
+
+    ping_result = verify_ping_to_vpn_ip(ad, vpn_ping_addr)
+    ip_xfrm_state = ad.adb.shell("ip xfrm state")
+    match_obj = re.search(r'hmac(.*)', "%s" % ip_xfrm_state)
+    if match_obj:
+        ip_xfrm_state = format(match_obj.group(0)).split()
+        ad.log.info("HMAC for ESP is %s " % ip_xfrm_state[0])
+
+    ad.droid.vpnStopLegacyVpn()
+    asserts.assert_true(ping_result,
+                        "Ping to the internal IP failed. "
+                        "Expected to pass as VPN is connected")
+
+    connected_vpn_info = ad.droid.vpnGetLegacyVpnInfo()
+    asserts.assert_true(not connected_vpn_info,
+                        "Unable to terminate VPN connection for %s"
+                        % vpn_profile)
+
+
+def download_load_certs(ad, vpn_params, vpn_type, vpn_server_addr,
+                        ipsec_server_type, log_path):
+    """ Download the certificates from VPN server and push to sdcard of DUT
+
+    Args:
+      ad: android device object
+      vpn_params: vpn params from config file
+      vpn_type: 1 of the 6 VPN types
+      vpn_server_addr: server addr to connect to
+      ipsec_server_type: ipsec version - strongswan or openswan
+      log_path: log path to download cert
+
+    Returns:
+      Client cert file name on DUT's sdcard
+    """
+    url = "http://%s%s%s" % (vpn_server_addr,
+                             vpn_params['cert_path_vpnserver'],
+                             vpn_params['client_pkcs_file_name'])
+    local_cert_name = "%s_%s_%s" % (vpn_type.name,
+                                    ipsec_server_type,
+                                    vpn_params['client_pkcs_file_name'])
+
+    local_file_path = os.path.join(log_path, local_cert_name)
+    logging.info("URL is: %s" % url)
+    try:
+        ret = urllib.request.urlopen(url)
+        with open(local_file_path, "wb") as f:
+            f.write(ret.read())
+    except Exception:
+        asserts.fail("Unable to download certificate from the server")
+
+    ad.adb.push("%s sdcard/" % local_file_path)
+    return local_cert_name
+
+
+def generate_legacy_vpn_profile(ad,
+                                vpn_params,
+                                vpn_type,
+                                vpn_server_addr,
+                                ipsec_server_type,
+                                log_path):
+    """ Generate legacy VPN profile for a VPN
+
+    Args:
+      ad: android device object
+      vpn_params: vpn params from config file
+      vpn_type: 1 of the 6 VPN types
+      vpn_server_addr: server addr to connect to
+      ipsec_server_type: ipsec version - strongswan or openswan
+      log_path: log path to download cert
+
+    Returns:
+      Vpn profile
+    """
+    vpn_profile = {VPN_CONST.USER: vpn_params['vpn_username'],
+                   VPN_CONST.PWD: vpn_params['vpn_password'],
+                   VPN_CONST.TYPE: vpn_type.value,
+                   VPN_CONST.SERVER: vpn_server_addr, }
+    vpn_profile[VPN_CONST.NAME] = "test_%s_%s" % (vpn_type.name,
+                                                  ipsec_server_type)
+    if vpn_type.name == "PPTP":
+        vpn_profile[VPN_CONST.NAME] = "test_%s" % vpn_type.name
+
+    psk_set = set(["L2TP_IPSEC_PSK", "IPSEC_XAUTH_PSK"])
+    rsa_set = set(["L2TP_IPSEC_RSA", "IPSEC_XAUTH_RSA", "IPSEC_HYBRID_RSA"])
+
+    if vpn_type.name in psk_set:
+        vpn_profile[VPN_CONST.IPSEC_SECRET] = vpn_params['psk_secret']
+    elif vpn_type.name in rsa_set:
+        cert_name = download_load_certs(ad,
+                                        vpn_params,
+                                        vpn_type,
+                                        vpn_server_addr,
+                                        ipsec_server_type,
+                                        log_path)
+        vpn_profile[VPN_CONST.IPSEC_USER_CERT] = cert_name.split('.')[0]
+        vpn_profile[VPN_CONST.IPSEC_CA_CERT] = cert_name.split('.')[0]
+        ad.droid.installCertificate(vpn_profile, cert_name,
+                                    vpn_params['cert_password'])
+    else:
+        vpn_profile[VPN_CONST.MPPE] = "mppe"
+
+    return vpn_profile
+
+def generate_ikev2_vpn_profile(ad, vpn_params, vpn_type, server_addr, log_path):
+    """Generate VPN profile for IKEv2 VPN.
+
+    Args:
+        ad: android device object.
+        vpn_params: vpn params from config file.
+        vpn_type: ikev2 vpn type.
+        server_addr: vpn server addr.
+        log_path: log path to download cert.
+
+    Returns:
+        Vpn profile.
+    """
+    vpn_profile = {
+        VPN_CONST.TYPE: vpn_type.value,
+        VPN_CONST.SERVER: server_addr,
+    }
+
+    if vpn_type.name == "IKEV2_IPSEC_USER_PASS":
+        vpn_profile[VPN_CONST.USER] = vpn_params["vpn_username"]
+        vpn_profile[VPN_CONST.PWD] = vpn_params["vpn_password"]
+        vpn_profile[VPN_CONST.IPSEC_ID] = vpn_params["vpn_identity"]
+        cert_name = download_load_certs(
+            ad, vpn_params, vpn_type, vpn_params["server_addr"],
+            "IKEV2_IPSEC_USER_PASS", log_path)
+        vpn_profile[VPN_CONST.IPSEC_CA_CERT] = cert_name.split('.')[0]
+        ad.droid.installCertificate(
+            vpn_profile, cert_name, vpn_params['cert_password'])
+    elif vpn_type.name == "IKEV2_IPSEC_PSK":
+        vpn_profile[VPN_CONST.IPSEC_ID] = vpn_params["vpn_identity"]
+        vpn_profile[VPN_CONST.IPSEC_SECRET] = vpn_params["psk_secret"]
+    else:
+        vpn_profile[VPN_CONST.IPSEC_ID] = "%s@%s" % (
+            vpn_params["vpn_identity"], server_addr)
+        logging.info("ID: %s@%s" % (vpn_params["vpn_identity"], server_addr))
+        cert_name = download_load_certs(
+            ad, vpn_params, vpn_type, vpn_params["server_addr"],
+            "IKEV2_IPSEC_RSA", log_path)
+        vpn_profile[VPN_CONST.IPSEC_USER_CERT] = cert_name.split('.')[0]
+        vpn_profile[VPN_CONST.IPSEC_CA_CERT] = cert_name.split('.')[0]
+        ad.droid.installCertificate(
+            vpn_profile, cert_name, vpn_params['cert_password'])
+
+    return vpn_profile
+
+def start_tcpdump(ad, test_name):
+    """Start tcpdump on all interfaces
+
+    Args:
+        ad: android device object.
+        test_name: tcpdump file name will have this
+    """
+    ad.log.info("Starting tcpdump on all interfaces")
+    try:
+        ad.adb.shell("killall -9 tcpdump")
+    except AdbError:
+        ad.log.warn("Killing existing tcpdump processes failed")
+    out = ad.adb.shell("ls -l %s" % TCPDUMP_PATH)
+    if "No such file" in out or not out:
+        ad.adb.shell("mkdir %s" % TCPDUMP_PATH)
+    else:
+        ad.adb.shell("rm -rf %s/*" % TCPDUMP_PATH, ignore_status=True)
+
+    begin_time = epoch_to_log_line_timestamp(get_current_epoch_time())
+    begin_time = normalize_log_line_timestamp(begin_time)
+
+    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)
+    try:
+        return start_standing_subprocess(cmd, 5)
+    except Exception:
+        ad.log.exception('Could not start standing process %s' % repr(cmd))
+
+    return None
+
+def stop_tcpdump(ad,
+                 proc,
+                 test_name,
+                 adb_pull_timeout=adb.DEFAULT_ADB_PULL_TIMEOUT):
+    """Stops tcpdump on any iface
+       Pulls the tcpdump file in the tcpdump dir
+
+    Args:
+        ad: android device object.
+        proc: need to know which pid to stop
+        test_name: test name to save the tcpdump file
+        adb_pull_timeout: timeout for adb_pull
+
+    Returns:
+      log_path of the tcpdump file
+    """
+    ad.log.info("Stopping and pulling tcpdump if any")
+    if proc is None:
+        return None
+    try:
+        stop_standing_subprocess(proc)
+    except Exception as e:
+        ad.log.warning(e)
+    log_path = os.path.join(ad.log_path, test_name)
+    os.makedirs(log_path, exist_ok=True)
+    ad.adb.pull("%s/. %s" % (TCPDUMP_PATH, log_path), timeout=adb_pull_timeout)
+    ad.adb.shell("rm -rf %s/*" % TCPDUMP_PATH, ignore_status=True)
+    file_name = "tcpdump_%s_%s.pcap" % (ad.serial, test_name)
+    return "%s/%s" % (log_path, file_name)
+
+def start_tcpdump_gce_server(ad, test_name, dest_port, gce):
+    """ Start tcpdump on gce server
+
+    Args:
+        ad: android device object
+        test_name: test case name
+        dest_port: port to collect tcpdump
+        gce: dictionary of gce instance
+
+    Returns:
+       process id and pcap file path from gce server
+    """
+    ad.log.info("Starting tcpdump on gce server")
+
+    # pcap file name
+    fname = "/tmp/%s_%s_%s_%s" % \
+        (test_name, ad.model, ad.serial,
+         time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime(time.time())))
+
+    # start tcpdump
+    tcpdump_cmd = "sudo bash -c \'tcpdump -i %s -w %s.pcap port %s > \
+        %s.txt 2>&1 & echo $!\'" % (gce["interface"], fname, dest_port, fname)
+    gcloud_ssh_cmd = "%s --project=%s --zone=%s %s@%s --command " % \
+        (GCE_SSH, gce["project"], gce["zone"], gce["username"], gce["hostname"])
+    gce_ssh_cmd = '%s "%s"' % (gcloud_ssh_cmd, tcpdump_cmd)
+    utils.exe_cmd(gce_ssh_cmd)
+
+    # get process id
+    ps_cmd = '%s "ps aux | grep tcpdump | grep %s"' % (gcloud_ssh_cmd, fname)
+    tcpdump_pid = utils.exe_cmd(ps_cmd).decode("utf-8", "ignore").split()
+    if not tcpdump_pid:
+        raise signals.TestFailure("Failed to start tcpdump on gce server")
+    return tcpdump_pid[1], fname
+
+def stop_tcpdump_gce_server(ad, tcpdump_pid, fname, gce):
+    """ Stop and pull tcpdump file from gce server
+
+    Args:
+        ad: android device object
+        tcpdump_pid: process id for tcpdump file
+        fname: tcpdump file path
+        gce: dictionary of gce instance
+
+    Returns:
+       pcap file from gce server
+    """
+    ad.log.info("Stop and pull pcap file from gce server")
+
+    # stop tcpdump
+    tcpdump_cmd = "sudo kill %s" % tcpdump_pid
+    gcloud_ssh_cmd = "%s --project=%s --zone=%s %s@%s --command " % \
+        (GCE_SSH, gce["project"], gce["zone"], gce["username"], gce["hostname"])
+    gce_ssh_cmd = '%s "%s"' % (gcloud_ssh_cmd, tcpdump_cmd)
+    utils.exe_cmd(gce_ssh_cmd)
+
+    # verify tcpdump is stopped
+    ps_cmd = '%s "ps aux | grep tcpdump"' % gcloud_ssh_cmd
+    res = utils.exe_cmd(ps_cmd).decode("utf-8", "ignore")
+    if tcpdump_pid in res.split():
+        raise signals.TestFailure("Failed to stop tcpdump on gce server")
+    if not fname:
+        return None
+
+    # pull pcap file
+    gcloud_scp_cmd = "%s --project=%s --zone=%s %s@%s:" % \
+        (GCE_SCP, gce["project"], gce["zone"], gce["username"], gce["hostname"])
+    pull_file = '%s%s.pcap %s/' % (gcloud_scp_cmd, fname, ad.device_log_path)
+    utils.exe_cmd(pull_file)
+    if not os.path.exists(
+        "%s/%s.pcap" % (ad.device_log_path, fname.split('/')[-1])):
+        raise signals.TestFailure("Failed to pull tcpdump from gce server")
+
+    # delete pcaps
+    utils.exe_cmd('%s "sudo rm %s.*"' % (gcloud_ssh_cmd, fname))
+
+    # return pcap file
+    pcap_file = "%s/%s.pcap" % (ad.device_log_path, fname.split('/')[-1])
+    return pcap_file
+
+def is_ipaddress_ipv6(ip_address):
+    """Verify if the given string is a valid IPv6 address.
+
+    Args:
+        ip_address: string containing the IP address
+
+    Returns:
+        True: if valid ipv6 address
+        False: if not
+    """
+    try:
+        socket.inet_pton(socket.AF_INET6, ip_address)
+        return True
+    except socket.error:
+        return False
+
+def carrier_supports_ipv6(dut):
+    """Verify if carrier supports ipv6.
+
+    Args:
+        dut: Android device that is being checked
+
+    Returns:
+        True: if carrier supports ipv6
+        False: if not
+    """
+
+    carrier_supports_ipv6 = ["vzw", "tmo", "Far EasTone", "Chunghwa Telecom"]
+    operator = get_operator_name("log", dut)
+    return operator in carrier_supports_ipv6
+
+def supports_ipv6_tethering(self, dut):
+    """ Check if provider supports IPv6 tethering.
+
+    Returns:
+        True: if provider supports IPv6 tethering
+        False: if not
+    """
+    carrier_supports_tethering = ["vzw", "tmo", "Far EasTone", "Chunghwa Telecom"]
+    operator = get_operator_name(self.log, dut)
+    return operator in carrier_supports_tethering
+
+
+def start_usb_tethering(ad):
+    """Start USB tethering.
+
+    Args:
+        ad: android device 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.")
+
+
+def stop_usb_tethering(ad):
+    """Stop USB tethering.
+
+    Args:
+        ad: android device object
+    """
+    ad.log.info("Stopping USB Tethering")
+    ad.stop_services()
+    ad.adb.shell(USB_CHARGE_MODE)
+    ad.adb.wait_for_device()
+    ad.start_services()
+
+
+def wait_for_new_iface(old_ifaces):
+    """Wait for the new interface to come up.
+
+    Args:
+        old_ifaces: list of old interfaces
+    """
+    old_set = set(old_ifaces)
+    # Try 10 times to find a new interface with a 1s sleep every time
+    # (equivalent to a 9s timeout)
+    for i in range(0, 10):
+        new_ifaces = set(get_if_list()) - old_set
+        asserts.assert_true(len(new_ifaces) < 2,
+                            "Too many new interfaces after turning on "
+                            "tethering")
+        if len(new_ifaces) == 1:
+            return new_ifaces.pop()
+        time.sleep(1)
+    asserts.fail("Timeout waiting for tethering interface on host")
diff --git a/acts_tests/acts_contrib/test_utils/net/nsd_const.py b/acts_tests/acts_contrib/test_utils/net/nsd_const.py
new file mode 100644
index 0000000..20b0ae1
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/nsd_const.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+######################################################
+# NsdManager.RegistrationListener events
+######################################################
+REG_LISTENER_EVENT = "NsdRegistrationListener"
+
+# event type - using REG_LISTENER_CALLBACK
+REG_LISTENER_EVENT_ON_REG_FAILED = "OnRegistrationFailed"
+REG_LISTENER_EVENT_ON_SERVICE_REGISTERED = "OnServiceRegistered"
+REG_LISTENER_EVENT_ON_SERVICE_UNREG = "OnServiceUnregistered"
+REG_LISTENER_EVENT_ON_UNREG_FAILED = "OnUnregistrationFailed"
+
+# event data keys
+REG_LISTENER_DATA_ID = "id"
+REG_LISTENER_CALLBACK = "callback"
+REG_LISTENER_ERROR_CODE = "error_code"
+
+######################################################
+# NsdManager.DiscoveryListener events
+######################################################
+DISCOVERY_LISTENER_EVENT = "NsdDiscoveryListener"
+
+# event type - using DISCOVERY_LISTENER_DATA_CALLBACK
+DISCOVERY_LISTENER_EVENT_ON_DISCOVERY_STARTED = "OnDiscoveryStarted"
+DISCOVERY_LISTENER_EVENT_ON_DISCOVERY_STOPPED = "OnDiscoveryStopped"
+DISCOVERY_LISTENER_EVENT_ON_SERVICE_FOUND = "OnServiceFound"
+DISCOVERY_LISTENER_EVENT_ON_SERVICE_LOST = "OnServiceLost"
+DISCOVERY_LISTENER_EVENT_ON_START_DISCOVERY_FAILED = "OnStartDiscoveryFailed"
+DISCOVERY_LISTENER_EVENT_ON_STOP_DISCOVERY_FAILED = "OnStopDiscoveryFailed"
+
+# event data keys
+DISCOVERY_LISTENER_DATA_ID = "id"
+DISCOVERY_LISTENER_DATA_CALLBACK = "callback"
+DISCOVERY_LISTENER_DATA_SERVICE_TYPE = "service_type"
+DISCOVERY_LISTENER_DATA_ERROR_CODE = "error_code"
+
+######################################################
+# NsdManager.ResolveListener events
+######################################################
+RESOLVE_LISTENER_EVENT = "NsdResolveListener"
+
+# event type using RESOLVE_LISTENER_DATA_CALLBACK
+RESOLVE_LISTENER_EVENT_ON_RESOLVE_FAIL = "OnResolveFail"
+RESOLVE_LISTENER_EVENT_ON_SERVICE_RESOLVED = "OnServiceResolved"
+
+# event data keys
+RESOLVE_LISTENER_DATA_ID = "id"
+RESOLVE_LISTENER_DATA_CALLBACK = "callback"
+RESOLVE_LISTENER_DATA_ERROR_CODE = "error_code"
+
+######################################################
+# NsdServiceInfo elements
+######################################################
+NSD_SERVICE_INFO_HOST = "serviceInfoHost"
+NSD_SERVICE_INFO_PORT = "serviceInfoPort"
+NSD_SERVICE_INFO_SERVICE_NAME = "serviceInfoServiceName"
+NSD_SERVICE_INFO_SERVICE_TYPE = "serviceInfoServiceType"
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/net/socket_test_utils.py b/acts_tests/acts_contrib/test_utils/net/socket_test_utils.py
new file mode 100644
index 0000000..b9a6bdf
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/socket_test_utils.py
@@ -0,0 +1,297 @@
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import queue
+import re
+import threading
+import time
+
+from acts_contrib.test_utils.net import connectivity_const as cconst
+from acts import asserts
+
+MSG = "Test message "
+PKTS = 5
+
+""" Methods for android.system.Os based sockets """
+def open_android_socket(ad, domain, sock_type, ip, port):
+    """ Open TCP or UDP using android.system.Os class
+
+    Args:
+      1. ad - android device object
+      2. domain - IPv4 or IPv6 type
+      3. sock_type - UDP or TCP socket
+      4. ip - IP addr on the device
+      5. port - open socket on port
+
+    Returns:
+      File descriptor key
+    """
+    fd_key = ad.droid.openSocket(domain, sock_type, ip, port)
+    ad.log.info("File descriptor: %s" % fd_key)
+    asserts.assert_true(fd_key, "Failed to open socket")
+    return fd_key
+
+def close_android_socket(ad, fd_key):
+    """ Close socket
+
+    Args:
+      1. ad - android device object
+      2. fd_key - file descriptor key
+    """
+    status = ad.droid.closeSocket(fd_key)
+    asserts.assert_true(status, "Failed to close socket")
+
+def listen_accept_android_socket(client,
+                                 server,
+                                 client_fd,
+                                 server_fd,
+                                 server_ip,
+                                 server_port):
+    """ Listen, accept TCP sockets
+
+    Args:
+      1. client : ad object for client device
+      2. server : ad object for server device
+      3. client_fd : client's socket handle
+      4. server_fd : server's socket handle
+      5. server_ip : send data to this IP
+      6. server_port : send data to this port
+    """
+    server.droid.listenSocket(server_fd)
+    client.droid.connectSocket(client_fd, server_ip, server_port)
+    sock = server.droid.acceptSocket(server_fd)
+    asserts.assert_true(sock, "Failed to accept socket")
+    return sock
+
+def send_recv_data_android_sockets(client,
+                                   server,
+                                   client_fd,
+                                   server_fd,
+                                   server_ip,
+                                   server_port):
+    """ Send TCP or UDP data over android os sockets from client to server.
+        Verify that server received the data.
+
+    Args:
+      1. client : ad object for client device
+      2. server : ad object for server device
+      3. client_fd : client's socket handle
+      4. server_fd : server's socket handle
+      5. server_ip : send data to this IP
+      6. server_port : send data to this port
+    """
+    send_list = []
+    recv_list = []
+
+    for _ in range(1, PKTS+1):
+        msg = MSG + " %s" % _
+        send_list.append(msg)
+        client.log.info("Sending message: %s" % msg)
+        client.droid.sendDataOverSocket(server_ip, server_port, msg, client_fd)
+        recv_msg = server.droid.recvDataOverSocket(server_fd)
+        server.log.info("Received message: %s" % recv_msg)
+        recv_list.append(recv_msg)
+
+    recv_list = [x.rstrip('\x00') if x else x for x in recv_list]
+    asserts.assert_true(send_list and recv_list and send_list == recv_list,
+                        "Send and recv information is incorrect")
+
+""" Methods for java.net.DatagramSocket based sockets """
+def open_datagram_socket(ad, ip, port):
+    """ Open datagram socket
+
+    Args:
+      1. ad : android device object
+      2. ip : IP addr on the device
+      3. port : socket port
+
+    Returns:
+      Hash key of the datagram socket
+    """
+    socket_key = ad.droid.openDatagramSocket(ip, port)
+    ad.log.info("Datagram socket: %s" % socket_key)
+    asserts.assert_true(socket_key, "Failed to open datagram socket")
+    return socket_key
+
+def close_datagram_socket(ad, socket_key):
+    """ Close datagram socket
+
+    Args:
+      1. socket_key : hash key of datagram socket
+    """
+    status = ad.droid.closeDatagramSocket(socket_key)
+    asserts.assert_true(status, "Failed to close datagram socket")
+
+def send_recv_data_datagram_sockets(client,
+                                    server,
+                                    client_sock,
+                                    server_sock,
+                                    server_ip,
+                                    server_port):
+    """ Send data over datagram socket from dut_a to dut_b.
+        Verify that dut_b received the data.
+
+    Args:
+      1. client : ad object for client device
+      2. server : ad object for server device
+      3. client_sock : client's socket handle
+      4. server_sock : server's socket handle
+      5. server_ip : send data to this IP
+      6. server_port : send data to this port
+    """
+    send_list = []
+    recv_list = []
+
+    for _ in range(1, PKTS+1):
+        msg = MSG + " %s" % _
+        send_list.append(msg)
+        client.log.info("Sending message: %s" % msg)
+        client.droid.sendDataOverDatagramSocket(client_sock,
+                                                msg,
+                                                server_ip,
+                                                server_port)
+        recv_msg = server.droid.recvDataOverDatagramSocket(server_sock)
+        server.log.info("Received message: %s" % recv_msg)
+        recv_list.append(recv_msg)
+
+    recv_list = [x.rstrip('\x00') if x else x for x in recv_list]
+    asserts.assert_true(send_list and recv_list and send_list == recv_list,
+                        "Send and recv information is incorrect")
+
+""" Utils methods for java.net.Socket based sockets """
+def _accept_socket(server, server_ip, server_port, server_sock, q):
+    sock = server.droid.acceptTcpSocket(server_sock)
+    server.log.info("Server socket: %s" % sock)
+    q.put(sock)
+
+def _client_socket(client, server_ip, server_port, client_ip, client_port, q):
+    time.sleep(0.5)
+    sock = client.droid.openTcpSocket(server_ip,
+                                      server_port,
+                                      client_ip,
+                                      client_port)
+    client.log.info("Client socket: %s" % sock)
+    q.put(sock)
+
+def open_connect_socket(client,
+                        server,
+                        client_ip,
+                        server_ip,
+                        client_port,
+                        server_port,
+                        server_sock):
+    """ Open tcp socket and connect to server
+
+    Args:
+      1. client : ad object for client device
+      2. server : ad object for server device
+      3. client_ip : client's socket handle
+      4. server_ip : send data to this IP
+      5. client_port : port on client socket
+      6. server_port : port on server socket
+      7. server_sock : server socket
+
+    Returns:
+      client and server socket from successful connect
+    """
+    sq = queue.Queue()
+    cq = queue.Queue()
+    s = threading.Thread(target = _accept_socket,
+                         args = (server, server_ip, server_port, server_sock,
+                                 sq))
+    c = threading.Thread(target = _client_socket,
+                         args = (client, server_ip, server_port, client_ip,
+                                 client_port, cq))
+    s.start()
+    c.start()
+    c.join()
+    s.join()
+
+    client_sock = cq.get()
+    server_sock = sq.get()
+    asserts.assert_true(client_sock and server_sock, "Failed to open sockets")
+
+    return client_sock, server_sock
+
+def open_server_socket(server, server_ip, server_port):
+    """ Open tcp server socket
+
+    Args:
+      1. server : ad object for server device
+      2. server_ip : send data to this IP
+      3. server_port : send data to this port
+    """
+    sock = server.droid.openTcpServerSocket(server_ip, server_port)
+    server.log.info("Server Socket: %s" % sock)
+    asserts.assert_true(sock, "Failed to open server socket")
+    return sock
+
+def close_socket(ad, socket):
+    """ Close socket
+
+    Args:
+      1. ad - android device object
+      2. socket - socket key
+    """
+    status = ad.droid.closeTcpSocket(socket)
+    asserts.assert_true(status, "Failed to socket")
+
+def close_server_socket(ad, socket):
+    """ Close server socket
+
+    Args:
+      1. ad - android device object
+      2. socket - server socket key
+    """
+    status = ad.droid.closeTcpServerSocket(socket)
+    asserts.assert_true(status, "Failed to socket")
+
+def shutdown_socket(ad, socket):
+    """ Shutdown socket
+
+    Args:
+      1. ad - androidandroid device object
+      2. socket - socket key
+    """
+    fd = ad.droid.getFileDescriptorOfSocket(socket)
+    asserts.assert_true(fd, "Failed to get FileDescriptor key")
+    status = ad.droid.shutdownFileDescriptor(fd)
+    asserts.assert_true(status, "Failed to shutdown socket")
+
+def send_recv_data_sockets(client, server, client_sock, server_sock):
+    """ Send data over TCP socket from client to server.
+        Verify that server received the data
+
+    Args:
+      1. client : ad object for client device
+      2. server : ad object for server device
+      3. client_sock : client's socket handle
+      4. server_sock : server's socket handle
+    """
+    send_list = []
+    recv_list = []
+
+    for _ in range(1, PKTS+1):
+        msg = MSG + " %s" % _
+        send_list.append(msg)
+        client.log.info("Sending message: %s" % msg)
+        client.droid.sendDataOverTcpSocket(client_sock, msg)
+        recv_msg = server.droid.recvDataOverTcpSocket(server_sock)
+        server.log.info("Received message: %s" % recv_msg)
+        recv_list.append(recv_msg)
+
+    recv_list = [x.rstrip('\x00') if x else x for x in recv_list]
+    asserts.assert_true(send_list and recv_list and send_list == recv_list,
+                        "Send and recv information is incorrect")
diff --git a/acts_tests/acts_contrib/test_utils/net/ui_utils.py b/acts_tests/acts_contrib/test_utils/net/ui_utils.py
new file mode 100644
index 0000000..eb3bc14
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/ui_utils.py
@@ -0,0 +1,246 @@
+"""Utils for adb-based UI operations."""
+
+import collections
+import logging
+import os
+import re
+import time
+
+from xml.dom import minidom
+from acts.controllers.android_lib.errors import AndroidDeviceError
+
+
+class Point(collections.namedtuple('Point', ['x', 'y'])):
+
+  def __repr__(self):
+    return '{x},{y}'.format(x=self.x, y=self.y)
+
+
+class Bounds(collections.namedtuple('Bounds', ['start', 'end'])):
+
+  def __repr__(self):
+    return '[{start}][{end}]'.format(start=str(self.start), end=str(self.end))
+
+  def calculate_middle_point(self):
+    return Point((self.start.x + self.end.x) // 2,
+                 (self.start.y + self.end.y) // 2)
+
+
+def get_key_value_pair_strings(kv_pairs):
+  return ' '.join(['%s="%s"' % (k, v) for k, v in kv_pairs.items()])
+
+
+def parse_bound(bounds_string):
+  """Parse UI bound string.
+
+  Args:
+    bounds_string: string, In the format of the UI element bound.
+                   e.g '[0,0][1080,2160]'
+
+  Returns:
+    Bounds, The bound of UI element.
+  """
+  bounds_pattern = re.compile(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]')
+  points = bounds_pattern.match(bounds_string).groups()
+  points = list(map(int, points))
+  return Bounds(Point(*points[:2]), Point(*points[-2:]))
+
+
+def _find_point_in_bounds(bounds_string):
+  """Finds a point that resides within the given bounds.
+
+  Args:
+    bounds_string: string, In the format of the UI element bound.
+
+  Returns:
+    A tuple of integers, representing X and Y coordinates of a point within
+    the given boundary.
+  """
+  return parse_bound(bounds_string).calculate_middle_point()
+
+
+def get_screen_dump_xml(device):
+  """Gets an XML dump of the current device screen.
+
+  This only works when there is no instrumentation process running. A running
+  instrumentation process will disrupt calls for `adb shell uiautomator dump`.
+
+  Args:
+    device: AndroidDevice object.
+
+  Returns:
+    XML Document of the screen dump.
+  """
+  os.makedirs(device.log_path, exist_ok=True)
+  device.adb.shell('uiautomator dump')
+  device.adb.pull('/sdcard/window_dump.xml %s' % device.log_path)
+  return minidom.parse('%s/window_dump.xml' % device.log_path)
+
+
+def match_node(node, **matcher):
+  """Determine if a mode matches with the given matcher.
+
+  Args:
+    node: Is a XML node to be checked against matcher.
+    **matcher: Is a dict representing mobly AdbUiDevice matchers.
+
+  Returns:
+    True if all matchers match the given node.
+  """
+  match_list = []
+  for k, v in matcher.items():
+    if k == 'class_name':
+      key = k.replace('class_name', 'class')
+    elif k == 'text_contains':
+      key = k.replace('text_contains', 'text')
+    else:
+      key = k.replace('_', '-')
+    try:
+      if k == 'text_contains':
+        match_list.append(v in node.attributes[key].value)
+      else:
+        match_list.append(node.attributes[key].value == v)
+    except KeyError:
+      match_list.append(False)
+  return all(match_list)
+
+
+def _find_node(screen_dump_xml, **kwargs):
+  """Finds an XML node from an XML DOM.
+
+  Args:
+    screen_dump_xml: XML doc, parsed from adb ui automator dump.
+    **kwargs: key/value pairs to match in an XML node's attributes. Value of
+      each key has to be string type. Below lists keys which can be used:
+        index
+        text
+        text_contains (matching a part of text attribute)
+        resource_id
+        class_name (representing "class" attribute)
+        package
+        content_desc
+        checkable
+        checked
+        clickable
+        enabled
+        focusable
+        focused
+        scrollable
+        long_clickable
+        password
+        selected
+
+  Returns:
+    XML node of the UI element or None if not found.
+  """
+  nodes = screen_dump_xml.getElementsByTagName('node')
+  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
+
+
+def wait_and_get_xml_node(device, timeout, child=None, sibling=None, **kwargs):
+  """Waits for a node to appear and return it.
+
+  Args:
+    device: AndroidDevice object.
+    timeout: float, The number of seconds to wait for before giving up.
+    child: dict, a dict contains child XML node's attributes. It is extra set of
+      conditions to match an XML node that is under the XML node which is found
+      by **kwargs.
+    sibling: dict, a dict contains sibling XML node's attributes. It is extra
+      set of conditions to match an XML node that is under parent of the XML
+      node which is found by **kwargs.
+    **kwargs: Key/value pairs to match in an XML node's attributes.
+
+  Returns:
+    The XML node of the UI element.
+
+  Raises:
+    AndroidDeviceError: if the UI element does not appear on screen within
+    timeout or extra sets of conditions of child and sibling are used in a call.
+  """
+  if child and sibling:
+    raise AndroidDeviceError(
+        device, 'Only use one extra set of conditions: child or sibling.')
+  start_time = time.time()
+  threshold = start_time + timeout
+  while time.time() < threshold:
+    time.sleep(1)
+    screen_dump_xml = get_screen_dump_xml(device)
+    node = _find_node(screen_dump_xml, **kwargs)
+    if node and child:
+      node = _find_node(node, **child)
+    if node and sibling:
+      node = _find_node(node.parentNode, **sibling)
+    if node:
+      return node
+  msg = ('Timed out after %ds waiting for UI node matching conditions: %s.'
+         % (timeout, get_key_value_pair_strings(kwargs)))
+  if child:
+    msg = ('%s extra conditions: %s'
+           % (msg, get_key_value_pair_strings(child)))
+  if sibling:
+    msg = ('%s extra conditions: %s'
+           % (msg, get_key_value_pair_strings(sibling)))
+  raise AndroidDeviceError(device, msg)
+
+
+def has_element(device, **kwargs):
+  """Checks a UI element whether appears or not in the current screen.
+
+  Args:
+    device: AndroidDevice object.
+    **kwargs: Key/value pairs to match in an XML node's attributes.
+
+  Returns:
+    True if the UI element appears in the current screen else False.
+  """
+  timeout_sec = kwargs.pop('timeout', 30)
+  try:
+    wait_and_get_xml_node(device, timeout_sec, **kwargs)
+    return True
+  except AndroidDeviceError:
+    return False
+
+
+def get_element_attributes(device, **kwargs):
+  """Gets a UI element's all attributes.
+
+  Args:
+    device: AndroidDevice object.
+    **kwargs: Key/value pairs to match in an XML node's attributes.
+
+  Returns:
+    XML Node Attributes.
+  """
+  timeout_sec = kwargs.pop('timeout', 30)
+  node = wait_and_get_xml_node(device, timeout_sec, **kwargs)
+  return node.attributes
+
+
+def wait_and_click(device, duration_ms=None, **kwargs):
+  """Wait for a UI element to appear and click on it.
+
+  This function locates a UI element on the screen by matching attributes of
+  nodes in XML DOM, calculates a point's coordinates within the boundary of the
+  element, and clicks on the point marked by the coordinates.
+
+  Args:
+    device: AndroidDevice object.
+    duration_ms: int, The number of milliseconds to long-click.
+    **kwargs: A set of `key=value` parameters that identifies a UI element.
+  """
+  timeout_sec = kwargs.pop('timeout', 30)
+  button_node = wait_and_get_xml_node(device, timeout_sec, **kwargs)
+  x, y = _find_point_in_bounds(button_node.attributes['bounds'].value)
+  args = []
+  if duration_ms is None:
+    args = 'input tap %s %s' % (str(x), str(y))
+  else:
+    # Long click.
+    args = 'input swipe %s %s %s %s %s' % \
+        (str(x), str(y), str(x), str(y), str(duration_ms))
+  device.adb.shell(args)
diff --git a/acts_tests/acts_contrib/test_utils/power/IperfHelper.py b/acts_tests/acts_contrib/test_utils/power/IperfHelper.py
new file mode 100644
index 0000000..cb5af7f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/IperfHelper.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import statistics
+import time
+import acts.controllers.iperf_server as ipf
+
+
+class IperfHelper(object):
+    """ Helps with iperf config and processing the results
+    
+    This class can be used to process the results of multiple iperf servers
+    (for example, dual traffic scenarios). It also helps in setting the
+    correct arguments for when using the phone as an iperf server
+    """
+    IPERF_CLIENT_RESULT_FILE_LOC_PHONE = '/sdcard/Download/'
+
+    def __init__(self, config):
+        self.traffic_type = config['traffic_type']
+        self.traffic_direction = config['traffic_direction']
+        self.duration = config['duration']
+        self.port = config['port']
+        self.server_idx = config['server_idx']
+        self.use_client_output = False
+        if 'bandwidth' in config:
+            self.bandwidth = config['bandwidth']
+        else:
+            self.bandwidth = None
+        if 'start_meas_time' in config:
+            self.start_meas_time = config['start_meas_time']
+        else:
+            self.start_meas_time = 0
+
+        iperf_args = '-i 1 -t {} -p {} -J'.format(self.duration, self.port)
+
+        if self.traffic_type == "UDP":
+            iperf_args = iperf_args + ' -u'
+        if self.traffic_direction == "DL":
+            iperf_args = iperf_args + ' -R'
+            self.use_client_output = True
+        # Set bandwidth in Mbit/s
+        if self.bandwidth is not None:
+            iperf_args = iperf_args + ' -b {}M'.format(self.bandwidth)
+
+        # Set the TCP window size
+        self.window = config.get("window", None)
+        if self.window:
+            iperf_args += ' -w {}M'.format(self.window)
+
+        # Parse the client side data to a file saved on the phone
+        self.results_filename_phone = self.IPERF_CLIENT_RESULT_FILE_LOC_PHONE \
+                                      + 'iperf_client_port_{}_{}.log'.format( \
+                                      self.port, self.traffic_direction)
+        iperf_args = iperf_args + ' > %s' % self.results_filename_phone
+
+        self.iperf_args = iperf_args
+
+    def process_iperf_results(self, dut, log, iperf_servers, test_name):
+        """Gets the iperf results from the phone and computes the average rate
+
+        Returns:
+             throughput: the average throughput (Mbit/s).
+        """
+        # Stopping the server (as safety to get the result file)
+        iperf_servers[self.server_idx].stop()
+        time.sleep(1)
+
+        # Get IPERF results and add this to the plot title
+        RESULTS_DESTINATION = os.path.join(
+            iperf_servers[self.server_idx].log_path,
+            'iperf_client_output_{}.log'.format(test_name))
+
+        PULL_FILE = '{} {}'.format(self.results_filename_phone,
+                                   RESULTS_DESTINATION)
+        dut.adb.pull(PULL_FILE)
+
+        # Calculate the average throughput
+        if self.use_client_output:
+            iperf_file = RESULTS_DESTINATION
+        else:
+            iperf_file = iperf_servers[self.server_idx].log_files[-1]
+        try:
+            iperf_result = ipf.IPerfResult(iperf_file)
+
+            # Get instantaneous rates after measuring starts
+            samples = iperf_result.instantaneous_rates[self.start_meas_time:-1]
+
+            # Convert values to Mbit/s
+            samples = [rate * 8 * (1.024**2) for rate in samples]
+
+            # compute mean, var and max_dev
+            mean = statistics.mean(samples)
+            var = statistics.variance(samples)
+            max_dev = 0
+            for rate in samples:
+                if abs(rate - mean) > max_dev:
+                    max_dev = abs(rate - mean)
+
+            log.info('The average throughput is {}. Variance is {} and max '
+                     'deviation is {}.'.format(
+                         round(mean, 2), round(var, 2), round(max_dev, 2)))
+
+        except:
+            log.warning('Cannot get iperf result.')
+            mean = 0
+
+        return mean
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerBTBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerBTBaseTest.py
new file mode 100644
index 0000000..056787c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/PowerBTBaseTest.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import time
+import acts_contrib.test_utils.bt.bt_power_test_utils as btputils
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
+import acts_contrib.test_utils.power.PowerBaseTest as PBT
+from acts_contrib.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bt_factory
+from math import copysign
+
+BLE_LOCATION_SCAN_DISABLE = 'settings put secure location_mode 0'
+PHONE_MUSIC_FILE_DIRECTORY = '/sdcard/Music'
+INIT_ATTEN = [0]
+
+
+def ramp_attenuation(obj_atten, attenuation_target, attenuation_step_max=20,
+                    time_wait_in_between=5 ):
+    """Ramp the attenuation up or down for BT tests.
+
+    Ramp the attenuation slowly so it won't have dramatic signal drop to affect
+    Link.
+
+    Args:
+        obj_atten: attenuator object, a single port attenuator
+        attenuation_target: target attenuation level to reach to.
+        attenuation_step_max: max step for attenuation set
+        time_wait_in_between: wait time between attenuation changes
+    """
+    sign = lambda x: copysign(1, x)
+    attenuation_delta = obj_atten.get_atten() - attenuation_target
+    while abs(attenuation_delta) > attenuation_step_max:
+        attenuation_intermediate = obj_atten.get_atten(
+        ) - sign(attenuation_delta) * attenuation_step_max
+        obj_atten.set_atten(attenuation_intermediate)
+        time.sleep(time_wait_in_between)
+        attenuation_delta = obj_atten.get_atten() - attenuation_target
+    obj_atten.set_atten(attenuation_target)
+
+
+class PowerBTBaseTest(PBT.PowerBaseTest):
+    """Base class for BT power related tests.
+
+    Inherited from the PowerBaseTest class
+    """
+    def setup_class(self):
+
+        super().setup_class()
+        # Get music file and push it to the phone
+        music_files = self.user_params.get('music_files', [])
+        if music_files:
+            music_src = music_files[0]
+            music_dest = PHONE_MUSIC_FILE_DIRECTORY
+            success = self.dut.push_system_file(music_src, music_dest)
+            if success:
+                self.music_file = os.path.join(PHONE_MUSIC_FILE_DIRECTORY,
+                                               os.path.basename(music_src))
+            # Initialize media_control class
+            self.media = btputils.MediaControl(self.dut, self.music_file)
+        # Set Attenuator to the initial attenuation
+        if hasattr(self, 'attenuators'):
+            self.set_attenuation(INIT_ATTEN)
+            self.attenuator = self.attenuators[0]
+        # Create the BTOE(Bluetooth-Other-End) device object
+        bt_devices = self.user_params.get('bt_devices', [])
+        if bt_devices:
+            attr, idx = bt_devices.split(':')
+            self.bt_device_controller = getattr(self, attr)[int(idx)]
+            self.bt_device = bt_factory().generate(self.bt_device_controller)
+        else:
+            self.log.error('No BT devices config is provided!')
+        # Turn off screen as all tests will be screen off
+        self.dut.droid.goToSleepNow()
+
+    def setup_test(self):
+
+        super().setup_test()
+        self.unpack_userparams(volume=0.9)
+        # Reset BT to factory defaults
+        self.dut.droid.bluetoothFactoryReset()
+        self.bt_device.reset()
+        self.bt_device.power_on()
+        btutils.enable_bluetooth(self.dut.droid, self.dut.ed)
+
+    def teardown_test(self):
+        """Tear down necessary objects after test case is finished.
+
+        Bring down the AP interface, delete the bridge interface, stop the
+        packet sender, and reset the ethernet interface for the packet sender
+        """
+        super().teardown_test()
+        self.dut.droid.bluetoothFactoryReset()
+        self.dut.adb.shell(BLE_LOCATION_SCAN_DISABLE)
+        if hasattr(self, 'media'):
+            self.media.stop()
+        # Set Attenuator to the initial attenuation
+        if hasattr(self, 'attenuators'):
+            self.set_attenuation(INIT_ATTEN)
+        self.bt_device.reset()
+        self.bt_device.power_off()
+        btutils.disable_bluetooth(self.dut.droid)
+
+    def teardown_class(self):
+        """Clean up the test class after tests finish running
+
+        """
+        super().teardown_class()
+        self.dut.droid.bluetoothFactoryReset()
+        self.dut.adb.shell(BLE_LOCATION_SCAN_DISABLE)
+        self.bt_device.reset()
+        self.bt_device.power_off()
+        btutils.disable_bluetooth(self.dut.droid)
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
new file mode 100644
index 0000000..0419290
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/PowerBaseTest.py
@@ -0,0 +1,502 @@
+#!/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 json
+import logging
+import math
+import os
+import re
+import time
+
+import acts.controllers.power_monitor as power_monitor_lib
+import acts.controllers.iperf_server as ipf
+from acts import asserts
+from acts import base_test
+from acts import utils
+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
+THRESHOLD_TOLERANCE_DEFAULT = 0.2
+GET_FROM_PHONE = 'get_from_dut'
+GET_FROM_AP = 'get_from_ap'
+PHONE_BATTERY_VOLTAGE_DEFAULT = 4.2
+MONSOON_MAX_CURRENT = 8.0
+DEFAULT_MONSOON_FREQUENCY = 500
+ENABLED_MODULATED_DTIM = 'gEnableModulatedDTIM='
+MAX_MODULATED_DTIM = 'gMaxLIModulatedDTIM='
+TEMP_FILE = '/sdcard/Download/tmp.log'
+
+
+class ObjNew(object):
+    """Create a random obj with unknown attributes and value.
+
+    """
+    def __init__(self, **kwargs):
+        self.__dict__.update(kwargs)
+
+    def __contains__(self, item):
+        """Function to check if one attribute is contained in the object.
+
+        Args:
+            item: the item to check
+        Return:
+            True/False
+        """
+        return hasattr(self, item)
+
+
+class PowerBaseTest(base_test.BaseTestClass):
+    """Base class for all wireless power related tests.
+
+    """
+    def __init__(self, controllers):
+
+        base_test.BaseTestClass.__init__(self, controllers)
+        self.power_result = BlackboxMetricLogger.for_test_case(
+            metric_name='avg_power')
+        self.start_meas_time = 0
+        self.rockbottom_script = None
+        self.img_name = ''
+        self.dut = None
+        self.power_logger = PowerMetricLogger.for_test_case()
+        self.power_monitor = None
+
+    @property
+    def final_test(self):
+        return len(
+            self.results.requested
+        ) > 0 and self.current_test_name == self.results.requested[-1]
+
+    @property
+    def display_name_test_suite(self):
+        return getattr(self, '_display_name_test_suite',
+                       self.__class__.__name__)
+
+    @display_name_test_suite.setter
+    def display_name_test_suite(self, name):
+        self._display_name_test_suite = name
+
+    @property
+    def display_name_test_case(self):
+        default_test_name = getattr(self, 'test_name', None)
+        return getattr(self, '_display_name_test_case', default_test_name)
+
+    @display_name_test_case.setter
+    def display_name_test_case(self, name):
+        self._display_name_test_case = name
+
+    def initialize_power_monitor(self):
+        """ Initializes the power monitor object.
+
+        Raises an exception if there are no controllers available.
+        """
+        if hasattr(self, 'monsoons'):
+            self.power_monitor = power_monitor_lib.PowerMonitorMonsoonFacade(
+                self.monsoons[0])
+            self.monsoons[0].set_max_current(8.0)
+            self.monsoons[0].set_voltage(self.mon_voltage)
+        else:
+            raise RuntimeError('No power monitors available.')
+
+    def setup_class(self):
+
+        self.log = logging.getLogger()
+        self.tests = self.get_existing_test_names()
+
+        # Obtain test parameters from user_params
+        TEST_PARAMS = self.TAG + '_params'
+        self.test_params = self.user_params.get(TEST_PARAMS, {})
+        if not self.test_params:
+            self.log.warning(TEST_PARAMS + ' was not found in the user '
+                             'parameters defined in the config file.')
+
+        # Override user_param values with test parameters
+        self.user_params.update(self.test_params)
+
+        # Unpack user_params with default values. All the usages of user_params
+        # as self attributes need to be included either as a required parameter
+        # or as a parameter with a default value.
+        req_params = ['custom_files', 'mon_duration']
+        self.unpack_userparams(req_params,
+                               mon_freq=DEFAULT_MONSOON_FREQUENCY,
+                               mon_offset=0,
+                               bug_report=False,
+                               extra_wait=None,
+                               iperf_duration=None,
+                               pass_fail_tolerance=THRESHOLD_TOLERANCE_DEFAULT,
+                               mon_voltage=PHONE_BATTERY_VOLTAGE_DEFAULT)
+
+        # Setup the must have controllers, phone and monsoon
+        self.dut = self.android_devices[0]
+        self.mon_data_path = os.path.join(self.log_path, 'Monsoon')
+        os.makedirs(self.mon_data_path, exist_ok=True)
+
+        # Initialize the power monitor object that will be used to measure
+        self.initialize_power_monitor()
+
+        # Unpack the thresholds file or fail class setup if it can't be found
+        for file in self.custom_files:
+            if 'pass_fail_threshold_' + self.dut.model in file:
+                self.threshold_file = file
+                break
+        else:
+            raise RuntimeError('Required test pass/fail threshold file is '
+                               'missing')
+
+        # Unpack the rockbottom script or fail class setup if it can't be found
+        for file in self.custom_files:
+            if 'rockbottom_' + self.dut.model in file:
+                self.rockbottom_script = file
+                break
+        else:
+            raise RuntimeError('Required rockbottom script is missing.')
+
+        # Unpack optional custom files
+        for file in self.custom_files:
+            if 'attenuator_setting' in file:
+                self.attenuation_file = file
+            elif 'network_config' in file:
+                self.network_file = file
+
+        if hasattr(self, 'attenuators'):
+            self.num_atten = self.attenuators[0].instrument.num_atten
+            self.atten_level = self.unpack_custom_file(self.attenuation_file)
+        self.threshold = self.unpack_custom_file(self.threshold_file)
+        self.mon_info = self.create_monsoon_info()
+
+        # 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:
+            img_src = screen_on_img[0]
+            img_dest = '/sdcard/Pictures/'
+            success = self.dut.push_system_file(img_src, img_dest)
+            if success:
+                self.img_name = os.path.basename(img_src)
+
+    def setup_test(self):
+        """Set up test specific parameters or configs.
+
+        """
+        # Reset result variables
+        self.avg_current = 0
+        self.samples = []
+        self.power_result.metric_value = 0
+
+        # 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:
+            self.more_wait_first_test()
+
+    def teardown_test(self):
+        """Tear down necessary objects after test case is finished.
+
+        """
+        self.log.info('Tearing down the test case')
+        self.power_monitor.connect_usb()
+        self.power_logger.set_avg_power(self.power_result.metric_value)
+        self.power_logger.set_avg_current(self.avg_current)
+        self.power_logger.set_voltage(self.mon_voltage)
+        self.power_logger.set_testbed(self.testbed_name)
+
+        # If a threshold was provided, log it in the power proto
+        if self.threshold and self.test_name in self.threshold:
+            avg_current_threshold = self.threshold[self.test_name]
+            self.power_logger.set_avg_current_threshold(avg_current_threshold)
+
+        build_id = self.dut.build_info.get('build_id', '')
+        incr_build_id = self.dut.build_info.get('incremental_build_id', '')
+        branch = self.user_params.get('branch', '')
+        target = self.dut.device_info.get('flavor', '')
+
+        self.power_logger.set_branch(branch)
+        self.power_logger.set_build_id(build_id)
+        self.power_logger.set_incremental_build_id(incr_build_id)
+        self.power_logger.set_target(target)
+
+        # Log the display name of the test suite and test case
+        if self.display_name_test_suite:
+            name = self.display_name_test_suite
+            self.power_logger.set_test_suite_display_name(name)
+
+        if self.display_name_test_case:
+            name = self.display_name_test_case
+            self.power_logger.set_test_case_display_name(name)
+
+        # Take Bugreport
+        if self.bug_report:
+            begin_time = utils.get_current_epoch_time()
+            self.dut.take_bug_report(self.test_name, begin_time)
+
+        # Allow the device to cooldown before executing the next test
+        cooldown = self.test_params.get('cooldown', None)
+        if cooldown and not self.final_test:
+            time.sleep(cooldown)
+
+    def teardown_class(self):
+        """Clean up the test class after tests finish running
+
+        """
+        self.log.info('Tearing down the test class')
+        if self.power_monitor:
+            self.power_monitor.connect_usb()
+
+    def on_fail(self, test_name, begin_time):
+        self.power_logger.set_pass_fail_status('FAIL')
+
+    def on_pass(self, test_name, begin_time):
+        self.power_logger.set_pass_fail_status('PASS')
+
+    def dut_rockbottom(self):
+        """Set the dut to rockbottom state
+
+        """
+        # The rockbottom script might include a device reboot, so it is
+        # necessary to stop SL4A during its execution.
+        self.dut.stop_services()
+        self.log.info('Executing rockbottom script for ' + self.dut.model)
+        os.chmod(self.rockbottom_script, 0o777)
+        os.system('{} {} {}'.format(self.rockbottom_script, self.dut.serial,
+                                    self.img_name))
+        # Make sure the DUT is in root mode after coming back
+        self.dut.root_adb()
+        # Restart SL4A
+        self.dut.start_services()
+
+    def unpack_custom_file(self, file, test_specific=True):
+        """Unpack the pass_fail_thresholds from a common 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
+
+    def decode_test_configs(self, attrs, indices):
+        """Decode the test config/params from test name.
+
+        Remove redundant function calls when tests are similar.
+        Args:
+            attrs: a list of the attrs of the test config obj
+            indices: a list of the location indices of keyword in the test name.
+        """
+        # Decode test parameters for the current test
+        test_params = self.current_test_name.split('_')
+        values = [test_params[x] for x in indices]
+        config_dict = dict(zip(attrs, values))
+        self.test_configs = ObjNew(**config_dict)
+
+    def more_wait_first_test(self):
+        # For the first test, increase the offset for longer wait time
+        if self.current_test_name == self.tests[0]:
+            self.mon_info.offset = self.mon_offset + self.extra_wait
+        else:
+            self.mon_info.offset = self.mon_offset
+
+    def set_attenuation(self, atten_list):
+        """Function to set the attenuator to desired attenuations.
+
+        Args:
+            atten_list: list containing the attenuation for each attenuator.
+        """
+        if len(atten_list) != self.num_atten:
+            raise Exception('List given does not have the correct length')
+        for i in range(self.num_atten):
+            self.attenuators[i].set_atten(atten_list[i])
+
+    def measure_power_and_validate(self):
+        """The actual test flow and result processing and validate.
+
+        """
+        self.collect_power_data()
+        self.pass_fail_check(self.avg_current)
+
+    def collect_power_data(self):
+        """Measure power, plot and take log if needed.
+
+        Returns:
+            A MonsoonResult object.
+        """
+        # Collecting current measurement data and plot
+        samples = self.power_monitor_data_collect_save()
+
+        current = [sample[1] for sample in samples]
+        average_current = sum(current) * 1000 / len(current)
+
+        self.power_result.metric_value = (average_current * self.mon_voltage)
+        self.avg_current = average_current
+
+        plot_title = '{}_{}_{}'.format(self.test_name, self.dut.model,
+                                       self.dut.build_info['build_id'])
+        plot_utils.current_waveform_plot(samples, self.mon_voltage,
+                                         self.mon_info.data_path, plot_title)
+
+        return samples
+
+    def pass_fail_check(self, average_current=None):
+        """Check the test result and decide if it passed or failed.
+
+        The threshold is provided in the config file. In this class, result is
+        current in mA.
+        """
+
+        if not self.threshold or self.test_name not in self.threshold:
+            self.log.error("No threshold is provided for the test '{}' in "
+                           "the configuration file.".format(self.test_name))
+            return
+
+        current_threshold = self.threshold[self.test_name]
+        if average_current:
+            asserts.assert_true(
+                abs(average_current - current_threshold) / current_threshold <
+                self.pass_fail_tolerance,
+                'Measured average current in [{}]: {:.2f}mA, which is '
+                'out of the acceptable range {:.2f}±{:.2f}mA'.format(
+                    self.test_name, average_current, current_threshold,
+                    self.pass_fail_tolerance * current_threshold))
+            asserts.explicit_pass(
+                'Measurement finished for [{}]: {:.2f}mA, which is '
+                'within the acceptable range {:.2f}±{:.2f}'.format(
+                    self.test_name, average_current, current_threshold,
+                    self.pass_fail_tolerance * current_threshold))
+        else:
+            asserts.fail(
+                'Something happened, measurement is not complete, test failed')
+
+    def create_monsoon_info(self):
+        """Creates the config dictionary for monsoon
+
+        Returns:
+            mon_info: Dictionary with the monsoon packet config
+        """
+        mon_info = ObjNew(freq=self.mon_freq,
+                          duration=self.mon_duration,
+                          offset=self.mon_offset,
+                          data_path=self.mon_data_path)
+        return mon_info
+
+    def power_monitor_data_collect_save(self):
+        """Current measurement and save the log file.
+
+        Collect current data using Monsoon box and return the path of the
+        log file. Take bug report if requested.
+
+        Returns:
+            A list of tuples in which the first element is a timestamp and the
+            second element is the sampled current in Amperes at that time.
+        """
+
+        tag = '{}_{}_{}'.format(self.test_name, self.dut.model,
+                                self.dut.build_info['build_id'])
+
+        data_path = os.path.join(self.mon_info.data_path, '{}.txt'.format(tag))
+
+        # If the specified Monsoon data file already exists (e.g., multiple
+        # measurements in a single test), write the results to a new file with
+        # the postfix "_#".
+        if os.path.exists(data_path):
+            highest_value = 1
+            for filename in os.listdir(os.path.dirname(data_path)):
+                match = re.match(r'{}_(\d+).txt'.format(tag), filename)
+                if match:
+                    highest_value = max(highest_value, int(match.group(1)))
+
+            data_path = os.path.join(self.mon_info.data_path,
+                                     '%s_%s.txt' % (tag, highest_value + 1))
+
+        # Resets the battery status right before the test starts.
+        self.dut.adb.shell(RESET_BATTERY_STATS)
+        self.log.info('Starting power measurement. Duration: {}s. Offset: '
+                      '{}s. Voltage: {} V.'.format(self.mon_info.duration,
+                                                   self.mon_info.offset,
+                                                   self.mon_voltage))
+
+        # TODO(b/155426729): Create an accurate host-to-device time difference
+        # measurement.
+        device_time_cmd = 'echo $EPOCHREALTIME'
+        device_time = self.dut.adb.shell(device_time_cmd)
+        host_time = time.time()
+        self.log.debug('device start time %s, host start time %s', device_time,
+                       host_time)
+        device_to_host_offset = float(device_time) - host_time
+
+        # Start the power measurement using monsoon.
+        self.dut.stop_services()
+        time.sleep(1)
+        self.power_monitor.disconnect_usb()
+        measurement_args = dict(duration=self.mon_info.duration,
+                                measure_after_seconds=self.mon_info.offset,
+                                hz=self.mon_info.freq)
+        self.power_monitor.measure(measurement_args=measurement_args,
+                                   start_time=device_to_host_offset,
+                                   output_path=data_path)
+        self.power_monitor.connect_usb()
+        self.dut.wait_for_boot_completion()
+        time.sleep(10)
+        self.dut.start_services()
+
+        return self.power_monitor.get_battery_waveform(
+            monsoon_file_path=data_path)
+
+    def process_iperf_results(self):
+        """Get the iperf results and process.
+
+        Returns:
+             throughput: the average throughput during tests.
+        """
+        # Get IPERF results and add this to the plot title
+        RESULTS_DESTINATION = os.path.join(
+            self.iperf_server.log_path,
+            'iperf_client_output_{}.log'.format(self.current_test_name))
+        self.dut.pull_files(TEMP_FILE, RESULTS_DESTINATION)
+        # Calculate the average throughput
+        if self.use_client_output:
+            iperf_file = RESULTS_DESTINATION
+        else:
+            iperf_file = self.iperf_server.log_files[-1]
+        try:
+            iperf_result = ipf.IPerfResult(iperf_file)
+
+            # Compute the throughput in Mbit/s
+            throughput = (math.fsum(
+                iperf_result.instantaneous_rates[self.start_meas_time:-1]
+            ) / len(iperf_result.instantaneous_rates[self.start_meas_time:-1])
+                          ) * 8 * (1.024**2)
+
+            self.log.info('The average throughput is {}'.format(throughput))
+        except ValueError:
+            self.log.warning('Cannot get iperf result. Setting to 0')
+            throughput = 0
+        return throughput
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerCoexBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerCoexBaseTest.py
new file mode 100644
index 0000000..8142f82
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/PowerCoexBaseTest.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+8
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT 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.PowerBTBaseTest as PBtBT
+import acts_contrib.test_utils.power.PowerWiFiBaseTest as PWBT
+from acts import utils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+
+
+class PowerCoexBaseTest(PBtBT.PowerBTBaseTest, PWBT.PowerWiFiBaseTest):
+    """Base class for BT power related tests.
+
+    Inherited from the PowerBaseTest class
+    """
+    def coex_test_phone_setup(self, Screen_status, WiFi_status, WiFi_band,
+                              BT_status, BLE_status, Cellular_status,
+                              Celluar_band):
+        """Setup the phone in desired state for coex tests.
+
+        Args:
+            Screen_status: 'ON' or 'OFF'
+            WiFi_status: 'ON', 'Connected', 'Disconnected', or 'OFF'
+            WiFi_band: '2g', '5g' or None, the band of AP
+            BT_status: 'ON' or 'OFF'
+            BLE_status: 'ON' or 'OFF'
+            Cellular_status: 'ON' or 'OFF'
+            Celluar_band: 'Verizon', 'Tmobile', or 'ATT' for live network,
+                actual band for callbox setup; 'None' when celluar is OFF
+        """
+        # Setup WiFi
+        if WiFi_status == 'ON':
+            wutils.wifi_toggle_state(self.dut, True)
+        elif WiFi_status == 'Connected':
+            self.setup_ap_connection(self.main_network[WiFi_band])
+        elif WiFi_status == 'Disconnected':
+            self.setup_ap_connection(self.main_network[WiFi_band],
+                                     connect=False)
+
+        # Setup BT/BLE
+        self.phone_setup_for_BT(BT_status, BLE_status, Screen_status)
+
+        # Setup Cellular
+        if Cellular_status == 'ON':
+            self.dut.droid.connectivityToggleAirplaneMode(False)
+            utils.set_mobile_data_always_on(self.dut, True)
+
+    def coex_scan_setup(self, WiFi_scan, BLE_scan_mode, wifi_scan_command):
+        """Setup for scan activities on WiFi, BT/BLE, and cellular.
+
+        Args:
+            WiFi_scan: 'ON', 'OFF' or 'PNO'
+            BLE_scan_mode: 'balanced', 'opportunistic', 'low_power', or 'low_latency'
+        """
+        if WiFi_scan == 'ON':
+            self.dut.adb.shell(wifi_scan_command)
+        if WiFi_scan == 'PNO':
+            self.log.info(
+                'Set attenuation so device loses connection to trigger PNO scans'
+            )
+            # Set to maximum attenuation 95 dB to cut down connection
+            [self.attenuators[i].set_atten(95) for i in range(self.num_atten)]
+        if BLE_scan_mode is not None:
+            self.start_pmc_ble_scan(BLE_scan_mode, self.mon_info.offset,
+                                    self.mon_info.duration)
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerGTWGnssBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerGTWGnssBaseTest.py
new file mode 100644
index 0000000..63c59db
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/PowerGTWGnssBaseTest.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+import time
+
+from acts import signals
+from acts import utils
+from acts_contrib.test_utils.power.PowerBaseTest import PowerBaseTest
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+
+DEFAULT_WAIT_TIME = 120
+STANDALONE_WAIT_TIME = 1200
+DPO_NV_VALUE = '15DC'
+MDS_TEST_PACKAGE = 'com.google.mdstest'
+MDS_RUNNER = 'com.google.mdstest.instrument.ModemConfigInstrumentation'
+
+
+class PowerGTWGnssBaseTest(PowerBaseTest):
+    """Power GTW Gnss Base test"""
+
+    def setup_class(self):
+        super().setup_class()
+        self.ad = self.android_devices[0]
+        req_params = [
+            'wifi_network', 'test_location', 'qdsp6m_path',
+            'calibrate_target'
+        ]
+        self.unpack_userparams(req_param_names=req_params)
+        gutils.disable_xtra_throttle(self.ad)
+
+    def setup_test(self):
+        super().setup_test()
+        # Enable GNSS setting for GNSS standalone mode
+        self.ad.adb.shell('settings put secure location_mode 3')
+
+    def teardown_test(self):
+        begin_time = utils.get_current_epoch_time()
+        self.ad.take_bug_report(self.test_name, begin_time)
+        gutils.get_gnss_qxdm_log(self.ad, self.qdsp6m_path)
+
+    def baseline_test(self):
+        """Baseline power measurement"""
+        self.ad.droid.goToSleepNow()
+        self.collect_power_data()
+        self.ad.log.info('TestResult AVG_Current %.2f' % self.avg_current)
+
+    def start_gnss_tracking_with_power_data(self,
+                                            mode='default',
+                                            is_signal=True,
+                                            freq=0,
+                                            lowpower=False,
+                                            meas=False):
+        """Start GNSS tracking and collect power metrics.
+
+        Args:
+            is_signal: default True, False for no Gnss signal test.
+            freq: an integer to set location update frequency.
+            lowpower: a boolean to set GNSS Low Power Mode.
+            mean: a boolean to set GNSS Measurement registeration.
+        """
+        self.ad.adb.shell('settings put secure location_mode 3')
+        gutils.clear_aiding_data_by_gtw_gpstool(self.ad)
+        gutils.start_gnss_by_gtw_gpstool(self.ad, True, 'gnss', True, freq,
+                                         lowpower, meas)
+        self.ad.droid.goToSleepNow()
+
+        sv_collecting_time = DEFAULT_WAIT_TIME
+        if mode == 'standalone':
+            sv_collecting_time = STANDALONE_WAIT_TIME
+        self.ad.log.info('Wait %d seconds for %s mode' %
+                         (sv_collecting_time, mode))
+        time.sleep(sv_collecting_time)
+
+        samples = self.collect_power_data()
+        self.ad.log.info('TestResult AVG_Current %.2f' % self.avg_current)
+        self.calibrate_avg_current(samples)
+        self.ad.send_keycode('WAKEUP')
+        gutils.start_gnss_by_gtw_gpstool(self.ad, False, 'gnss')
+        if is_signal:
+            gutils.parse_gtw_gpstool_log(
+                self.ad, self.test_location, type='gnss')
+
+    def calibrate_avg_current(self, samples):
+        """Calibrate average current by filtering AP wake up current with target
+           value.
+
+        Args:
+            samples: a list of tuples where the first element is a timestamp
+            and the second element is a current sample.
+        """
+        calibrate_results = [
+            sample[1] * 1000 for sample in samples
+            if sample[1] * 1000 < self.calibrate_target
+        ]
+        avg_current = sum(calibrate_results) / len(calibrate_results)
+        self.ad.log.info('TestResult Calibrate_AVG_Current %.2f' % avg_current)
+
+    def enable_DPO(self, enable):
+        """Enable or disable the DPO option.
+
+        Args:
+            enable: True or False to enable DPO.
+        """
+        self.ad.log.info('Change DPO to new state: %s.' % enable)
+        val = '02' if enable else '00'
+        options = {'request': 'writeNV', 'item': DPO_NV_VALUE, 'data': val}
+        instrument_cmd = gutils.build_instrumentation_call(
+            MDS_TEST_PACKAGE, MDS_RUNNER, options=options)
+        result = self.ad.adb.shell(instrument_cmd)
+        if 'SUCCESS' not in result:
+            self.ad.log.info(result)
+            raise signals.TestFailure('DPO is not able to Turn: %s' % enable)
+        self.dut_rockbottom()
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerGnssBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerGnssBaseTest.py
new file mode 100644
index 0000000..36ab5fe
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/PowerGnssBaseTest.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import time
+
+import os
+
+import acts_contrib.test_utils.power.PowerBaseTest as PBT
+
+from acts import base_test
+from acts.controllers import monsoon
+from bokeh.layouts import column, layout
+from bokeh.models import CustomJS, ColumnDataSource
+from bokeh.models import tools as bokeh_tools
+from bokeh.models.widgets import DataTable, TableColumn
+from bokeh.plotting import figure, output_file, save
+from acts.controllers.monsoon_lib.api.common import PassthroughStates
+from acts.controllers.monsoon_lib.api.common import MonsoonError
+
+LOGTIME_RETRY_COUNT = 3
+RESET_BATTERY_STATS = 'dumpsys batterystats --reset'
+RECOVER_MONSOON_RETRY_COUNT = 3
+MONSOON_RETRY_INTERVAL = 300
+
+class PowerGnssBaseTest(PBT.PowerBaseTest):
+    """
+    Base Class for all GNSS Power related tests
+    """
+
+    def setup_class(self):
+        super().setup_class()
+        req_params = ['customjsfile', 'maskfile']
+        self.unpack_userparams(req_params)
+
+    def collect_power_data(self):
+        """Measure power and plot."""
+        samples = super().collect_power_data()
+        plot_title = '{}_{}_{}_Power'.format(self.test_name, self.dut.model,
+                                             self.dut.build_info['build_id'])
+        self.monsoon_data_plot_power(samples, self.mon_voltage,
+                                     self.mon_info.data_path, plot_title)
+        return samples
+
+    def monsoon_data_plot_power(self, samples, voltage, dest_path, plot_title):
+        """Plot the monsoon power data using bokeh interactive plotting tool.
+
+        Args:
+            samples: a list of tuples in which the first element is a timestamp
+            and the second element is the sampled current at that time
+            voltage: the voltage that was used during the measurement
+            dest_path: destination path
+            plot_title: a filename and title for the plot.
+
+        """
+
+        logging.info('Plotting the power measurement data.')
+
+        time_relative = [sample[0] for sample in samples]
+        duration = time_relative[-1] - time_relative[0]
+        current_data = [sample[1] * 1000 for sample in samples]
+        avg_current = sum(current_data) / len(current_data)
+
+        power_data = [current * voltage for current in current_data]
+
+        color = ['navy'] * len(samples)
+
+        # Preparing the data and source link for bokehn java callback
+        source = ColumnDataSource(
+            data=dict(x0=time_relative, y0=power_data, color=color))
+        s2 = ColumnDataSource(
+            data=dict(
+                z0=[duration],
+                y0=[round(avg_current, 2)],
+                x0=[round(avg_current * voltage, 2)],
+                z1=[round(avg_current * voltage * duration, 2)],
+                z2=[round(avg_current * duration, 2)]))
+        # Setting up data table for the output
+        columns = [
+            TableColumn(field='z0', title='Total Duration (s)'),
+            TableColumn(field='y0', title='Average Current (mA)'),
+            TableColumn(field='x0', title='Average Power (4.2v) (mW)'),
+            TableColumn(field='z1', title='Average Energy (mW*s)'),
+            TableColumn(field='z2', title='Normalized Average Energy (mA*s)')
+        ]
+        dt = DataTable(
+            source=s2, columns=columns, width=1300, height=60, editable=True)
+
+        output_file(os.path.join(dest_path, plot_title + '.html'))
+        tools = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save'
+        # Create a new plot with the datatable above
+        plot = figure(
+            plot_width=1300,
+            plot_height=700,
+            title=plot_title,
+            tools=tools,
+            output_backend='webgl')
+        plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='width'))
+        plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='height'))
+        plot.line('x0', 'y0', source=source, line_width=2)
+        plot.circle('x0', 'y0', source=source, size=0.5, fill_color='color')
+        plot.xaxis.axis_label = 'Time (s)'
+        plot.yaxis.axis_label = 'Power (mW)'
+        plot.title.text_font_size = {'value': '15pt'}
+        jsscript = open(self.customjsfile, 'r')
+        customjsscript = jsscript.read()
+        # Callback Java scripting
+        source.callback = CustomJS(
+            args=dict(mytable=dt),
+            code=customjsscript)
+
+        # Layout the plot and the datatable bar
+        save(layout([[dt], [plot]]))
+
+    def disconnect_usb(self, ad, sleeptime):
+        """Disconnect usb while device is on sleep and
+        connect the usb again once the sleep time completes
+
+        sleeptime: sleep time where dut is disconnected from usb
+        """
+        self.dut.adb.shell(RESET_BATTERY_STATS)
+        time.sleep(1)
+        for _ in range(LOGTIME_RETRY_COUNT):
+            self.monsoons[0].usb(PassthroughStates.OFF)
+            if not ad.is_connected():
+                time.sleep(sleeptime)
+                self.monsoons[0].usb(PassthroughStates.ON)
+                break
+        else:
+            self.log.error('Test failed after maximum retry')
+            for _ in range(RECOVER_MONSOON_RETRY_COUNT):
+                if self.monsoon_recover():
+                    break
+                else:
+                    self.log.warning(
+                        'Wait for {} second then try again'.format(
+                            MONSOON_RETRY_INTERVAL))
+                    time.sleep(MONSOON_RETRY_INTERVAL)
+            else:
+                self.log.error('Failed to recover monsoon')
+
+    def monsoon_recover(self):
+        """Test loop to wait for monsoon recover from unexpected error.
+
+        Wait for a certain time duration, then quit.0
+        Args:
+            mon: monsoon object
+        Returns:
+            True/False
+        """
+        try:
+            self.power_monitor.connect_usb()
+            logging.info('Monsoon recovered from unexpected error')
+            time.sleep(2)
+            return True
+        except MonsoonError:
+            try:
+                self.log.info(self.monsoons[0]._mon.ser.in_waiting)
+            except AttributeError:
+                # This attribute does not exist for HVPMs.
+                pass
+            logging.warning('Unable to recover monsoon from unexpected error')
+            return False
diff --git a/acts_tests/acts_contrib/test_utils/power/PowerWiFiBaseTest.py b/acts_tests/acts_contrib/test_utils/power/PowerWiFiBaseTest.py
new file mode 100644
index 0000000..7ca573e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/PowerWiFiBaseTest.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts_contrib.test_utils.power.PowerBaseTest as PBT
+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 import plot_utils
+
+IPERF_DURATION = 'iperf_duration'
+INITIAL_ATTEN = [0, 0, 90, 90]
+IPERF_TAIL = 5
+
+
+class PowerWiFiBaseTest(PBT.PowerBaseTest):
+    """Base class for WiFi power related tests.
+
+    Inherited from the PowerBaseTest class
+    """
+    def setup_class(self):
+
+        super().setup_class()
+        if hasattr(self, 'access_points'):
+            self.access_point = self.access_points[0]
+            self.access_point_main = self.access_points[0]
+            if len(self.access_points) > 1:
+                self.access_point_aux = self.access_points[1]
+        if hasattr(self, 'attenuators'):
+            self.set_attenuation(INITIAL_ATTEN)
+        if hasattr(self, 'network_file'):
+            self.networks = self.unpack_custom_file(self.network_file, False)
+            self.main_network = self.networks['main_network']
+            self.aux_network = self.networks['aux_network']
+        if hasattr(self, 'packet_senders'):
+            self.pkt_sender = self.packet_senders[0]
+        if hasattr(self, 'iperf_servers'):
+            self.iperf_server = self.iperf_servers[0]
+        if self.iperf_duration:
+            self.mon_duration = self.iperf_duration - self.mon_offset - IPERF_TAIL
+            self.create_monsoon_info()
+
+    def teardown_test(self):
+        """Tear down necessary objects after test case is finished.
+
+        Bring down the AP interface, delete the bridge interface, stop the
+        packet sender, and reset the ethernet interface for the packet sender
+        """
+        super().teardown_test()
+        if hasattr(self, 'pkt_sender'):
+            self._safe_teardown('pkt_sender stop sending',
+                                self.pkt_sender.stop_sending,
+                                ignore_status=True)
+        if hasattr(self, 'brconfigs'):
+            self._safe_teardown('brconfigs', self.access_point.bridge.teardown,
+                                self.brconfigs)
+            delattr(self, 'brconfigs')
+        if hasattr(self, 'brconfigs_main'):
+            self._safe_teardown('brconfigs_main',
+                                self.access_point_main.bridge.teardown,
+                                self.brconfigs_main)
+            delattr(self, 'brconfigs_main')
+        if hasattr(self, 'brconfigs_aux'):
+            self._safe_teardown('brconfigs_aux',
+                                self.access_point_aux.bridge.teardown,
+                                self.brconfigs_aux)
+            delattr(self, 'brconfigs_aux')
+        if hasattr(self, 'access_points'):
+            for ap in self.access_points:
+                self._safe_teardown('access point {}'.format(ap.identifier),
+                                    ap.close)
+        if hasattr(self, 'pkt_sender'):
+            self._safe_teardown('pkt_sender reset host interface',
+                                wputils.reset_host_interface,
+                                self.pkt_sender.interface)
+        if hasattr(self, 'iperf_server'):
+            self._safe_teardown('iperf_server', self.iperf_server.stop);
+
+    def _safe_teardown(self, attr, teardown_method, *arg, **kwargs):
+        """Teardown the object with try block.
+
+        Adds a try block for each teardown step to make sure that each
+        teardown step is executed.
+
+        Args:
+            attr: the teardown attribute description for logging
+            teardown_method: the method for teardown
+            *arg: positional arguments for teardown_method
+            **kwargs: keyword arguments for teardown_method
+        """
+        try:
+            self.log.info('teardown %s with %s', attr, teardown_method.__name__)
+            teardown_method(*arg, **kwargs)
+        except Exception as e:
+            self.log.warning('teardown of %s fails with %s', attr, e)
+
+    def teardown_class(self):
+        """Clean up the test class after tests finish running
+
+        """
+        super().teardown_class()
+        if hasattr(self, 'access_points'):
+            for ap in self.access_points:
+                ap.close()
+
+    def setup_ap_connection(self,
+                            network,
+                            bandwidth=80,
+                            connect=True,
+                            ap=None):
+        """Setup AP and connect DUT to it.
+
+        Args:
+            network: the network config for the AP to be setup
+            bandwidth: bandwidth of the WiFi network to be setup
+            connect: indicator of if connect dut to the network after setup
+            ap: access point object, default is None to find the main AP
+        Returns:
+            self.brconfigs: dict for bridge interface configs
+        """
+        wutils.wifi_toggle_state(self.dut, True)
+        if not ap:
+            if hasattr(self, 'access_points'):
+                self.brconfigs = wputils.ap_setup(self.access_point,
+                                                  network,
+                                                  bandwidth=bandwidth)
+        else:
+            self.brconfigs = wputils.ap_setup(ap, network, bandwidth=bandwidth)
+        if connect:
+            wutils.wifi_connect(self.dut, network, num_of_tries=3)
+
+        if ap or (not ap and hasattr(self, 'access_points')):
+            return self.brconfigs
+
+    def collect_power_data(self):
+        """Measure power, plot and check pass/fail.
+
+        If IPERF is run, need to pull iperf results and attach it to the plot.
+        """
+        samples = super().collect_power_data()
+        tag = ''
+        if self.iperf_duration:
+            throughput = self.process_iperf_results()
+            plot_title = '{}_{}_{}_RSSI_{0:d}dBm_Throughput_{1:.2f}Mbps'.format(
+                self.test_name, self.dut.model,
+                self.dut.build_info['build_id'], self.RSSI, throughput)
+            plot_utils.current_waveform_plot(samples, self.mon_voltage,
+                                             self.mon_info.data_path,
+                                             plot_title)
+        return samples
diff --git a/acts_tests/acts_contrib/test_utils/power/__init__.py b/acts_tests/acts_contrib/test_utils/power/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/__init__.py b/acts_tests/acts_contrib/test_utils/power/cellular/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/__init__.py
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
new file mode 100644
index 0000000..a4c2df6
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_hotspot_traffic_power_test.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+from acts_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.wifi import wifi_test_utils as wutils
+import acts_contrib.test_utils.power.cellular.cellular_traffic_power_test as ctpt
+
+
+class PowerTelHotspotTest(ctpt.PowerTelTrafficTest):
+    """ Cellular traffic over WiFi tethering power test.
+
+    Treated as a different case of data traffic. Inherits from
+    PowerTelTrafficTest and only needs to make a change in the measurement step.
+    """
+
+    # Class config parameters
+    CONFIG_KEY_WIFI = 'hotspot_network'
+
+    # Test name configuration keywords
+    PARAM_WIFI_BAND = "wifiband"
+    PARAM_2G_BAND = "2g"
+    PARAM_5G_BAND = "5g"
+
+    def __init__(self, controllers):
+        """ Class initialization
+
+        Set attributes to default values.
+        """
+
+        super().__init__(controllers)
+
+        # Initialize values
+        self.wifi_band = None
+        self.iperf_results = None
+
+    def setup_class(self):
+        """ Executed before any test case is started.
+
+        Set country code for client and host devices.
+
+        """
+
+        if not super().setup_class():
+            return False
+
+        # If an SSID and password are indicated in the configuration parameters,
+        # use those. If not, use default parameters and warn the user.
+
+        if hasattr(self, self.CONFIG_KEY_WIFI):
+
+            self.network = getattr(self, self.CONFIG_KEY_WIFI)
+
+            if not (wutils.WifiEnums.SSID_KEY in self.network
+                    and wutils.WifiEnums.PWD_KEY in self.network):
+                raise RuntimeError(
+                    "The '{}' key in the configuration file needs"
+                    " to contain the '{}' and '{}' fields.".format(
+                        self.CONFIG_KEY_WIFI, wutils.WifiEnums.SSID_KEY,
+                        wutils.WifiEnums.PWD_KEY))
+        else:
+
+            self.log.warning("The configuration file doesn't indicate an SSID "
+                             "password for the hotspot. Using default values. "
+                             "To configured the SSID and pwd include a the key"
+                             " {} containing the '{}' and '{}' fields.".format(
+                                 self.CONFIG_KEY_WIFI,
+                                 wutils.WifiEnums.SSID_KEY,
+                                 wutils.WifiEnums.PWD_KEY))
+
+            self.network = {
+                wutils.WifiEnums.SSID_KEY: "Pixel_1030",
+                wutils.WifiEnums.PWD_KEY: "1234567890"
+            }
+
+    def power_tel_tethering_test(self):
+        """ Measure power and throughput during data transmission.
+
+        Starts WiFi tethering in the DUT and connects a second device. Then
+        the iPerf client is hosted in the second android device.
+
+        """
+        # Country Code set to 00 after toggling airplane mode.
+        # We need to set this right before we setup a hotspot
+        # Set country codes on both devices to US to connect to 5GHz
+        country_code = "US"
+        hotspot_dut = self.dut
+        slave_dut = self.android_devices[1]
+        for dut in [hotspot_dut, slave_dut]:
+            self.log.info("Setting Country Code to %s for SN:%s" %
+                          (country_code, dut.serial))
+            wutils.set_wifi_country_code(dut, country_code)
+
+        # Setup tethering
+        wutils.start_wifi_tethering(self.dut,
+                                    self.network[wutils.WifiEnums.SSID_KEY],
+                                    self.network[wutils.WifiEnums.PWD_KEY],
+                                    self.wifi_band)
+
+        wutils.wifi_connect(self.android_devices[1],
+                            self.network,
+                            check_connectivity=False)
+
+        # Start data traffic
+        iperf_helpers = self.start_tel_traffic(self.android_devices[1])
+
+        # Measure power
+        self.collect_power_data()
+
+        # Wait for iPerf to finish
+        time.sleep(self.IPERF_MARGIN + 2)
+
+        # Collect throughput measurement
+        self.iperf_results = self.get_iperf_results(self.android_devices[1],
+                                                    iperf_helpers)
+
+        # Checks if power is below the required threshold.
+        self.pass_fail_check(self.avg_current)
+
+    def setup_test(self):
+        """ Executed before every test case.
+
+        Parses test configuration from the test name and prepares
+        the simulation for measurement.
+        """
+
+        # Call parent method first to setup simulation
+        if not super().setup_test():
+            return False
+
+        try:
+            values = self.consume_parameter(self.PARAM_WIFI_BAND, 1)
+
+            if values[1] == self.PARAM_2G_BAND:
+                self.wifi_band = WIFI_CONFIG_APBAND_2G
+            elif values[1] == self.PARAM_5G_BAND:
+                self.wifi_band = WIFI_CONFIG_APBAND_5G
+            else:
+                raise ValueError()
+        except:
+            self.log.error(
+                "The test name has to include parameter {} followed by "
+                "either {} or {}.".format(self.PARAM_WIFI_BAND,
+                                          self.PARAM_2G_BAND,
+                                          self.PARAM_5G_BAND))
+            return False
+
+        return True
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_idle_power_test.py b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_idle_power_test.py
new file mode 100644
index 0000000..72cb7ee
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_idle_power_test.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts_contrib.test_utils.power.cellular.cellular_power_base_test as PWCEL
+
+
+class PowerTelIdleTest(PWCEL.PowerCellularLabBaseTest):
+    """Cellular idle power test.
+
+    Inherits from PowerCellularLabBaseTest. Tests power consumption during
+    cellular idle scenarios to verify the ability to set power consumption
+    to a minimum during connectivity power tests.
+    """
+
+    TIME_SLOT_WINDOW_SECONDS = 1.280
+    FILTER_CURRENT_THRESHOLD = 20
+
+    def power_tel_idle_test(self, filter_results=False):
+        """ Measures power when the device is on LTE RRC idle state.
+
+        Args:
+            filter_results: when True the reported result is filtered to only
+                samples where average power was below a certain threshold.
+        """
+
+        idle_wait_time = self.simulation.rrc_sc_timer + 30
+
+        # Wait for RRC status change to trigger
+        self.cellular_simulator.wait_until_idle_state(idle_wait_time)
+
+        # Measure power
+        samples = self.collect_power_data()
+
+        # If necessary, replace the test result with the filtered metric
+        if filter_results:
+            self.avg_current = self.filter_for_idle_state(samples)
+            self.power_result.metric_value = self.avg_current * self.mon_voltage
+
+        # Check if power measurement is below the required value
+        self.pass_fail_check(self.avg_current)
+
+    def filter_for_idle_state(self, samples):
+        """ Process results and only take an average of time slots that are
+        below a certain threshold.
+
+        Args:
+            samples: a list of tuples in which the first element is a timestamp
+            and the second element is the sampled current in micro amps at that
+            time.
+        """
+        # Calculate the time slot duration in number of samples
+        slot_length = round(self.mon_freq * self.TIME_SLOT_WINDOW_SECONDS)
+
+        # Transform the currents from samples into milli_amps.
+        milli_amps = [sample[1] * 1000 for sample in samples]
+
+        filtered_slot_averages = []
+        for slot in range(int(len(milli_amps) / slot_length)):
+            # Calculate the average in this time slot
+            slot_start = slot_length * slot
+            slot_end = slot_start + slot_length
+            slot_average = sum(milli_amps[slot_start:slot_end]) / slot_length
+            # Only use time slots in which the average was below the threshold
+            if slot_average <= self.FILTER_CURRENT_THRESHOLD:
+                filtered_slot_averages.append(slot_average)
+
+        if filtered_slot_averages:
+            # Calculate the average from all the filtered slots
+            result = sum(filtered_slot_averages) / len(filtered_slot_averages)
+            self.log.info(
+                "The {} s window average current was below {} mA "
+                "for {} s. During that time the average current was {} mA.".
+                format(
+                    self.TIME_SLOT_WINDOW_SECONDS,
+                    self.FILTER_CURRENT_THRESHOLD,
+                    self.TIME_SLOT_WINDOW_SECONDS *
+                    len(filtered_slot_averages), result))
+            return result
+        else:
+            self.log.error("The device was not in idle state for the whole "
+                           "duration of the test.")
+            return 0
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_pdcch_power_test.py b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_pdcch_power_test.py
new file mode 100644
index 0000000..c071c5a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_pdcch_power_test.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import acts_contrib.test_utils.power.cellular.cellular_power_base_test as base_test
+
+
+class PowerTelPDCCHTest(base_test.PowerCellularLabBaseTest):
+    """ PDCCH only power test.
+
+    In this test the UE is only listening and decoding the PDCCH channel. """
+    def power_pdcch_test(self):
+        """ Measures power during PDCCH only.
+
+        There's nothing to do here other than starting the power measurement
+        and deciding for pass or fail, as the base class will handle attaching.
+        Requirements for this test are that mac padding is off and that the
+        inactivity timer is not enabled. """
+
+        # Measure power
+        self.collect_power_data()
+
+        # Check if power measurement is within the required values
+        self.pass_fail_check(self.avg_current)
diff --git a/acts_tests/acts_contrib/test_utils/power/cellular/cellular_power_base_test.py b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_power_base_test.py
new file mode 100644
index 0000000..adc49b7
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_power_base_test.py
@@ -0,0 +1,421 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+import time
+import os
+
+import acts_contrib.test_utils.power.PowerBaseTest as PBT
+import acts.controllers.cellular_simulator as simulator
+from acts.controllers.anritsu_lib import md8475_cellular_simulator as anritsu
+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_contrib.test_utils.tel import tel_test_utils as telutils
+from acts_contrib.test_utils.power import plot_utils
+
+
+class PowerCellularLabBaseTest(PBT.PowerBaseTest):
+    """ Base class for Cellular power related tests.
+
+    Inherits from PowerBaseTest so it has methods to collect power measurements.
+    Provides methods to setup and control the Anritsu simulation.
+
+    """
+
+    # List of test name keywords that indicate the RAT to be used
+
+    PARAM_SIM_TYPE_LTE = "lte"
+    PARAM_SIM_TYPE_LTE_CA = "lteca"
+    PARAM_SIM_TYPE_LTE_IMS = "lteims"
+    PARAM_SIM_TYPE_UMTS = "umts"
+    PARAM_SIM_TYPE_GSM = "gsm"
+
+    # Custom files
+    FILENAME_CALIBRATION_TABLE_UNFORMATTED = 'calibration_table_{}.json'
+
+    # Name of the files in the logs directory that will contain test results
+    # and other information in csv format.
+    RESULTS_SUMMARY_FILENAME = 'cellular_power_results.csv'
+    CALIBRATION_TABLE_FILENAME = 'calibration_table.csv'
+
+    def __init__(self, controllers):
+        """ Class initialization.
+
+        Sets class attributes to None.
+        """
+
+        super().__init__(controllers)
+
+        self.simulation = None
+        self.cellular_simulator = None
+        self.calibration_table = {}
+        self.power_results = {}
+
+    def setup_class(self):
+        """ Executed before any test case is started.
+
+        Sets the device to rockbottom and connects to the cellular instrument.
+
+        Returns:
+            False if connecting to the callbox fails.
+        """
+
+        super().setup_class()
+
+        # Unpack test parameters used in this class
+        self.unpack_userparams(md8475_version=None,
+                               md8475a_ip_address=None,
+                               cmw500_ip=None,
+                               cmw500_port=None,
+                               cmx500_ip=None,
+                               cmx500_port=None,
+                               qxdm_logs=None)
+
+        # Load calibration tables
+        filename_calibration_table = (
+            self.FILENAME_CALIBRATION_TABLE_UNFORMATTED.format(
+                self.testbed_name))
+
+        for file in self.custom_files:
+            if filename_calibration_table in file:
+                self.calibration_table = self.unpack_custom_file(file, False)
+                self.log.info('Loading calibration table from ' + file)
+                self.log.debug(self.calibration_table)
+                break
+
+        # Ensure the calibration table only contains non-negative values
+        self.ensure_valid_calibration_table(self.calibration_table)
+
+        # Turn on airplane mode for all devices, as some might
+        # be unused during the test
+        for ad in self.android_devices:
+            telutils.toggle_airplane_mode(self.log, ad, True)
+
+        # Establish a connection with the cellular simulator equipment
+        try:
+            self.cellular_simulator = self.initialize_simulator()
+        except ValueError:
+            self.log.error('No cellular simulator could be selected with the '
+                           'current configuration.')
+            raise
+        except simulator.CellularSimulatorError:
+            self.log.error('Could not initialize the cellular simulator.')
+            raise
+
+    def initialize_simulator(self):
+        """ Connects to Anritsu Callbox and gets handle object.
+
+        Returns:
+            False if a connection with the callbox could not be started
+        """
+
+        if self.md8475_version:
+
+            self.log.info('Selecting Anrtisu MD8475 callbox.')
+
+            # Verify the callbox IP address has been indicated in the configs
+            if not self.md8475a_ip_address:
+                raise RuntimeError(
+                    'md8475a_ip_address was not included in the test '
+                    'configuration.')
+
+            if self.md8475_version == 'A':
+                return anritsu.MD8475CellularSimulator(self.md8475a_ip_address)
+            elif self.md8475_version == 'B':
+                return anritsu.MD8475BCellularSimulator(
+                    self.md8475a_ip_address)
+            else:
+                raise ValueError('Invalid MD8475 version.')
+
+        elif self.cmw500_ip or self.cmw500_port:
+
+            for key in ['cmw500_ip', 'cmw500_port']:
+                if not getattr(self, key):
+                    raise RuntimeError('The CMW500 cellular simulator '
+                                       'requires %s to be set in the '
+                                       'config file.' % key)
+
+            return cmw.CMW500CellularSimulator(self.cmw500_ip,
+                                               self.cmw500_port)
+        elif self.cmx500_ip or self.cmx500_port:
+            for key in ['cmx500_ip', 'cmx500_port']:
+                if not getattr(self, key):
+                    raise RuntimeError('The CMX500 cellular simulator '
+                                       'requires %s to be set in the '
+                                       'config file.' % key)
+
+            return cmx.CMX500CellularSimulator(self.cmx500_ip,
+                                               self.cmx500_port)
+
+        else:
+            raise RuntimeError(
+                'The simulator could not be initialized because '
+                'a callbox was not defined in the configs file.')
+
+    def setup_test(self):
+        """ Executed before every test case.
+
+        Parses parameters from the test name and sets a simulation up according
+        to those values. Also takes care of attaching the phone to the base
+        station. Because starting new simulations and recalibrating takes some
+        time, the same simulation object is kept between tests and is only
+        destroyed and re instantiated in case the RAT is different from the
+        previous tests.
+
+        Children classes need to call the parent method first. This method will
+        create the list self.parameters with the keywords separated by
+        underscores in the test name and will remove the ones that were consumed
+        for the simulation config. The setup_test methods in the children
+        classes can then consume the remaining values.
+        """
+
+        super().setup_test()
+
+        # Get list of parameters from the test name
+        self.parameters = self.current_test_name.split('_')
+
+        # Remove the 'test' keyword
+        self.parameters.remove('test')
+
+        # Decide what type of simulation and instantiate it if needed
+        if self.consume_parameter(self.PARAM_SIM_TYPE_LTE):
+            self.init_simulation(self.PARAM_SIM_TYPE_LTE)
+        elif self.consume_parameter(self.PARAM_SIM_TYPE_LTE_CA):
+            self.init_simulation(self.PARAM_SIM_TYPE_LTE_CA)
+        elif self.consume_parameter(self.PARAM_SIM_TYPE_LTE_IMS):
+            self.init_simulation(self.PARAM_SIM_TYPE_LTE_IMS)
+        elif self.consume_parameter(self.PARAM_SIM_TYPE_UMTS):
+            self.init_simulation(self.PARAM_SIM_TYPE_UMTS)
+        elif self.consume_parameter(self.PARAM_SIM_TYPE_GSM):
+            self.init_simulation(self.PARAM_SIM_TYPE_GSM)
+        else:
+            self.log.error(
+                "Simulation type needs to be indicated in the test name.")
+            return False
+
+        # 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)
+
+        # 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)
+
+        # Start the simulation. This method will raise an exception if
+        # the phone is unable to attach.
+        self.simulation.start()
+
+        # Make the device go to sleep
+        self.dut.droid.goToSleepNow()
+
+        return True
+
+    def collect_power_data(self):
+        """ Collect power data using base class method and plot result
+        histogram. """
+
+        samples = super().collect_power_data()
+        plot_title = '{}_{}_{}_histogram'.format(
+            self.test_name, self.dut.model, self.dut.build_info['build_id'])
+        plot_utils.monsoon_histogram_plot(samples, self.mon_info.data_path,
+                                          plot_title)
+        return samples
+
+    def teardown_test(self):
+        """ Executed after every test case, even if it failed or an exception
+        happened.
+
+        Save results to dictionary so they can be displayed after completing
+        the test batch.
+        """
+        super().teardown_test()
+
+        self.power_results[self.test_name] = self.power_result.metric_value
+
+        # 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)
+            self.dut.get_qxdm_logs()
+
+    def consume_parameter(self, parameter_name, num_values=0):
+        """ Parses a parameter from the test name.
+
+        Allows the test to get parameters from its name. Deletes parameters from
+        the list after consuming them to ensure that they are not used twice.
+
+        Args:
+            parameter_name: keyword to look up in the test name
+            num_values: number of arguments following the parameter name in the
+                test name
+        Returns:
+            A list containing the parameter name and the following num_values
+            arguments.
+        """
+
+        try:
+            i = self.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(self.parameters.pop(i))
+        except IndexError:
+            self.log.error(
+                "Parameter {} has to be followed by {} values.".format(
+                    parameter_name, num_values))
+            raise ValueError()
+
+        return return_list
+
+    def teardown_class(self):
+        """Clean up the test class after tests finish running.
+
+        Stops the simulation and disconnects from the Anritsu Callbox. Then
+        displays the test results.
+
+        """
+        super().teardown_class()
+
+        try:
+            if self.cellular_simulator:
+                self.cellular_simulator.destroy()
+        except simulator.CellularSimulatorError as e:
+            self.log.error('Error while tearing down the callbox controller. '
+                           'Error message: ' + str(e))
+
+        # Log a summary of results
+        results_table_log = 'Results for cellular power tests:'
+
+        for test_name, value in self.power_results.items():
+            results_table_log += '\n{}\t{}'.format(test_name, value)
+
+        # Save this summary to a csv file in the logs directory
+        self.save_summary_to_file()
+
+        self.log.info(results_table_log)
+
+    def save_summary_to_file(self):
+        """ Creates CSV format files with a summary of results.
+
+        This CSV files can be easily imported in a spreadsheet to analyze the
+        results obtained from the tests.
+        """
+
+        # Save a csv file with the power measurements done in all the tests
+
+        path = os.path.join(self.log_path, self.RESULTS_SUMMARY_FILENAME)
+
+        with open(path, 'w') as csvfile:
+            csvfile.write('test,avg_power')
+            for test_name, value in self.power_results.items():
+                csvfile.write('\n{},{}'.format(test_name, value))
+
+        # Save a csv file with the calibration table for each simulation type
+
+        for sim_type in self.calibration_table:
+
+            path = os.path.join(
+                self.log_path, '{}_{}'.format(sim_type,
+                                              self.CALIBRATION_TABLE_FILENAME))
+
+            with open(path, 'w') as csvfile:
+                csvfile.write('band,dl_pathloss, ul_pathloss')
+                for band, pathloss in self.calibration_table[sim_type].items():
+                    csvfile.write('\n{},{},{}'.format(
+                        band, pathloss.get('dl', 'Error'),
+                        pathloss.get('ul', 'Error')))
+
+    def init_simulation(self, sim_type):
+        """ Starts a new simulation only if needed.
+
+        Only starts a new simulation if type is different from the one running
+        before.
+
+        Args:
+            type: defines the type of simulation to be started.
+        """
+
+        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
+        }
+
+        if not sim_type in simulation_dictionary:
+            raise ValueError("The provided simulation type is invalid.")
+
+        simulation_class = simulation_dictionary[sim_type]
+
+        if isinstance(self.simulation, simulation_class):
+            # The simulation object we already have is enough.
+            return
+
+        if self.simulation:
+            # Make sure the simulation is stopped before loading a new one
+            self.simulation.stop()
+
+        # If the calibration table doesn't have an entry for this simulation
+        # type add an empty one
+        if sim_type not in self.calibration_table:
+            self.calibration_table[sim_type] = {}
+
+        cellular_dut = AndroidCellularDut.AndroidCellularDut(
+            self.dut, self.log)
+        # Instantiate a new simulation
+        self.simulation = simulation_class(self.cellular_simulator, self.log,
+                                           cellular_dut, self.test_params,
+                                           self.calibration_table[sim_type])
+
+    def ensure_valid_calibration_table(self, calibration_table):
+        """ Ensures the calibration table has the correct structure.
+
+        A valid calibration table is a nested dictionary with non-negative
+        number values
+
+        """
+        if not isinstance(calibration_table, dict):
+            raise TypeError('The calibration table must be a dictionary')
+        for val in calibration_table.values():
+            if isinstance(val, dict):
+                self.ensure_valid_calibration_table(val)
+            elif not isinstance(val, float) and not isinstance(val, int):
+                raise TypeError('Calibration table value must be a number')
+            elif val < 0.0:
+                raise ValueError('Calibration table contains negative values')
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
new file mode 100644
index 0000000..21e3dcf
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_traffic_power_test.py
@@ -0,0 +1,580 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+import scapy.all as scapy
+
+from acts import asserts
+from acts import utils
+from acts.metrics.loggers.blackbox import BlackboxMetricLogger
+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
+
+
+class PowerTelTrafficTest(PWCEL.PowerCellularLabBaseTest):
+    """ Cellular traffic power test.
+
+    Inherits from PowerCellularLabBaseTest. Parses config specific
+    to this kind of test. Contains methods to start data traffic
+    between a local instance of iPerf and one running in the dut.
+
+    """
+
+    # Keywords for test name parameters
+    PARAM_DIRECTION = 'direction'
+    PARAM_DIRECTION_UL = 'ul'
+    PARAM_DIRECTION_DL = 'dl'
+    PARAM_DIRECTION_DL_UL = 'dlul'
+    PARAM_BANDWIDTH_LIMIT = 'blimit'
+
+    # Iperf waiting time
+    IPERF_MARGIN = 10
+
+    def __init__(self, controllers):
+        """ Class initialization.
+
+        Sets test parameters to initial values.
+        """
+
+        super().__init__(controllers)
+
+        # These variables are passed to iPerf when starting data
+        # traffic with the -b parameter to limit throughput on
+        # the application layer.
+        self.bandwidth_limit_dl = None
+        self.bandwidth_limit_ul = None
+
+        # Throughput obtained from iPerf
+        self.iperf_results = {}
+
+        # Blackbox metrics loggers
+
+        self.dl_tput_logger = BlackboxMetricLogger.for_test_case(
+            metric_name='avg_dl_tput')
+        self.ul_tput_logger = BlackboxMetricLogger.for_test_case(
+            metric_name='avg_ul_tput')
+
+    def setup_class(self):
+        super().setup_class()
+
+        # Unpack test parameters used in this class
+        self.unpack_userparams(tcp_window_fraction=0, tcp_dumps=False)
+
+        # Verify that at least one PacketSender controller has been initialized
+        if not hasattr(self, 'packet_senders'):
+            raise RuntimeError('At least one packet sender controller needs '
+                               'to be defined in the test config files.')
+
+    def setup_test(self):
+        """ Executed before every test case.
+
+        Parses test configuration from the test name and prepares
+        the simulation for measurement.
+        """
+
+        # Reset results at the start of the test
+        self.iperf_results = {}
+
+        # Call parent method first to setup simulation
+        if not super().setup_test():
+            return False
+
+        # Traffic direction
+
+        values = self.consume_parameter(self.PARAM_DIRECTION, 1)
+
+        if not values:
+            self.log.warning("The keyword {} was not included in the testname "
+                             "parameters. Setting to {} by default.".format(
+                                 self.PARAM_DIRECTION,
+                                 self.PARAM_DIRECTION_DL_UL))
+            self.traffic_direction = self.PARAM_DIRECTION_DL_UL
+        elif values[1] in [
+                self.PARAM_DIRECTION_DL, self.PARAM_DIRECTION_UL,
+                self.PARAM_DIRECTION_DL_UL
+        ]:
+            self.traffic_direction = values[1]
+        else:
+            self.log.error("The test name has to include parameter {} "
+                           "followed by {}/{}/{}.".format(
+                               self.PARAM_DIRECTION, self.PARAM_DIRECTION_UL,
+                               self.PARAM_DIRECTION_DL,
+                               self.PARAM_DIRECTION_DL_UL))
+            return False
+
+        # Bandwidth limit
+
+        values = self.consume_parameter(self.PARAM_BANDWIDTH_LIMIT, 2)
+
+        if values:
+            self.bandwidth_limit_dl = values[1]
+            self.bandwidth_limit_ul = values[2]
+        else:
+            self.bandwidth_limit_dl = 0
+            self.bandwidth_limit_ul = 0
+            self.log.error(
+                "No bandwidth limit was indicated in the test parameters. "
+                "Setting to default value of 0 (no limit to bandwidth). To set "
+                "a different value include parameter '{}' followed by two "
+                "strings indicating downlink and uplink bandwidth limits for "
+                "iPerf.".format(self.PARAM_BANDWIDTH_LIMIT))
+
+        # No errors when parsing parameters
+        return True
+
+    def teardown_test(self):
+        """Tear down necessary objects after test case is finished.
+
+        """
+
+        super().teardown_test()
+
+        # Log the throughput values to Blackbox
+        self.dl_tput_logger.metric_value = self.iperf_results.get('DL', 0)
+        self.ul_tput_logger.metric_value = self.iperf_results.get('UL', 0)
+
+        # Log the throughput values to Spanner
+        self.power_logger.set_dl_tput(self.iperf_results.get('DL', 0))
+        self.power_logger.set_ul_tput(self.iperf_results.get('UL', 0))
+
+        try:
+            dl_max_throughput = self.simulation.maximum_downlink_throughput()
+            ul_max_throughput = self.simulation.maximum_uplink_throughput()
+            self.power_logger.set_dl_tput_threshold(dl_max_throughput)
+            self.power_logger.set_ul_tput_threshold(ul_max_throughput)
+        except NotImplementedError as e:
+            self.log.error("%s Downlink/uplink thresholds will not be "
+                           "logged in the power proto" % e)
+
+        for ips in self.iperf_servers:
+            ips.stop()
+
+    def power_tel_traffic_test(self):
+        """ Measures power and throughput during data transmission.
+
+        Measurement step in this test. Starts iPerf client in the DUT and then
+        initiates power measurement. After that, DUT is connected again and
+        the result from iPerf is collected. Pass or fail is decided with a
+        threshold value.
+        """
+
+        # Start data traffic
+        iperf_helpers = self.start_tel_traffic(self.dut)
+
+        # Measure power
+        self.collect_power_data()
+
+        # Wait for iPerf to finish
+        time.sleep(self.IPERF_MARGIN + 2)
+
+        # Collect throughput measurement
+        self.iperf_results = self.get_iperf_results(self.dut, iperf_helpers)
+
+        # Check if power measurement is below the required value
+        self.pass_fail_check(self.avg_current)
+
+        return self.avg_current, self.iperf_results
+
+    def get_iperf_results(self, device, iperf_helpers):
+        """ Pulls iperf results from the device.
+
+        Args:
+            device: the device from which iperf results need to be pulled.
+
+        Returns:
+            a dictionary containing DL/UL throughput in Mbit/s.
+        """
+
+        # 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)
+
+        throughput = {}
+
+        for iph in iperf_helpers:
+
+            self.log.info("Getting {} throughput results.".format(
+                iph.traffic_direction))
+
+            iperf_result = iph.process_iperf_results(device, self.log,
+                                                     self.iperf_servers,
+                                                     self.test_name)
+
+            throughput[iph.traffic_direction] = iperf_result
+
+        return throughput
+
+    def check_throughput_results(self, iperf_results):
+        """ Checks throughput results.
+
+        Compares the obtained throughput with the expected value
+        provided by the simulation class.
+
+        """
+
+        for direction, throughput in iperf_results.items():
+            try:
+                if direction == "UL":
+                    expected_t = self.simulation.maximum_uplink_throughput()
+                elif direction == "DL":
+                    expected_t = self.simulation.maximum_downlink_throughput()
+                else:
+                    raise RuntimeError("Unexpected traffic direction value.")
+            except NotImplementedError:
+                # Some simulation classes might not have implemented the max
+                # throughput calculation yet.
+                self.log.debug("Expected throughput is not available for the "
+                               "current simulation class.")
+            else:
+
+                self.log.info(
+                    "The expected {} throughput is {} Mbit/s.".format(
+                        direction, expected_t))
+                asserts.assert_true(
+                    0.90 < throughput / expected_t < 1.10,
+                    "{} throughput differed more than 10% from the expected "
+                    "value! ({}/{} = {})".format(
+                        direction, round(throughput, 3), round(expected_t, 3),
+                        round(throughput / expected_t, 3)))
+
+    def pass_fail_check(self, average_current=None):
+        """ Checks power consumption and throughput.
+
+        Uses the base class method to check power consumption. Also, compares
+        the obtained throughput with the expected value provided by the
+        simulation class.
+
+        """
+        self.check_throughput_results(self.iperf_results)
+        super().pass_fail_check(average_current)
+
+    def start_tel_traffic(self, client_host):
+        """ Starts iPerf in the indicated device and initiates traffic.
+
+        Starts the required iperf clients and servers according to the traffic
+        pattern config in the current test.
+
+        Args:
+            client_host: device handler in which to start the iperf client.
+
+        Returns:
+            A list of iperf helpers.
+        """
+        # The iPerf server is hosted in this computer
+        self.iperf_server_address = scapy.get_if_addr(
+            self.packet_senders[0].interface)
+
+        self.log.info('Testing IP connectivity with ping.')
+        if not utils.adb_shell_ping(
+                client_host, count=10, dest_ip=self.iperf_server_address):
+            raise RuntimeError('Ping between DUT and host failed.')
+
+        # Start iPerf traffic
+        iperf_helpers = []
+
+        # If the tcp_window_fraction parameter was set, calculate the TCP
+        # window size as a fraction of the peak throughput.
+        ul_tcp_window = None
+        dl_tcp_window = None
+        if self.tcp_window_fraction == 0:
+            self.log.info("tcp_window_fraction was not indicated. "
+                          "Disabling fixed TCP window.")
+        else:
+            try:
+                max_dl_tput = self.simulation.maximum_downlink_throughput()
+                max_ul_tput = self.simulation.maximum_uplink_throughput()
+                dl_tcp_window = max_dl_tput / self.tcp_window_fraction
+                ul_tcp_window = max_ul_tput / self.tcp_window_fraction
+            except NotImplementedError:
+                self.log.error("Maximum downlink/uplink throughput method not "
+                               "implemented for %s." %
+                               type(self.simulation).__name__)
+
+        if self.traffic_direction in [
+                self.PARAM_DIRECTION_DL, self.PARAM_DIRECTION_DL_UL
+        ]:
+            # Downlink traffic
+            iperf_helpers.append(
+                self.start_iperf_traffic(client_host,
+                                         server_idx=len(iperf_helpers),
+                                         traffic_direction='DL',
+                                         window=dl_tcp_window,
+                                         bandwidth=self.bandwidth_limit_dl))
+
+        if self.traffic_direction in [
+                self.PARAM_DIRECTION_UL, self.PARAM_DIRECTION_DL_UL
+        ]:
+            # Uplink traffic
+            iperf_helpers.append(
+                self.start_iperf_traffic(client_host,
+                                         server_idx=len(iperf_helpers),
+                                         traffic_direction='UL',
+                                         window=ul_tcp_window,
+                                         bandwidth=self.bandwidth_limit_ul))
+
+        # Enable TCP logger.
+        if self.tcp_dumps:
+            self.log.info('Enabling TCP logger.')
+            telutils.start_adb_tcpdump(self.dut)
+
+        return iperf_helpers
+
+    def start_iperf_traffic(self,
+                            client_host,
+                            server_idx,
+                            traffic_direction,
+                            bandwidth=0,
+                            window=None):
+        """Starts iPerf data traffic.
+
+        Starts an iperf client in an android device and a server locally.
+
+        Args:
+            client_host: device handler in which to start the iperf client
+            server_idx: id of the iperf server to connect to
+            traffic_direction: has to be either 'UL' or 'DL'
+            bandwidth: bandwidth limit for data traffic
+            window: the tcp window. if None, no window will be passed to iperf
+
+        Returns:
+            An IperfHelper object for the started client/server pair.
+        """
+
+        # Start the server locally
+        self.iperf_servers[server_idx].start()
+
+        config = {
+            'traffic_type': 'TCP',
+            'duration':
+            self.mon_duration + self.mon_offset + self.IPERF_MARGIN,
+            'start_meas_time': 4,
+            'server_idx': server_idx,
+            'port': self.iperf_servers[server_idx].port,
+            'traffic_direction': traffic_direction,
+            'window': window
+        }
+
+        # If bandwidth is equal to zero then no bandwidth requirements are set
+        if bandwidth > 0:
+            config['bandwidth'] = bandwidth
+
+        iph = IPH.IperfHelper(config)
+
+        # Start the client in the android device
+        client_host.adb.shell_nb(
+            "nohup >/dev/null 2>&1 sh -c 'iperf3 -c {} {} "
+            "&'".format(self.iperf_server_address, iph.iperf_args))
+
+        self.log.info('{} iPerf started on port {}.'.format(
+            traffic_direction, iph.port))
+
+        return iph
+
+
+class PowerTelRvRTest(PowerTelTrafficTest):
+    """ Gets Range vs Rate curves while measuring power consumption.
+
+    Uses PowerTelTrafficTest as a base class.
+    """
+
+    # Test name configuration keywords
+    PARAM_SWEEP = "sweep"
+    PARAM_SWEEP_UPLINK = "uplink"
+    PARAM_SWEEP_DOWNLINK = "downlink"
+
+    # Sweep values. Need to be set before starting test by test
+    # function or child class.
+    downlink_power_sweep = None
+    uplink_power_sweep = None
+
+    def setup_test(self):
+        """ Executed before every test case.
+
+        Parses test configuration from the test name and prepares
+        the simulation for measurement.
+        """
+
+        # Call parent method first to setup simulation
+        if not super().setup_test():
+            return False
+
+        # Get which power value to sweep from config
+
+        try:
+            values = self.consume_parameter(self.PARAM_SWEEP, 1)
+
+            if values[1] == self.PARAM_SWEEP_UPLINK:
+                self.sweep = self.PARAM_SWEEP_UPLINK
+            elif values[1] == self.PARAM_SWEEP_DOWNLINK:
+                self.sweep = self.PARAM_SWEEP_DOWNLINK
+            else:
+                raise ValueError()
+        except:
+            self.log.error(
+                "The test name has to include parameter {} followed by "
+                "either {} or {}.".format(self.PARAM_SWEEP,
+                                          self.PARAM_SWEEP_DOWNLINK,
+                                          self.PARAM_SWEEP_UPLINK))
+            return False
+
+        return True
+
+    def power_tel_rvr_test(self):
+        """ Main function for the RvR test.
+
+        Produces the RvR curve according to the indicated sweep values.
+        """
+
+        if self.sweep == self.PARAM_SWEEP_DOWNLINK:
+            sweep_range = self.downlink_power_sweep
+        elif self.sweep == self.PARAM_SWEEP_UPLINK:
+            sweep_range = self.uplink_power_sweep
+
+        current = []
+        throughput = []
+
+        for pw in sweep_range:
+
+            if self.sweep == self.PARAM_SWEEP_DOWNLINK:
+                self.simulation.set_downlink_rx_power(self.simulation.bts1, pw)
+            elif self.sweep == self.PARAM_SWEEP_UPLINK:
+                self.simulation.set_uplink_tx_power(self.simulation.bts1, pw)
+
+            i, t = self.power_tel_traffic_test()
+            self.log.info("---------------------")
+            self.log.info("{} -- {} --".format(self.sweep, pw))
+            self.log.info("{} ----- {}".format(i, t[0]))
+            self.log.info("---------------------")
+
+            current.append(i)
+            throughput.append(t[0])
+
+        print(sweep_range)
+        print(current)
+        print(throughput)
+
+
+class PowerTelTxPowerSweepTest(PowerTelTrafficTest):
+    """ Gets Average Current vs Tx Power plot.
+
+    Uses PowerTelTrafficTest as a base class.
+    """
+
+    # Test config keywords
+    KEY_TX_STEP = 'step'
+    KEY_UP_TOLERANCE = 'up_tolerance'
+    KEY_DOWN_TOLERANCE = 'down_tolerance'
+
+    # Test name parameters
+    PARAM_TX_POWER_SWEEP = 'sweep'
+
+    def setup_class(self):
+        super().setup_class()
+        self.unpack_userparams(
+            [self.KEY_TX_STEP, self.KEY_UP_TOLERANCE, self.KEY_DOWN_TOLERANCE])
+
+    def setup_test(self):
+        """ Executed before every test case.
+
+        Parses test configuration from the test name and prepares
+        the simulation for measurement.
+        """
+        # Call parent method first to setup simulation
+        if not super().setup_test():
+            return False
+
+        # Determine power range to sweep from test case params
+        try:
+            values = self.consume_parameter(self.PARAM_TX_POWER_SWEEP, 2)
+
+            if len(values) == 3:
+                self.start_dbm = int(values[1].replace('n', '-'))
+                self.end_dbm = int(values[2].replace('n', '-'))
+            else:
+                raise ValueError('Not enough params specified for sweep.')
+        except ValueError as e:
+            self.log.error("Unable to parse test param sweep: {}".format(e))
+            return False
+
+        return True
+
+    def pass_fail_check(self, currents, txs, iperf_results):
+        """ Compares the obtained throughput with the expected
+        value provided by the simulation class. Also, ensures
+        consecutive currents do not increase or decrease beyond
+        specified tolerance
+        """
+        for iperf_result in iperf_results:
+            self.check_throughput_results(iperf_result)
+
+        # x = reference current value, y = next current value, i = index of x
+        for i, (x, y) in enumerate(zip(currents[::], currents[1::])):
+            measured_change = (y - x) / x * 100
+            asserts.assert_true(
+                -self.down_tolerance < measured_change < self.up_tolerance,
+                "Current went from {} to {} ({}%) between {} dBm and {} dBm. "
+                "Tolerance range: -{}% to {}%".format(x, y, measured_change,
+                                                      txs[i], txs[i + 1],
+                                                      self.down_tolerance,
+                                                      self.up_tolerance))
+
+    def create_power_plot(self, currents, txs):
+        """ Creates average current vs tx power plot
+        """
+        title = '{}_{}_{}_tx_power_sweep'.format(
+            self.test_name, self.dut.model, self.dut.build_info['build_id'])
+
+        plot_utils.monsoon_tx_power_sweep_plot(self.mon_info.data_path, title,
+                                               currents, txs)
+
+    def power_tel_tx_sweep(self):
+        """ Main function for the Tx power sweep test.
+
+        Produces a plot of power consumption vs tx power
+        """
+        currents = []
+        txs = []
+        iperf_results = []
+        for tx in range(self.start_dbm, self.end_dbm + 1, self.step):
+
+            self.simulation.set_uplink_tx_power(tx)
+
+            iperf_helpers = self.start_tel_traffic(self.dut)
+
+            # Measure power
+            self.collect_power_data()
+
+            # Wait for iPerf to finish
+            time.sleep(self.IPERF_MARGIN + 2)
+
+            # Collect and check throughput measurement
+            iperf_result = self.get_iperf_results(self.dut, iperf_helpers)
+
+            currents.append(self.avg_current)
+
+            # Get the actual Tx power as measured from the callbox side
+            measured_tx = self.simulation.get_measured_ul_power()
+
+            txs.append(measured_tx)
+            iperf_results.append(iperf_result)
+
+        self.create_power_plot(currents, txs)
+        self.pass_fail_check(currents, txs, iperf_results)
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
new file mode 100644
index 0000000..802d8cc
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_voice_call_power_test.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from acts.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
+
+
+class PowerTelVoiceCallTest(PWCEL.PowerCellularLabBaseTest):
+    """ Voice call power test.
+
+    Inherits from PowerCellularLabBaseTest. Contains methods to initiate
+    a voice call from the DUT and pick up from the callbox.
+
+    """
+
+    # Time for the callbox to pick up the call
+    CALL_SETTLING_TIME = 10
+
+    def setup_class(self):
+        """ Executed only once when initializing the class.
+
+        Configs the callbox to pick up the call automatically and
+        sets the phone to silent mode.
+        """
+
+        super().setup_class()
+
+        # Make the callbox pick up the call automatically
+        self.virtualPhoneHandle = self.anritsu.get_VirtualPhone()
+        self.virtualPhoneHandle.auto_answer = (VirtualPhoneAutoAnswer.ON, 0)
+
+        # Set voice call volume to minimum
+        set_phone_silent_mode(self.log, self.dut)
+
+    def power_voice_call_test(self):
+        """ Measures power during a voice call.
+
+        Measurement step in this test. Starts the voice call and
+        initiates power measurement. Pass or fail is decided with a
+        threshold value.
+        """
+
+        # Initiate the voice call
+        initiate_call(self.log, self.dut, "+11112223333")
+
+        # Wait for the callbox to pick up
+        time.sleep(self.CALL_SETTLING_TIME)
+
+        # Mute the call
+        self.dut.droid.telecomCallMute()
+
+        # Turn of screen
+        self.dut.droid.goToSleepNow()
+
+        # Measure power
+        self.collect_power_data()
+
+        # End the call
+        hangup_call(self.log, self.dut)
+
+        # Check if power measurement is within the required values
+        self.pass_fail_check(self.avg_current)
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
new file mode 100644
index 0000000..986878d
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/cellular/cellular_volte_power_test.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+import acts_contrib.test_utils.tel.anritsu_utils as anritsu_utils
+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
+
+
+class PowerTelVoLTECallTest(PWCEL.PowerCellularLabBaseTest):
+    """ VoLTE call power test.
+
+    Inherits from PowerCellularLabBaseTest. Contains methods to initiate
+    a voice call from the IMS server and pick up on the UE.
+
+    """
+
+    # Waiting time before trying to pick up from the phone
+    CALL_INITIATING_TIME = 10
+
+    def setup_class(self):
+        """ Executed only once when initializing the class. """
+
+        super().setup_class()
+
+        # Set voice call volume to minimum
+        set_phone_silent_mode(self.log, self.dut)
+
+    def power_volte_call_test(self):
+        """ Measures power during a VoLTE call.
+
+        Measurement step in this test. Starts the voice call and
+        initiates power measurement. Pass or fail is decided with a
+        threshold value. """
+
+        # Initiate the voice call
+        self.anritsu.ims_cscf_call_action(
+            anritsu_utils.DEFAULT_IMS_VIRTUAL_NETWORK_ID,
+            md8475a.ImsCscfCall.MAKE.value)
+
+        # Wait for the call to be started
+        time.sleep(self.CALL_INITIATING_TIME)
+
+        # Pickup the call
+        self.dut.adb.shell('input keyevent KEYCODE_CALL')
+
+        # Mute the call
+        self.dut.droid.telecomCallMute()
+
+        # Turn of screen
+        self.dut.droid.goToSleepNow()
+
+        # Measure power
+        self.collect_power_data()
+
+        # End the call
+        hangup_call(self.log, self.dut)
+
+        # Check if power measurement is within the required values
+        self.pass_fail_check()
diff --git a/acts_tests/acts_contrib/test_utils/power/loggers/__init__.py b/acts_tests/acts_contrib/test_utils/power/loggers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/loggers/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/power/loggers/power_metric_logger.py b/acts_tests/acts_contrib/test_utils/power/loggers/power_metric_logger.py
new file mode 100644
index 0000000..89deb5c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/loggers/power_metric_logger.py
@@ -0,0 +1,88 @@
+# /usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import os
+import acts_contrib.test_utils.power.loggers.protos.power_metric_pb2 as power_protos
+
+from acts.metrics.core import ProtoMetric
+from acts.metrics.logger import MetricLogger
+
+# Initializes the path to the protobuf
+PROTO_PATH = os.path.join(os.path.dirname(__file__), 'protos',
+                          'power_metric.proto')
+
+
+class PowerMetricLogger(MetricLogger):
+    """A logger for gathering Power test metrics
+
+    Attributes:
+        proto: Module used to store Power metrics in a proto
+    """
+    def __init__(self, event):
+        super().__init__(event=event)
+        self.proto = power_protos.PowerMetric()
+
+    def set_dl_tput(self, avg_dl_tput):
+        self.proto.cellular_metric.avg_dl_tput = avg_dl_tput
+
+    def set_ul_tput(self, avg_ul_tput):
+        self.proto.cellular_metric.avg_ul_tput = avg_ul_tput
+
+    def set_dl_tput_threshold(self, avg_dl_tput_threshold):
+        self.proto.cellular_metric.avg_dl_tput_threshold = avg_dl_tput_threshold
+
+    def set_ul_tput_threshold(self, avg_ul_tput_threshold):
+        self.proto.cellular_metric.avg_ul_tput_threshold = avg_ul_tput_threshold
+
+    def set_avg_power(self, avg_power):
+        self.proto.avg_power = avg_power
+
+    def set_avg_current(self, avg_current):
+        self.proto.avg_current = avg_current
+
+    def set_voltage(self, voltage):
+        self.proto.voltage = voltage
+
+    def set_testbed(self, testbed):
+        self.proto.testbed = testbed
+
+    def set_branch(self, branch):
+        self.proto.branch = branch
+
+    def set_build_id(self, build_id):
+        self.proto.build_id = build_id
+
+    def set_incremental_build_id(self, incremental_build_id):
+        self.proto.incremental_build_id = incremental_build_id
+
+    def set_target(self, target):
+        self.proto.target = target
+
+    def set_test_suite_display_name(self, test_suite_display_name):
+        self.proto.test_suite_display_name = test_suite_display_name
+
+    def set_test_case_display_name(self, test_case_display_name):
+        self.proto.test_case_display_name = test_case_display_name
+
+    def set_avg_current_threshold(self, avg_current_threshold):
+        self.proto.avg_current_threshold = avg_current_threshold
+
+    def set_pass_fail_status(self, status):
+        self.proto.pass_fail_status = status
+
+    def end(self, event):
+        metric = ProtoMetric(name='spanner_power_metric', data=self.proto)
+        return self.publisher.publish(metric)
diff --git a/acts_tests/acts_contrib/test_utils/power/loggers/protos/__init__.py b/acts_tests/acts_contrib/test_utils/power/loggers/protos/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/loggers/protos/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/power/loggers/protos/power_metric.proto b/acts_tests/acts_contrib/test_utils/power/loggers/protos/power_metric.proto
new file mode 100644
index 0000000..0e64bf8
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/loggers/protos/power_metric.proto
@@ -0,0 +1,39 @@
+/* Note: If making any changes to this file be sure to generate a new
+   compiled *_pb2.py file by running the following command from the same dir:
+   $ protoc -I=. --python_out=. power_metric.proto
+
+   Be sure that you are compiling with protoc 3.4.0
+
+   More info can be found at:
+   https://developers.google.com/protocol-buffers/docs/pythontutorial
+*/
+
+syntax = "proto2";
+
+package wireless.android.platform.testing.power.metrics;
+
+/*
+  Power metrics to be uploaded to Spanner
+*/
+message PowerMetric {
+  optional float avg_power = 1; // Required
+  optional string testbed = 2; // Required
+  optional PowerCellularMetric cellular_metric = 3;
+  optional string branch = 4;
+  optional string build_id = 5;
+  optional string target = 6;
+  optional float avg_current = 7;
+  optional float voltage = 8;
+  optional string test_suite_display_name = 9;
+  optional string test_case_display_name = 10;
+  optional string incremental_build_id = 11;
+  optional float avg_current_threshold = 12;
+  optional string pass_fail_status = 13;
+}
+
+message PowerCellularMetric {
+  optional float avg_dl_tput = 1;
+  optional float avg_ul_tput = 2;
+  optional float avg_dl_tput_threshold = 3;
+  optional float avg_ul_tput_threshold = 4;
+}
diff --git a/acts_tests/acts_contrib/test_utils/power/loggers/protos/power_metric_pb2.py b/acts_tests/acts_contrib/test_utils/power/loggers/protos/power_metric_pb2.py
new file mode 100644
index 0000000..d1682cc
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/loggers/protos/power_metric_pb2.py
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: power_metric.proto
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='power_metric.proto',
+  package='wireless.android.platform.testing.power.metrics',
+  syntax='proto2',
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x12power_metric.proto\x12/wireless.android.platform.testing.power.metrics\"\x80\x03\n\x0bPowerMetric\x12\x11\n\tavg_power\x18\x01 \x01(\x02\x12\x0f\n\x07testbed\x18\x02 \x01(\t\x12]\n\x0f\x63\x65llular_metric\x18\x03 \x01(\x0b\x32\x44.wireless.android.platform.testing.power.metrics.PowerCellularMetric\x12\x0e\n\x06\x62ranch\x18\x04 \x01(\t\x12\x10\n\x08\x62uild_id\x18\x05 \x01(\t\x12\x0e\n\x06target\x18\x06 \x01(\t\x12\x13\n\x0b\x61vg_current\x18\x07 \x01(\x02\x12\x0f\n\x07voltage\x18\x08 \x01(\x02\x12\x1f\n\x17test_suite_display_name\x18\t \x01(\t\x12\x1e\n\x16test_case_display_name\x18\n \x01(\t\x12\x1c\n\x14incremental_build_id\x18\x0b \x01(\t\x12\x1d\n\x15\x61vg_current_threshold\x18\x0c \x01(\x02\x12\x18\n\x10pass_fail_status\x18\r \x01(\t\"}\n\x13PowerCellularMetric\x12\x13\n\x0b\x61vg_dl_tput\x18\x01 \x01(\x02\x12\x13\n\x0b\x61vg_ul_tput\x18\x02 \x01(\x02\x12\x1d\n\x15\x61vg_dl_tput_threshold\x18\x03 \x01(\x02\x12\x1d\n\x15\x61vg_ul_tput_threshold\x18\x04 \x01(\x02'
+)
+
+
+
+
+_POWERMETRIC = _descriptor.Descriptor(
+  name='PowerMetric',
+  full_name='wireless.android.platform.testing.power.metrics.PowerMetric',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='avg_power', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.avg_power', index=0,
+      number=1, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='testbed', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.testbed', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='cellular_metric', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.cellular_metric', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='branch', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.branch', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='build_id', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.build_id', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='target', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.target', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='avg_current', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.avg_current', index=6,
+      number=7, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='voltage', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.voltage', index=7,
+      number=8, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='test_suite_display_name', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.test_suite_display_name', index=8,
+      number=9, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='test_case_display_name', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.test_case_display_name', index=9,
+      number=10, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='incremental_build_id', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.incremental_build_id', index=10,
+      number=11, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='avg_current_threshold', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.avg_current_threshold', index=11,
+      number=12, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='pass_fail_status', full_name='wireless.android.platform.testing.power.metrics.PowerMetric.pass_fail_status', index=12,
+      number=13, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=72,
+  serialized_end=456,
+)
+
+
+_POWERCELLULARMETRIC = _descriptor.Descriptor(
+  name='PowerCellularMetric',
+  full_name='wireless.android.platform.testing.power.metrics.PowerCellularMetric',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='avg_dl_tput', full_name='wireless.android.platform.testing.power.metrics.PowerCellularMetric.avg_dl_tput', index=0,
+      number=1, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='avg_ul_tput', full_name='wireless.android.platform.testing.power.metrics.PowerCellularMetric.avg_ul_tput', index=1,
+      number=2, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='avg_dl_tput_threshold', full_name='wireless.android.platform.testing.power.metrics.PowerCellularMetric.avg_dl_tput_threshold', index=2,
+      number=3, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='avg_ul_tput_threshold', full_name='wireless.android.platform.testing.power.metrics.PowerCellularMetric.avg_ul_tput_threshold', index=3,
+      number=4, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=458,
+  serialized_end=583,
+)
+
+_POWERMETRIC.fields_by_name['cellular_metric'].message_type = _POWERCELLULARMETRIC
+DESCRIPTOR.message_types_by_name['PowerMetric'] = _POWERMETRIC
+DESCRIPTOR.message_types_by_name['PowerCellularMetric'] = _POWERCELLULARMETRIC
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+PowerMetric = _reflection.GeneratedProtocolMessageType('PowerMetric', (_message.Message,), {
+  'DESCRIPTOR' : _POWERMETRIC,
+  '__module__' : 'power_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.power.metrics.PowerMetric)
+  })
+_sym_db.RegisterMessage(PowerMetric)
+
+PowerCellularMetric = _reflection.GeneratedProtocolMessageType('PowerCellularMetric', (_message.Message,), {
+  'DESCRIPTOR' : _POWERCELLULARMETRIC,
+  '__module__' : 'power_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.power.metrics.PowerCellularMetric)
+  })
+_sym_db.RegisterMessage(PowerCellularMetric)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/acts_tests/acts_contrib/test_utils/power/plot_utils.py b/acts_tests/acts_contrib/test_utils/power/plot_utils.py
new file mode 100644
index 0000000..aed43cc
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/plot_utils.py
@@ -0,0 +1,199 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 Google, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT 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 logging
+import numpy
+import math
+
+from bokeh.layouts import layout
+from bokeh.models import CustomJS, ColumnDataSource
+from bokeh.models import tools as bokeh_tools
+from bokeh.models.widgets import DataTable, TableColumn
+from bokeh.plotting import figure, output_file, save
+
+
+def current_waveform_plot(samples, voltage, dest_path, plot_title):
+    """Plot the current data using bokeh interactive plotting tool.
+
+    Plotting power measurement data with bokeh to generate interactive plots.
+    You can do interactive data analysis on the plot after generating with the
+    provided widgets, which make the debugging much easier. To realize that,
+    bokeh callback java scripting is used. View a sample html output file:
+    https://drive.google.com/open?id=0Bwp8Cq841VnpT2dGUUxLYWZvVjA
+
+    Args:
+        samples: a list of tuples in which the first element is a timestamp and
+          the second element is the sampled current in milli amps at that time.
+        voltage: the voltage that was used during the measurement.
+        dest_path: destination path.
+        plot_title: a filename and title for the plot.
+    Returns:
+        plot: the plotting object of bokeh, optional, will be needed if multiple
+           plots will be combined to one html file.
+        dt: the datatable object of bokeh, optional, will be needed if multiple
+           datatables will be combined to one html file.
+    """
+    logging.info('Plotting the power measurement data.')
+
+    time_relative = [sample[0] for sample in samples]
+    duration = time_relative[-1] - time_relative[0]
+    current_data = [sample[1] * 1000 for sample in samples]
+    avg_current = sum(current_data) / len(current_data)
+
+    color = ['navy'] * len(samples)
+
+    # Preparing the data and source link for bokehn java callback
+    source = ColumnDataSource(
+        data=dict(x=time_relative, y=current_data, color=color))
+    s2 = ColumnDataSource(
+        data=dict(a=[duration],
+                  b=[round(avg_current, 2)],
+                  c=[round(avg_current * voltage, 2)],
+                  d=[round(avg_current * voltage * duration, 2)],
+                  e=[round(avg_current * duration, 2)]))
+    # Setting up data table for the output
+    columns = [
+        TableColumn(field='a', title='Total Duration (s)'),
+        TableColumn(field='b', title='Average Current (mA)'),
+        TableColumn(field='c', title='Average Power (4.2v) (mW)'),
+        TableColumn(field='d', title='Average Energy (mW*s)'),
+        TableColumn(field='e', title='Normalized Average Energy (mA*s)')
+    ]
+    dt = DataTable(source=s2,
+                   columns=columns,
+                   width=1300,
+                   height=60,
+                   editable=True)
+
+    output_file(os.path.join(dest_path, plot_title + '.html'))
+    tools = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save'
+    # Create a new plot with the datatable above
+    plot = figure(plot_width=1300,
+                  plot_height=700,
+                  title=plot_title,
+                  tools=tools)
+    plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='width'))
+    plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='height'))
+    plot.line('x', 'y', source=source, line_width=2)
+    plot.circle('x', 'y', source=source, size=0.5, fill_color='color')
+    plot.xaxis.axis_label = 'Time (s)'
+    plot.yaxis.axis_label = 'Current (mA)'
+    plot.title.text_font_size = {'value': '15pt'}
+
+    # Callback JavaScript
+    source.selected.js_on_change(
+        "indices",
+        CustomJS(args=dict(source=source, mytable=dt),
+                 code="""
+        const inds = source.selected.indices;
+        const d1 = source.data;
+        const d2 = mytable.source.data;
+        var ym = 0
+        var ts = 0
+        var min=d1['x'][inds[0]]
+        var max=d1['x'][inds[0]]
+        d2['a'] = []
+        d2['b'] = []
+        d2['c'] = []
+        d2['d'] = []
+        d2['e'] = []
+        if (inds.length==0) {return;}
+        for (var i = 0; i < inds.length; i++) {
+        ym += d1['y'][inds[i]]
+        d1['color'][inds[i]] = "red"
+        if (d1['x'][inds[i]] < min) {
+          min = d1['x'][inds[i]]}
+        if (d1['x'][inds[i]] > max) {
+          max = d1['x'][inds[i]]}
+        }
+        ym /= inds.length
+        ts = max - min
+        d2['a'].push(Math.round(ts*1000.0)/1000.0)
+        d2['b'].push(Math.round(ym*100.0)/100.0)
+        d2['c'].push(Math.round(ym*4.2*100.0)/100.0)
+        d2['d'].push(Math.round(ym*4.2*ts*100.0)/100.0)
+        d2['e'].push(Math.round(ym*ts*100.0)/100.0)
+        source.change.emit();
+        mytable.change.emit();
+    """))
+
+    # Layout the plot and the datatable bar
+    save(layout([[dt], [plot]]))
+    return plot, dt
+
+
+def monsoon_histogram_plot(samples, dest_path, plot_title):
+    """ Creates a histogram from a monsoon result object.
+
+    Args:
+        samples: a list of tuples in which the first element is a timestamp and
+          the second element is the sampled current in milli amps at that time.
+        dest_path: destination path
+        plot_title: a filename and title for the plot.
+    Returns:
+        a tuple of arrays containing the values of the histogram and the
+        bin edges.
+    """
+    milli_amps = [sample[1] * 1000 for sample in samples]
+    hist, edges = numpy.histogram(milli_amps,
+                                  bins=math.ceil(max(milli_amps)),
+                                  range=(0, max(milli_amps)))
+
+    output_file(os.path.join(dest_path, plot_title + '.html'))
+
+    plot = figure(title=plot_title,
+                  y_axis_type='log',
+                  background_fill_color='#fafafa')
+
+    plot.quad(top=hist,
+              bottom=0,
+              left=edges[:-1],
+              right=edges[1:],
+              fill_color='navy')
+
+    plot.y_range.start = 0
+    plot.xaxis.axis_label = 'Instantaneous current [mA]'
+    plot.yaxis.axis_label = 'Count'
+    plot.grid.grid_line_color = 'white'
+
+    save(plot)
+
+    return hist, edges
+
+
+def monsoon_tx_power_sweep_plot(dest_path, plot_title, currents, txs):
+    """ Creates average current vs tx power plot
+
+    Args:
+        dest_path: destination path
+        plot_title: a filename and title for the plot.
+        currents: List of average currents measured during power sweep
+        txs: List of uplink input power levels specified for each measurement
+    """
+
+    output_file(os.path.join(dest_path, plot_title + '.html'))
+
+    plot = figure(title=plot_title,
+                  y_axis_label='Average Current [mA]',
+                  x_axis_label='Tx Power [dBm]',
+                  background_fill_color='#fafafa')
+
+    plot.line(txs, currents)
+    plot.circle(txs, currents, fill_color='white', size=8)
+    plot.y_range.start = 0
+
+    save(plot)
diff --git a/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py b/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py
new file mode 100644
index 0000000..d9f922c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/TelephonyBaseTest.py
@@ -0,0 +1,580 @@
+#!/usr/bin/env python3
+#
+#   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.
+"""
+    Base Class for Defining Common Telephony Test Functionality
+"""
+
+import logging
+import os
+import re
+import shutil
+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_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
+
+
+class TelephonyBaseTest(BaseTestClass):
+    # Use for logging in the test cases to facilitate
+    # faster log lookup and reduce ambiguity in logging.
+    @staticmethod
+    def tel_test_wrap(fn):
+        def _safe_wrap_test_case(self, *args, **kwargs):
+            test_id = "%s:%s:%s" % (self.__class__.__name__, self.test_name,
+                                    self.log_begin_time.replace(' ', '-'))
+            self.test_id = test_id
+            self.result_detail = ""
+            self.testsignal_details = ""
+            self.testsignal_extras = {}
+            tries = int(self.user_params.get("telephony_auto_rerun", 1))
+            for ad in self.android_devices:
+                ad.log_path = self.log_path
+            for i in range(tries + 1):
+                result = True
+                if i > 0:
+                    log_string = "[Test Case] RERUN %s" % self.test_name
+                    self.log.info(log_string)
+                    self._teardown_test(self.test_name)
+                    self._setup_test(self.test_name)
+                try:
+                    result = fn(self, *args, **kwargs)
+                except signals.TestFailure as e:
+                    self.testsignal_details = e.details
+                    self.testsignal_extras = e.extras
+                    result = False
+                except signals.TestSignal:
+                    raise
+                except Exception as e:
+                    self.log.exception(e)
+                    asserts.fail(self.result_detail)
+                if result is False:
+                    if i < tries:
+                        continue
+                else:
+                    break
+            if self.user_params.get("check_crash", True):
+                new_crash = ad.check_crash_report(self.test_name,
+                                                  self.begin_time, True)
+                if new_crash:
+                    msg = "Find new crash reports %s" % new_crash
+                    ad.log.error(msg)
+                    self.result_detail = "%s %s %s" % (self.result_detail,
+                                                       ad.serial, msg)
+                    result = False
+            if result is not False:
+                asserts.explicit_pass(self.result_detail)
+            else:
+                if self.result_detail:
+                    asserts.fail(self.result_detail)
+                else:
+                    asserts.fail(self.testsignal_details, self.testsignal_extras)
+
+        return _safe_wrap_test_case
+
+    def setup_class(self):
+        super().setup_class()
+        self.wifi_network_ssid = self.user_params.get(
+            "wifi_network_ssid") or self.user_params.get(
+                "wifi_network_ssid_2g") or self.user_params.get(
+                    "wifi_network_ssid_5g")
+        self.wifi_network_pass = self.user_params.get(
+            "wifi_network_pass") or self.user_params.get(
+                "wifi_network_pass_2g") or self.user_params.get(
+                    "wifi_network_ssid_5g")
+
+        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.enable_radio_log_on = self.user_params.get(
+            "enable_radio_log_on", False)
+        self.cbrs_esim = self.user_params.get("cbrs_esim", False)
+        self.account_util = self.user_params.get("account_util", None)
+        self.save_passing_logs = self.user_params.get("save_passing_logs", False)
+        if isinstance(self.account_util, list):
+            self.account_util = self.account_util[0]
+        self.fi_util = self.user_params.get("fi_util", None)
+        if isinstance(self.fi_util, list):
+            self.fi_util = self.fi_util[0]
+        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(
+            "skip_reset_between_cases", True)
+        self.log_path = getattr(logging, "log_path", None)
+        self.sim_config = {
+                            "config":SINGLE_SIM_CONFIG,
+                            "number_of_sims":1
+                        }
+
+        for ad in self.android_devices:
+            if hasattr(ad, "dsds"):
+                self.sim_config = {
+                                    "config":MULTI_SIM_CONFIG,
+                                    "number_of_sims":2
+                                }
+                break
+        if "anritsu_md8475a_ip_address" in self.user_params:
+            return
+        qxdm_log_mask_cfg = self.user_params.get("qxdm_log_mask_cfg", None)
+        if isinstance(qxdm_log_mask_cfg, list):
+            qxdm_log_mask_cfg = qxdm_log_mask_cfg[0]
+        if qxdm_log_mask_cfg and "dev/null" in qxdm_log_mask_cfg:
+            qxdm_log_mask_cfg = None
+        sim_conf_file = self.user_params.get("sim_conf_file")
+        if not sim_conf_file:
+            self.log.info("\"sim_conf_file\" is not provided test bed config!")
+        else:
+            if isinstance(sim_conf_file, list):
+                sim_conf_file = sim_conf_file[0]
+            # If the sim_conf_file is not a full path, attempt to find it
+            # relative to the config file.
+            if not os.path.isfile(sim_conf_file):
+                sim_conf_file = os.path.join(
+                    self.user_params[Config.key_config_path.value],
+                    sim_conf_file)
+                if not os.path.isfile(sim_conf_file):
+                    self.log.error("Unable to load user config %s ",
+                                   sim_conf_file)
+
+        tasks = [(self._setup_device, [ad, sim_conf_file, qxdm_log_mask_cfg])
+                 for ad in self.android_devices]
+        return multithread_func(self.log, tasks)
+
+    def _init_device(self, ad):
+        synchronize_device_time(ad)
+        ad.log_path = self.log_path
+        print_radio_info(ad)
+        unlock_sim(ad)
+        ad.wakeup_screen()
+        ad.adb.shell("input keyevent 82")
+
+    def wait_for_sim_ready(self,ad):
+        wait_for_sim_ready_on_sim_config = {
+              SINGLE_SIM_CONFIG : lambda:wait_for_sim_ready_by_adb(self.log,ad),
+              MULTI_SIM_CONFIG : lambda:wait_for_sims_ready_by_adb(self.log,ad)
+              }
+        if not wait_for_sim_ready_on_sim_config[self.sim_config["config"]]:
+            raise signals.TestAbortClass("unable to load the SIM")
+
+    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)
+        if self.user_params.get("enable_connectivity_metrics", False):
+            enable_connectivity_metrics(ad)
+        if self.user_params.get("build_id_override", False):
+            build_postfix = self.user_params.get("build_id_postfix",
+                                                 "LAB_TEST")
+            build_id_override(
+                ad,
+                new_build_id=self.user_params.get("build_id_override_with",
+                                                  None),
+                postfix=build_postfix)
+        if self.enable_radio_log_on:
+            enable_radio_log_on(ad)
+        list_of_models = ["sdm", "msm", "kon", "lit"]
+        if any(model in ad.model for model in list_of_models):
+            phone_mode = "ssss"
+            if hasattr(ad, "mtp_dsds"):
+                phone_mode = "dsds"
+            if ad.adb.getprop("persist.radio.multisim.config") != phone_mode:
+                ad.adb.shell("setprop persist.radio.multisim.config %s" \
+                             % phone_mode)
+                reboot_device(ad)
+
+        stop_qxdm_logger(ad)
+        if ad.qxdm_log:
+            qxdm_log_mask = getattr(ad, "qxdm_log_mask", None)
+            if qxdm_log_mask_cfg:
+                qxdm_mask_path = self.user_params.get("qxdm_log_path",
+                                                      DEFAULT_QXDM_LOG_PATH)
+                ad.adb.shell("mkdir %s" % qxdm_mask_path, ignore_status=True)
+                ad.log.info("Push %s to %s", qxdm_log_mask_cfg, qxdm_mask_path)
+                ad.adb.push("%s %s" % (qxdm_log_mask_cfg, qxdm_mask_path))
+                mask_file_name = os.path.split(qxdm_log_mask_cfg)[-1]
+                qxdm_log_mask = os.path.join(qxdm_mask_path, mask_file_name)
+            set_qxdm_logger_command(ad, mask=qxdm_log_mask)
+            start_qxdm_logger(ad, utils.get_current_epoch_time())
+        elif ad.sdm_log:
+            start_sdm_logger(ad)
+        else:
+            disable_qxdm_logger(ad)
+        if not unlock_sim(ad):
+            raise signals.TestAbortClass("unable to unlock the SIM")
+
+        # If device is setup already, skip the following setup procedures
+        if getattr(ad, "telephony_test_setup", None):
+            return True
+
+        # eSIM enablement
+        if hasattr(ad, "fi_esim"):
+            if not ensure_wifi_connected(self.log, ad, self.wifi_network_ssid,
+                                         self.wifi_network_pass):
+                ad.log.error("Failed to connect to wifi")
+            if check_google_fi_activated(ad):
+                ad.log.info("Google Fi is already Activated")
+            else:
+                install_googleaccountutil_apk(ad, self.account_util)
+                add_google_account(ad)
+                install_googlefi_apk(ad, self.fi_util)
+                if not activate_google_fi_account(ad):
+                    ad.log.error("Failed to activate Fi")
+                check_google_fi_activated(ad)
+        if hasattr(ad, "dsds"):
+            sim_mode = ad.droid.telephonyGetPhoneCount()
+            if sim_mode == 1:
+                ad.log.info("Phone in Single SIM Mode")
+                if not phone_switch_to_msim_mode(ad):
+                    ad.log.error("Failed to switch to Dual SIM Mode")
+                    return False
+            elif sim_mode == 2:
+                ad.log.info("Phone already in Dual SIM Mode")
+        if get_sim_state(ad) in (SIM_STATE_ABSENT, SIM_STATE_UNKNOWN):
+            ad.log.info("Device has no or unknown SIM in it")
+            # eSIM needs activation
+            activate_esim_using_suw(ad)
+            ensure_phone_idle(self.log, ad)
+        elif self.user_params.get("Attenuator"):
+            ad.log.info("Device in chamber room")
+            ensure_phone_idle(self.log, ad)
+            setup_droid_properties(self.log, ad, sim_conf_file)
+        else:
+            self.wait_for_sim_ready(ad)
+            ensure_phone_default_state(self.log, ad)
+            setup_droid_properties(self.log, ad, sim_conf_file)
+
+        if hasattr(ad, "dsds"):
+            default_slot = getattr(ad, "default_slot", 0)
+            if get_subid_from_slot_index(ad.log, ad, default_slot) != INVALID_SUB_ID:
+                ad.log.info("Slot %s is the default slot.", default_slot)
+                set_default_sub_for_all_services(ad, default_slot)
+            else:
+                ad.log.warning("Slot %s is NOT a valid slot. Slot %s will be used by default.",
+                    default_slot, 1-default_slot)
+                set_default_sub_for_all_services(ad, 1-default_slot)
+
+        # Activate WFC on Verizon, AT&T and Canada operators as per # b/33187374 &
+        # b/122327716
+        activate_wfc_on_device(self.log, ad)
+
+        # Sub ID setup
+        initial_set_up_for_subid_infomation(self.log, ad)
+
+
+        #try:
+        #    ad.droid.wifiEnableVerboseLogging(WIFI_VERBOSE_LOGGING_ENABLED)
+        #except Exception:
+        #    pass
+
+        # Disable Emergency alerts
+        # Set chrome browser start with no-first-run verification and
+        # disable-fre. Give permission to read from and write to storage.
+        for cmd in ("pm disable com.android.cellbroadcastreceiver",
+                    "pm grant com.android.chrome "
+                    "android.permission.READ_EXTERNAL_STORAGE",
+                    "pm grant com.android.chrome "
+                    "android.permission.WRITE_EXTERNAL_STORAGE",
+                    "rm /data/local/chrome-command-line",
+                    "am set-debug-app --persistent com.android.chrome",
+                    'echo "chrome --no-default-browser-check --no-first-run '
+                    '--disable-fre" > /data/local/tmp/chrome-command-line'):
+            ad.adb.shell(cmd, ignore_status=True)
+
+        # Curl for 2016/7 devices
+        if not getattr(ad, "curl_capable", False):
+            try:
+                out = ad.adb.shell("/data/curl --version")
+                if not out or "not found" in out:
+                    if int(ad.adb.getprop("ro.product.first_api_level")) >= 25:
+                        tel_data = self.user_params.get("tel_data", "tel_data")
+                        if isinstance(tel_data, list):
+                            tel_data = tel_data[0]
+                        curl_file_path = os.path.join(tel_data, "curl")
+                        if not os.path.isfile(curl_file_path):
+                            curl_file_path = os.path.join(
+                                self.user_params[Config.key_config_path.value],
+                                curl_file_path)
+                        if os.path.isfile(curl_file_path):
+                            ad.log.info("Pushing Curl to /data dir")
+                            ad.adb.push("%s /data" % (curl_file_path))
+                            ad.adb.shell(
+                                "chmod 777 /data/curl", ignore_status=True)
+                else:
+                    setattr(ad, "curl_capable", True)
+            except Exception:
+                ad.log.info("Failed to push curl on this device")
+
+        # Ensure that a test class starts from a consistent state that
+        # improves chances of valid network selection and facilitates
+        # logging.
+        try:
+            if not set_phone_screen_on(self.log, ad):
+                self.log.error("Failed to set phone screen-on time.")
+                return False
+            if not set_phone_silent_mode(self.log, ad):
+                self.log.error("Failed to set phone silent mode.")
+                return False
+            ad.droid.telephonyAdjustPreciseCallStateListenLevel(
+                PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND, True)
+            ad.droid.telephonyAdjustPreciseCallStateListenLevel(
+                PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING, True)
+            ad.droid.telephonyAdjustPreciseCallStateListenLevel(
+                PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND, True)
+        except Exception as e:
+            self.log.error("Failure with %s", e)
+        setattr(ad, "telephony_test_setup", True)
+        return True
+
+    def _teardown_device(self, ad):
+        try:
+            stop_qxdm_logger(ad)
+            stop_sdm_logger(ad)
+        except Exception as e:
+            self.log.error("Failure with %s", e)
+        try:
+            ad.droid.disableDevicePassword()
+        except Exception as e:
+            self.log.error("Failure with %s", e)
+        if self.user_params.get("enable_connectivity_metrics", False):
+            if not ensure_wifi_connected(self.log, ad, self.wifi_network_ssid,
+                                         self.wifi_network_pass):
+                ad.log.error("Failed to connect to wifi")
+            force_connectivity_metrics_upload(ad)
+            time.sleep(30)
+        try:
+            ad.droid.wifiEnableVerboseLogging(WIFI_VERBOSE_LOGGING_DISABLED)
+        except Exception as e:
+            self.log.error("Failure with %s", e)
+        try:
+            if self.user_params.get("build_id_override",
+                                    False) and self.user_params.get(
+                                        "recover_build_id", False):
+                recover_build_id(ad)
+        except Exception as e:
+            self.log.error("Failure with %s", e)
+
+    def teardown_class(self):
+        tasks = [(self._teardown_device, [ad]) for ad in self.android_devices]
+        multithread_func(self.log, tasks)
+        return True
+
+    def setup_test(self):
+        if getattr(self, "qxdm_log", True):
+            if not self.user_params.get("qxdm_log_mask_cfg", None):
+                if "wfc" in self.test_name:
+                    for ad in self.android_devices:
+                        if not getattr(ad, "qxdm_logger_command", None) or (
+                                "IMS_DS_CNE_LnX_Golden.cfg" not in getattr(
+                                    ad, "qxdm_logger_command", "")):
+                            set_qxdm_logger_command(
+                                ad, "IMS_DS_CNE_LnX_Golden.cfg")
+                else:
+                    for ad in self.android_devices:
+                        if not getattr(ad, "qxdm_logger_command", None) or (
+                                "IMS_DS_CNE_LnX_Golden.cfg" in getattr(
+                                    ad, "qxdm_logger_command", "")):
+                            set_qxdm_logger_command(ad, None)
+            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:
+            mask = getattr(self, "tcpdump_mask", "all")
+            interface = getattr(self, "tcpdump_interface", "wlan0")
+            start_tcpdumps(
+                self.android_devices,
+                begin_time=self.begin_time,
+                interface=interface,
+                mask=mask)
+        else:
+            stop_tcpdumps(self.android_devices)
+        for ad in self.android_devices:
+            if self.skip_reset_between_cases:
+                ensure_phone_idle(self.log, ad)
+            else:
+                ensure_phone_default_state(self.log, ad)
+            for session in ad._sl4a_manager.sessions.values():
+                ed = session.get_event_dispatcher()
+                ed.clear_all_events()
+            output = ad.adb.logcat("-t 1")
+            match = re.search(r"\d+-\d+\s\d+:\d+:\d+.\d+", output)
+            if match:
+                ad.test_log_begin_time = match.group(0)
+
+    def teardown_test(self):
+        stop_tcpdumps(self.android_devices)
+
+    def on_fail(self, test_name, begin_time):
+        self._take_bug_report(test_name, begin_time)
+
+    def on_pass(self, test_name, begin_time):
+        if self.save_passing_logs:
+            self._take_bug_report(test_name, begin_time)
+
+    def _ad_take_extra_logs(self, ad, test_name, begin_time):
+        ad.adb.wait_for_device()
+        result = True
+
+        try:
+            # get tcpdump and screen shot log
+            get_tcpdump_log(ad, test_name, begin_time)
+            get_screen_shot_log(ad, test_name, begin_time)
+        except Exception as e:
+            ad.log.error("Exception error %s", e)
+            result = False
+
+        try:
+            ad.check_crash_report(test_name, begin_time, log_crash_report=True)
+        except Exception as e:
+            ad.log.error("Failed to check crash report for %s with error %s",
+                         test_name, e)
+            result = False
+
+        extra_qxdm_logs_in_seconds = self.user_params.get(
+            "extra_qxdm_logs_in_seconds", 60 * 3)
+        if getattr(ad, "qxdm_log", True):
+            # Gather qxdm log modified 3 minutes earlier than test start time
+            if begin_time:
+                qxdm_begin_time = begin_time - 1000 * extra_qxdm_logs_in_seconds
+            else:
+                qxdm_begin_time = None
+            try:
+                time.sleep(10)
+                ad.get_qxdm_logs(test_name, qxdm_begin_time)
+            except Exception as e:
+                ad.log.error("Failed to get QXDM log for %s with error %s",
+                             test_name, e)
+                result = False
+        if getattr(ad, "sdm_log", False):
+            # Gather sdm log modified 3 minutes earlier than test start time
+            if begin_time:
+                sdm_begin_time = begin_time - 1000 * extra_qxdm_logs_in_seconds
+            else:
+                sdm_begin_time = None
+            try:
+                time.sleep(10)
+                ad.get_sdm_logs(test_name, sdm_begin_time)
+            except Exception as e:
+                ad.log.error("Failed to get SDM log for %s with error %s",
+                             test_name, e)
+                result = False
+
+        return result
+
+    def _take_bug_report(self, test_name, begin_time):
+        if self._skip_bug_report(test_name):
+            return
+        dev_num = getattr(self, "number_of_devices", None) or len(
+            self.android_devices)
+        tasks = [(self._ad_take_bugreport, (ad, test_name, begin_time))
+                 for ad in self.android_devices[:dev_num]]
+        tasks.extend([(self._ad_take_extra_logs, (ad, test_name, begin_time))
+                      for ad in self.android_devices[:dev_num]])
+        run_multithread_func(self.log, tasks)
+        for ad in self.android_devices[:dev_num]:
+            if getattr(ad, "reboot_to_recover", False):
+                reboot_device(ad)
+                ad.reboot_to_recover = False
+        # Zip log folder
+        if not self.user_params.get("zip_log", False): return
+        src_dir = os.path.join(self.log_path, test_name)
+        os.makedirs(src_dir, exist_ok=True)
+        file_name = "%s_%s" % (src_dir, begin_time)
+        self.log.info("Zip folder %s to %s.zip", src_dir, file_name)
+        shutil.make_archive(file_name, "zip", src_dir)
+        shutil.rmtree(src_dir)
+
+    def _block_all_test_cases(self, tests, reason='Failed class setup'):
+        """Over-write _block_all_test_cases in BaseTestClass."""
+        for (i, (test_name, test_func)) in enumerate(tests):
+            signal = signals.TestFailure(reason)
+            record = records.TestResultRecord(test_name, self.TAG)
+            record.test_begin()
+            # mark all test cases as FAIL
+            record.test_fail(signal)
+            self.results.add_record(record)
+            # only gather bug report for the first test case
+            if i == 0:
+                self.on_fail(test_name, record.begin_time)
+
+    def get_stress_test_number(self):
+        """Gets the stress_test_number param from user params.
+
+        Gets the stress_test_number param. If absent, returns default 100.
+        """
+        return int(self.user_params.get("stress_test_number", 100))
diff --git a/acts_tests/acts_contrib/test_utils/tel/__init__.py b/acts_tests/acts_contrib/test_utils/tel/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/tel/anritsu_utils.py b/acts_tests/acts_contrib/test_utils/tel/anritsu_utils.py
new file mode 100644
index 0000000..04a9e35
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/anritsu_utils.py
@@ -0,0 +1,2769 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+
+from queue import Empty
+from datetime import datetime
+from acts.controllers.anritsu_lib import band_constants
+from acts.controllers.anritsu_lib._anritsu_utils import AnritsuUtils
+from acts.controllers.anritsu_lib.md8475a import BtsNumber
+from acts.controllers.anritsu_lib.md8475a import BtsNwNameEnable
+from acts.controllers.anritsu_lib.md8475a import BtsServiceState
+from acts.controllers.anritsu_lib.md8475a import BtsTechnology
+from acts.controllers.anritsu_lib.md8475a import CsfbType
+from acts.controllers.anritsu_lib.md8475a import ImsCscfCall
+from acts.controllers.anritsu_lib.md8475a import ImsCscfStatus
+from acts.controllers.anritsu_lib.md8475a import MD8475A
+from acts.controllers.anritsu_lib.md8475a import ReturnToEUTRAN
+from acts.controllers.anritsu_lib.md8475a import VirtualPhoneStatus
+from acts.controllers.anritsu_lib.md8475a import TestProcedure
+from acts.controllers.anritsu_lib.md8475a import TestPowerControl
+from acts.controllers.anritsu_lib.md8475a import TestMeasurement
+from acts.controllers.anritsu_lib.md8475a import Switch
+from acts.controllers.anritsu_lib.md8475a import BtsPacketRate
+from acts_contrib.test_utils.tel.tel_defines import CALL_TEARDOWN_PHONE
+from acts_contrib.test_utils.tel.tel_defines import CALL_TEARDOWN_REMOTE
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALL_DROP
+from acts_contrib.test_utils.tel.tel_defines import RAT_1XRTT
+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_LONG
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
+from acts_contrib.test_utils.tel.tel_defines import EventCmasReceived
+from acts_contrib.test_utils.tel.tel_defines import EventEtwsReceived
+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
+
+# Timers
+# Time to wait after registration before sending a command to Anritsu
+# to ensure the phone has sufficient time to reconfigure based on new
+# network in Anritsu
+WAIT_TIME_ANRITSU_REG_AND_OPER = 10
+# Time to wait after registration to ensure the phone
+# has sufficient time to reconfigure based on new network in Anritsu
+WAIT_TIME_ANRITSU_REG_AND_CALL = 10
+# Max time to wait for Anritsu's virtual phone state change
+MAX_WAIT_TIME_VIRTUAL_PHONE_STATE = 45
+# Time to wait for Anritsu's IMS CSCF state change
+MAX_WAIT_TIME_IMS_CSCF_STATE = 30
+# Time to wait for before aSRVCC
+WAIT_TIME_IN_ALERT = 5
+
+# SIM card names
+P0250Ax = "P0250Ax"
+VzW12349 = "VzW12349"
+P0135Ax = "P0135Ax"
+FiTMO = "FiTMO"
+FiSPR = "FiSPR"
+FiUSCC = "FiUSCC"
+
+# Test PLMN information
+TEST_PLMN_LTE_NAME = "MD8475A_LTE"
+TEST_PLMN_WCDMA_NAME = "MD8475A_WCDMA"
+TEST_PLMN_GSM_NAME = "MD8475A_GSM"
+TEST_PLMN_1X_NAME = "MD8475A_1X"
+TEST_PLMN_1_MCC = "001"
+TEST_PLMN_1_MNC = "01"
+DEFAULT_MCC = "310"
+DEFAULT_MNC = "260"
+DEFAULT_RAC = 1
+DEFAULT_LAC = 1
+VzW_MCC = "311"
+VzW_MNC = "480"
+TMO_MCC = "310"
+TMO_MNC = "260"
+Fi_TMO_MCC = "310"
+Fi_TMO_MNC = "260"
+Fi_SPR_MCC = "310"
+Fi_SPR_MNC = "120"
+Fi_USCC_MCC = "311"
+Fi_USCC_MNC = "580"
+
+# IP address information for internet sharing
+#GATEWAY_IPV4_ADDR = "192.168.137.1"
+#UE_IPV4_ADDR_1 = "192.168.137.2"
+#UE_IPV4_ADDR_2 = "192.168.137.3"
+#UE_IPV4_ADDR_3 = "192.168.137.4"
+#DNS_IPV4_ADDR = "192.168.137.1"
+#CSCF_IPV4_ADDR = "192.168.137.1"
+
+# Default IP address in Smart Studio, work for Internet Sharing with and
+# without WLAN ePDG server. Remember to add 192.168.1.2 to Ethernet 0
+# on MD8475A after turn on Windows' Internet Coonection Sharing
+GATEWAY_IPV4_ADDR = "192.168.1.2"
+UE_IPV4_ADDR_1 = "192.168.1.1"
+UE_IPV4_ADDR_2 = "192.168.1.11"
+UE_IPV4_ADDR_3 = "192.168.1.21"
+UE_IPV6_ADDR_1 = "2001:0:0:1::1"
+UE_IPV6_ADDR_2 = "2001:0:0:2::1"
+UE_IPV6_ADDR_3 = "2001:0:0:3::1"
+DNS_IPV4_ADDR = "192.168.1.12"
+CSCF_IPV4_ADDR = "192.168.1.2"
+CSCF_IPV6_ADDR = "2001:0:0:1::2"
+CSCF_IPV6_ADDR_2 = "2001:0:0:2::2"
+CSCF_IPV6_ADDR_3 = "2001:0:0:3::2"
+
+# Google Fi IP Config:
+
+Fi_GATEWAY_IPV4_ADDR_Data = "100.107.235.94"
+Fi_GATEWAY_IPV6_ADDR_Data = "fe80::aef2:c5ff:fe71:4b9"
+Fi_GATEWAY_IPV4_ADDR_IMS_911 = "192.168.1.2"
+Fi_GATEWAY_IPV6_ADDR_IMS_911 = "2001:0:0:1::2"
+
+Fi_UE_IPV4_ADDR_Data = "100.107.235.81"
+Fi_UE_IPV4_ADDR_IMS = "192.168.1.1"
+Fi_UE_IPV4_ADDR_911 = "192.168.1.11"
+Fi_UE_IPV6_ADDR_Data = "2620::1000:1551:1140:c0f9:d6a8:44eb"
+Fi_UE_IPV6_ADDR_IMS = "2001:0:0:1::1"
+Fi_UE_IPV6_ADDR_911 = "2001:0:0:2::1"
+
+Fi_DNS_IPV4_ADDR_Pri = "8.8.8.8"
+Fi_DNS_IPV4_ADDR_Sec = "8.8.8.4"
+Fi_DNS_IPV6_ADDR = "2001:4860:4860::8888"
+
+Fi_CSCF_IPV4_ADDR_Data = "192.168.1.2"
+Fi_CSCF_IPV6_ADDR_Data = "2001:0:0:1::2"
+Fi_CSCF_IPV4_ADDR_IMS = "192.168.1.2"
+Fi_CSCF_IPV6_ADDR_IMS = "2001:0:0:1::3"
+Fi_CSCF_IPV4_ADDR_911 = "192.168.1.12"
+Fi_CSCF_IPV6_ADDR_911 = "2001:0:0:2::2"
+
+# Default Cell Parameters
+DEFAULT_OUTPUT_LEVEL = -30
+DEFAULT_1X_OUTPUT_LEVEL = -35
+DEFAULT_INPUT_LEVEL = 0
+DEFAULT_LTE_BAND = [2, 4]
+Fi_LTE_TMO_BAND = [4]
+Fi_LTE_SPR_BAND = [25]
+Fi_LTE_USCC_BAND = [12]
+Fi_GSM_TMO_BAND = band_constants.GSM_BAND_PGSM900
+DEFAULT_WCDMA_BAND = 1
+DEFAULT_WCDMA_PACKET_RATE = BtsPacketRate.WCDMA_DLHSAUTO_REL7_ULHSAUTO
+DEFAULT_GSM_BAND = band_constants.GSM_BAND_GSM850
+
+#Google Fi CDMA Bands
+
+Fi_USCC1X_MCC = 209
+Fi_USCC1X_BAND = 1
+Fi_USCC1X_CH = 600
+Fi_USCC1X_SID = 5
+Fi_USCC1X_NID = 21
+
+Fi_SPR1X_MCC = 320
+Fi_SPR1X_BAND = 1
+Fi_SPR1X_CH = 600
+Fi_SPR1X_SID = 4183
+Fi_SPR1X_NID = 233
+
+Fi_EVDO_BAND = 1
+Fi_EVDO_CH = 625
+Fi_EVDO_SECTOR_ID = "00000000,00000000,00000000,00000000"
+
+DEFAULT_CDMA1X_BAND = 0
+DEFAULT_CDMA1X_CH = 356
+DEFAULT_CDMA1X_SID = 0
+DEFAULT_CDMA1X_NID = 65535
+DEFAULT_EVDO_BAND = 0
+DEFAULT_EVDO_CH = 356
+DEFAULT_EVDO_SECTOR_ID = "00000000,00000000,00000000,00000000"
+VzW_CDMA1x_BAND = 1
+VzW_CDMA1x_CH = 150
+VzW_CDMA1X_SID = 26
+VzW_CDMA1X_NID = 65535
+VzW_EVDO_BAND = 0
+VzW_EVDO_CH = 384
+VzW_EVDO_SECTOR_ID = "12345678,00000000,00000000,00000000"
+DEFAULT_T_MODE = "TM1"
+DEFAULT_DL_ANTENNA = 1
+
+# CMAS Message IDs
+CMAS_MESSAGE_PRESIDENTIAL_ALERT = hex(0x1112)
+CMAS_MESSAGE_EXTREME_IMMEDIATE_OBSERVED = hex(0x1113)
+CMAS_MESSAGE_EXTREME_IMMEDIATE_LIKELY = hex(0x1114)
+CMAS_MESSAGE_EXTREME_EXPECTED_OBSERVED = hex(0x1115)
+CMAS_MESSAGE_EXTREME_EXPECTED_LIKELY = hex(0x1116)
+CMAS_MESSAGE_SEVERE_IMMEDIATE_OBSERVED = hex(0x1117)
+CMAS_MESSAGE_SEVERE_IMMEDIATE_LIKELY = hex(0x1118)
+CMAS_MESSAGE_SEVERE_EXPECTED_OBSERVED = hex(0x1119)
+CMAS_MESSAGE_SEVERE_EXPECTED_LIKELY = hex(0x111A)
+CMAS_MESSAGE_CHILD_ABDUCTION_EMERGENCY = hex(0x111B)
+CMAS_MESSAGE_MONTHLY_TEST = hex(0x111C)
+CMAS_MESSAGE_CMAS_EXECERCISE = hex(0x111D)
+
+# ETWS Message IDs
+ETWS_WARNING_EARTHQUAKE = hex(0x1100)
+ETWS_WARNING_TSUNAMI = hex(0x1101)
+ETWS_WARNING_EARTHQUAKETSUNAMI = hex(0x1102)
+ETWS_WARNING_TEST_MESSAGE = hex(0x1103)
+ETWS_WARNING_OTHER_EMERGENCY = hex(0x1104)
+
+# C2K CMAS Message Constants
+CMAS_C2K_CATEGORY_PRESIDENTIAL = "Presidential"
+CMAS_C2K_CATEGORY_EXTREME = "Extreme"
+CMAS_C2K_CATEGORY_SEVERE = "Severe"
+CMAS_C2K_CATEGORY_AMBER = "AMBER"
+CMAS_C2K_CATEGORY_CMASTEST = "CMASTest"
+
+CMAS_C2K_PRIORITY_NORMAL = "Normal"
+CMAS_C2K_PRIORITY_INTERACTIVE = "Interactive"
+CMAS_C2K_PRIORITY_URGENT = "Urgent"
+CMAS_C2K_PRIORITY_EMERGENCY = "Emergency"
+
+CMAS_C2K_RESPONSETYPE_SHELTER = "Shelter"
+CMAS_C2K_RESPONSETYPE_EVACUATE = "Evacuate"
+CMAS_C2K_RESPONSETYPE_PREPARE = "Prepare"
+CMAS_C2K_RESPONSETYPE_EXECUTE = "Execute"
+CMAS_C2K_RESPONSETYPE_MONITOR = "Monitor"
+CMAS_C2K_RESPONSETYPE_AVOID = "Avoid"
+CMAS_C2K_RESPONSETYPE_ASSESS = "Assess"
+CMAS_C2K_RESPONSETYPE_NONE = "None"
+
+CMAS_C2K_SEVERITY_EXTREME = "Extreme"
+CMAS_C2K_SEVERITY_SEVERE = "Severe"
+
+CMAS_C2K_URGENCY_IMMEDIATE = "Immediate"
+CMAS_C2K_URGENCY_EXPECTED = "Expected"
+
+CMAS_C2K_CERTIANTY_OBSERVED = "Observed"
+CMAS_C2K_CERTIANTY_LIKELY = "Likely"
+
+#PDN Numbers
+PDN_NO_1 = 1
+PDN_NO_2 = 2
+PDN_NO_3 = 3
+PDN_NO_4 = 4
+PDN_NO_5 = 5
+
+# IMS Services parameters
+DEFAULT_VNID = 1
+NDP_NIC_NAME = '"Intel(R) 82577LM Gigabit Network Connection"'
+CSCF_Monitoring_UA_URI = '"sip:+11234567890@test.3gpp.com"'
+TMO_CSCF_Monitoring_UA_URI = '"sip:001010123456789@msg.lab.t-mobile.com"'
+CSCF_Virtual_UA_URI = '"sip:+11234567891@test.3gpp.com"'
+TMO_CSCF_Virtual_UA_URI = '"sip:0123456789@ims.mnc01.mcc001.3gppnetwork.org"'
+CSCF_HOSTNAME = '"ims.mnc01.mcc001.3gppnetwork.org"'
+TMO_USERLIST_NAME = "310260123456789@msg.lab.t-mobile.com"
+VZW_USERLIST_NAME = "001010123456789@test.3gpp.com"
+
+# Google Fi IMS Services parameters
+Fi_CSCF_Monitoring_UA_URI = '"sip:310260971239432@ims.mnc260.mcc310.3gppnetwork.org"'
+Fi_CSCF_Virtual_UA_URI = '"sip:0123456789@msg.pc.t-mobile.com"'
+Fi_CSCF_HOSTNAME = '"ims.mnc260.mcc310.3gppnetwork.org"'
+Fi_USERLIST_NAME = "310260971239432@msg.pc.t-mobile.com"
+
+#Cell Numbers
+CELL_1 = 1
+CELL_2 = 2
+
+# default ims virtual network id for Anritsu ims call test.
+DEFAULT_IMS_VIRTUAL_NETWORK_ID = 1
+
+def cb_serial_number():
+    """ CMAS/ETWS serial number generator """
+    i = 0x3000
+    while True:
+        yield i
+        i += 1
+
+
+def set_usim_parameters(anritsu_handle, sim_card):
+    """ set USIM parameters in MD8475A simulationn parameter
+
+    Args:
+        anritsu_handle: anritusu device object.
+        sim_card : "P0250Ax" or "12349"
+
+    Returns:
+        None
+    """
+    if sim_card == P0250Ax:
+        anritsu_handle.usim_key = "000102030405060708090A0B0C0D0E0F"
+    elif sim_card == P0135Ax:
+        anritsu_handle.usim_key = "00112233445566778899AABBCCDDEEFF"
+    elif sim_card == VzW12349:
+        anritsu_handle.usim_key = "465B5CE8B199B49FAA5F0A2EE238A6BC"
+        anritsu_handle.send_command("IMSI 311480012345678")
+        anritsu_handle.send_command("SECURITY3G MILENAGE")
+        anritsu_handle.send_command(
+            "MILENAGEOP 5F1D289C5D354D0A140C2548F5F3E3BA")
+    elif sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        anritsu_handle.usim_key = "000102030405060708090A0B0C0D0E0F"
+
+
+def save_anritsu_log_files(anritsu_handle, test_name, user_params):
+    """ saves the anritsu smart studio log files
+        The logs should be saved in Anritsu system. Need to provide
+        log folder path in Anritsu system
+
+    Args:
+        anritsu_handle: anritusu device object.
+        test_name: test case name
+        user_params : user supplied parameters list
+
+    Returns:
+        None
+    """
+    md8475a_log_folder = user_params["anritsu_log_file_path"]
+    file_name = getfilenamewithtimestamp(test_name)
+    seq_logfile = "{}\\{}_seq.csv".format(md8475a_log_folder, file_name)
+    msg_logfile = "{}\\{}_msg.csv".format(md8475a_log_folder, file_name)
+    trace_logfile = "{}\\{}_trace.lgex".format(md8475a_log_folder, file_name)
+    anritsu_handle.save_sequence_log(seq_logfile)
+    anritsu_handle.save_message_log(msg_logfile)
+    anritsu_handle.save_trace_log(trace_logfile, "BINARY", 1, 0, 0)
+    anritsu_handle.clear_sequence_log()
+    anritsu_handle.clear_message_log()
+
+
+def getfilenamewithtimestamp(test_name):
+    """ Gets the test name appended with current time
+
+    Args:
+        test_name : test case name
+
+    Returns:
+        string of test name appended with current time
+    """
+    time_stamp = datetime.now().strftime("%m-%d-%Y_%H-%M-%S")
+    return "{}_{}".format(test_name, time_stamp)
+
+
+def _init_lte_bts(bts, user_params, cell_no, sim_card):
+    """ initializes the LTE BTS
+        All BTS parameters should be set here
+
+    Args:
+        bts: BTS object.
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        None
+    """
+    bts.nw_fullname_enable = BtsNwNameEnable.NAME_ENABLE
+    bts.nw_fullname = TEST_PLMN_LTE_NAME
+    bts.mcc = get_lte_mcc(user_params, cell_no, sim_card)
+    bts.mnc = get_lte_mnc(user_params, cell_no, sim_card)
+    bts.band = get_lte_band(user_params, cell_no, sim_card)
+    bts.transmode = get_transmission_mode(user_params, cell_no)
+    bts.dl_antenna = get_dl_antenna(user_params, cell_no)
+    bts.output_level = DEFAULT_OUTPUT_LEVEL
+    bts.input_level = DEFAULT_INPUT_LEVEL
+
+
+def _init_wcdma_bts(bts, user_params, cell_no, sim_card):
+    """ initializes the WCDMA BTS
+        All BTS parameters should be set here
+
+    Args:
+        bts: BTS object.
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        None
+    """
+    bts.nw_fullname_enable = BtsNwNameEnable.NAME_ENABLE
+    bts.nw_fullname = TEST_PLMN_WCDMA_NAME
+    bts.mcc = get_wcdma_mcc(user_params, cell_no, sim_card)
+    bts.mnc = get_wcdma_mnc(user_params, cell_no, sim_card)
+    bts.band = get_wcdma_band(user_params, cell_no)
+    bts.rac = get_wcdma_rac(user_params, cell_no)
+    bts.lac = get_wcdma_lac(user_params, cell_no)
+    bts.output_level = DEFAULT_OUTPUT_LEVEL
+    bts.input_level = DEFAULT_INPUT_LEVEL
+    bts.packet_rate = DEFAULT_WCDMA_PACKET_RATE
+
+
+def _init_gsm_bts(bts, user_params, cell_no, sim_card):
+    """ initializes the GSM BTS
+        All BTS parameters should be set here
+
+    Args:
+        bts: BTS object.
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        None
+    """
+    bts.nw_fullname_enable = BtsNwNameEnable.NAME_ENABLE
+    bts.nw_fullname = TEST_PLMN_GSM_NAME
+    bts.mcc = get_gsm_mcc(user_params, cell_no, sim_card)
+    bts.mnc = get_gsm_mnc(user_params, cell_no, sim_card)
+    bts.band = get_gsm_band(user_params, cell_no, sim_card)
+    bts.rac = get_gsm_rac(user_params, cell_no)
+    bts.lac = get_gsm_lac(user_params, cell_no)
+    bts.output_level = DEFAULT_OUTPUT_LEVEL
+    bts.input_level = DEFAULT_INPUT_LEVEL
+
+
+def _init_1x_bts(bts, user_params, cell_no, sim_card):
+    """ initializes the 1X BTS
+        All BTS parameters should be set here
+
+    Args:
+        bts: BTS object.
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        None
+    """
+    bts.sector1_mcc = get_1x_mcc(user_params, cell_no, sim_card)
+    bts.band = get_1x_band(user_params, cell_no, sim_card)
+    bts.dl_channel = get_1x_channel(user_params, cell_no, sim_card)
+    bts.sector1_sid = get_1x_sid(user_params, cell_no, sim_card)
+    bts.sector1_nid = get_1x_nid(user_params, cell_no, sim_card)
+    bts.output_level = DEFAULT_1X_OUTPUT_LEVEL
+
+
+def _init_evdo_bts(bts, user_params, cell_no, sim_card):
+    """ initializes the EVDO BTS
+        All BTS parameters should be set here
+
+    Args:
+        bts: BTS object.
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        None
+    """
+    bts.band = get_evdo_band(user_params, cell_no, sim_card)
+    bts.dl_channel = get_evdo_channel(user_params, cell_no, sim_card)
+    bts.evdo_sid = get_evdo_sid(user_params, cell_no, sim_card)
+    bts.output_level = DEFAULT_1X_OUTPUT_LEVEL
+
+
+def _init_PDN(anritsu_handle,
+              sim_card,
+              pdn,
+              ipv4,
+              ipv6,
+              ims_binding,
+              vnid_number=DEFAULT_VNID):
+    """ initializes the PDN parameters
+        All PDN parameters should be set here
+
+    Args:
+        anritsu_handle: anritusu device object.
+        pdn: pdn object
+        ip_address : UE IP address
+        ims_binding: to bind with IMS VNID(1) or not
+
+    Returns:
+        None
+    """
+    # Setting IP address for internet connection sharing
+    # 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
+        if ims_binding:
+            pdn.pdn_ims = Switch.ENABLE
+            pdn.pdn_vnid = vnid_number
+            pdn.pdn_DG_selection = 'USER'
+            pdn.pdn_gateway_ipv4addr = Fi_GATEWAY_IPV4_ADDR_IMS_911
+            pdn.pdn_gateway_ipv6addr = Fi_GATEWAY_IPV6_ADDR_IMS_911
+
+        else:
+            anritsu_handle.gateway_ipv4addr = Fi_GATEWAY_IPV4_ADDR_Data
+            anritsu_handle.gateway_ipv6addr = Fi_GATEWAY_IPV6_ADDR_Data
+            pdn.primary_dns_address_ipv4 = Fi_DNS_IPV4_ADDR_Pri
+            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    
+    # Pixel Lab _init_PDN_
+    else:  
+        anritsu_handle.gateway_ipv4addr = GATEWAY_IPV4_ADDR
+        pdn.ue_address_ipv4 = ipv4
+        pdn.ue_address_ipv6 = ipv6
+        if ims_binding:
+            pdn.pdn_ims = Switch.ENABLE
+            pdn.pdn_vnid = vnid_number
+        else:
+            pdn.primary_dns_address_ipv4 = DNS_IPV4_ADDR
+            pdn.secondary_dns_address_ipv4 = DNS_IPV4_ADDR
+            pdn.cscf_address_ipv4 = CSCF_IPV4_ADDR
+
+
+def _init_IMS(anritsu_handle,
+              vnid,
+              sim_card=None,
+              ipv4_address=CSCF_IPV4_ADDR,
+              ipv6_address=CSCF_IPV6_ADDR,
+              ip_type="IPV4V6",
+              auth=False):
+    """ initializes the IMS VNID parameters
+        All IMS parameters should be set here
+
+    Args:
+        anritsu_handle: anritusu device object.
+        vnid: IMS Services object
+
+    Returns:
+        None
+    """
+    # vnid.sync = Switch.ENABLE # supported in 6.40a release
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        vnid.cscf_address_ipv4 = ipv4_address
+        vnid.cscf_address_ipv6 = ipv6_address
+        vnid.imscscf_iptype = ip_type
+        vnid.dns = Switch.DISABLE
+        vnid.ndp_nic = NDP_NIC_NAME
+        vnid.ndp_prefix = ipv6_address
+        if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+            vnid.cscf_monitoring_ua = Fi_CSCF_Monitoring_UA_URI
+            vnid.cscf_virtual_ua = Fi_CSCF_Virtual_UA_URI
+            vnid.cscf_host_name = Fi_CSCF_HOSTNAME
+            vnid.cscf_ims_authentication = "ENABLE"
+            if auth:
+                vnid.cscf_ims_authentication = "ENABLE"
+                vnid.fi_cscf_userslist_add = Fi_USERLIST_NAME
+        else:
+            vnid.cscf_monitoring_ua = CSCF_Monitoring_UA_URI
+        vnid.psap = Switch.ENABLE
+        vnid.psap_auto_answer = Switch.ENABLE
+    else:   
+        vnid.cscf_address_ipv4 = CSCF_IPV4_ADDR
+        vnid.cscf_address_ipv6 = ipv6_address
+        vnid.imscscf_iptype = ip_type
+        vnid.dns = Switch.DISABLE
+        vnid.ndp_nic = NDP_NIC_NAME
+        vnid.ndp_prefix = ipv6_address
+        if sim_card == P0135Ax:
+            vnid.cscf_monitoring_ua = TMO_CSCF_Monitoring_UA_URI
+            vnid.cscf_virtual_ua = TMO_CSCF_Virtual_UA_URI
+            vnid.cscf_host_name = CSCF_HOSTNAME
+            vnid.cscf_precondition = "ENABLE"
+            vnid.cscf_ims_authentication = "DISABLE"
+            if auth:
+                vnid.cscf_ims_authentication = "ENABLE"
+                vnid.tmo_cscf_userslist_add = TMO_USERLIST_NAME
+        elif sim_card == VzW12349:
+            vnid.cscf_monitoring_ua = CSCF_Monitoring_UA_URI
+            vnid.cscf_virtual_ua = CSCF_Virtual_UA_URI
+            vnid.cscf_ims_authentication = "DISABLE"
+            if auth:
+                vnid.cscf_ims_authentication = "ENABLE"
+                vnid.vzw_cscf_userslist_add = VZW_USERLIST_NAME
+        else:
+            vnid.cscf_monitoring_ua = CSCF_Monitoring_UA_URI
+        vnid.psap = Switch.ENABLE
+        vnid.psap_auto_answer = Switch.ENABLE
+
+
+def set_system_model_lte_lte(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for LTE and LTE simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Lte and Wcdma BTS objects
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.LTE,
+                                        BtsTechnology.LTE)
+    # setting BTS parameters
+    lte1_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    lte2_bts = anritsu_handle.get_BTS(BtsNumber.BTS2)
+    _init_lte_bts(lte1_bts, user_params, CELL_1, sim_card)
+    _init_lte_bts(lte2_bts, user_params, CELL_2, sim_card)
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        pdn4 = anritsu_handle.get_PDN(PDN_NO_4)
+        pdn5 = anritsu_handle.get_PDN(PDN_NO_5)
+
+        # Initialize PDN IP address for internet connection sharing
+        _init_PDN(anritsu_handle, sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn2, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn3, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn4, Fi_UE_IPV4_ADDR_IMS,
+                  Fi_UE_IPV6_ADDR_IMS,
+                  True)
+        _init_PDN(anritsu_handle, sim_card, pdn5, Fi_UE_IPV4_ADDR_911,
+                  Fi_UE_IPV6_ADDR_911,
+                  True)
+        vnid1 = anritsu_handle.get_IMS(1)
+        vnid2 = anritsu_handle.get_IMS(2)
+        # _init_IMS(
+        #     anritsu_handle,
+        #     vnid1,
+        #     sim_card,
+        #     ipv4_address=CSCF_IPV4_ADDR,
+        #     ipv6_address=CSCF_IPV6_ADDR,
+        #     auth=False)
+        _init_IMS(
+            anritsu_handle,
+            vnid1,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_IMS,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_IMS,
+            auth=True)
+        _init_IMS(
+            anritsu_handle,
+            vnid2,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_911,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_911,
+            auth=False)
+    else:
+        _init_lte_bts(lte1_bts, user_params, CELL_1, sim_card)
+        _init_lte_bts(lte2_bts, user_params, CELL_2, sim_card)
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        # Initialize PDN IP address for internet connection sharing
+        _init_PDN(anritsu_handle, sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, True)
+        _init_PDN(anritsu_handle, sim_card, pdn2, UE_IPV4_ADDR_2, UE_IPV6_ADDR_2, False)
+        _init_PDN(anritsu_handle, sim_card, pdn3, UE_IPV4_ADDR_3, UE_IPV6_ADDR_3, True)
+        vnid1 = anritsu_handle.get_IMS(DEFAULT_VNID)
+        if sim_card == P0135Ax:
+            vnid2 = anritsu_handle.get_IMS(2)
+            vnid3 = anritsu_handle.get_IMS(3)
+            _init_IMS(
+                anritsu_handle,
+                vnid1,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR,
+                auth=True)
+            _init_IMS(
+                anritsu_handle,
+                vnid2,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_2,
+                ip_type="IPV6")
+            _init_IMS(
+                anritsu_handle,
+                vnid3,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_3,
+                ip_type="IPV6")
+        elif sim_card == VzW12349:
+            _init_IMS(anritsu_handle, vnid1, sim_card, auth=True)
+        else:
+            _init_IMS(anritsu_handle, vnid1, sim_card)
+        return [lte1_bts, lte2_bts]
+
+
+def set_system_model_wcdma_wcdma(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for WCDMA and WCDMA simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Lte and Wcdma BTS objects
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.WCDMA,
+                                        BtsTechnology.WCDMA)
+    # setting BTS parameters
+    wcdma1_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    wcdma2_bts = anritsu_handle.get_BTS(BtsNumber.BTS2)
+    _init_wcdma_bts(wcdma1_bts, user_params, CELL_1, sim_card)
+    _init_wcdma_bts(wcdma2_bts, user_params, CELL_2, sim_card)
+    pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+    # Initialize PDN IP address for internet connection sharing
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        _init_PDN(anritsu_handle, sim_card, pdn1, Fi_UE_IPV4_ADDR_Data, Fi_UE_IPV6_ADDR_Data,
+                  False)
+    else:
+        _init_PDN(anritsu_handle, sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, False)
+    return [wcdma1_bts, wcdma2_bts]
+
+
+def set_system_model_lte_wcdma(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for LTE and WCDMA simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Lte and Wcdma BTS objects
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.LTE, BtsTechnology.WCDMA)
+    # setting BTS parameters
+    lte_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    wcdma_bts = anritsu_handle.get_BTS(BtsNumber.BTS2)
+    _init_lte_bts(lte_bts, user_params, CELL_1, sim_card)
+    _init_wcdma_bts(wcdma_bts, user_params, CELL_2, sim_card)
+    pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+    pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+    pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+    # Initialize PDN IP address for internet connection sharing
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        pdn4 = anritsu_handle.get_PDN(PDN_NO_4)
+        pdn5 = anritsu_handle.get_PDN(PDN_NO_5)
+        # Initialize PDN IP address.
+        _init_PDN(anritsu_handle, sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn2, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn3, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn4, Fi_UE_IPV4_ADDR_IMS,
+                  Fi_UE_IPV6_ADDR_IMS,
+                  True)
+        _init_PDN(anritsu_handle, sim_card, pdn5, Fi_UE_IPV4_ADDR_911,
+                  Fi_UE_IPV6_ADDR_911,
+                  True)
+        vnid1 = anritsu_handle.get_IMS(1)
+        vnid2 = anritsu_handle.get_IMS(2)
+        _init_IMS(
+            anritsu_handle,
+            vnid1,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_IMS,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_IMS,
+            auth=True)
+        _init_IMS(
+            anritsu_handle,
+            vnid2,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_911,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_911,
+            auth=False)
+        return [lte_bts, wcdma_bts]
+    else:
+        _init_PDN(anritsu_handle, sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, True)
+        _init_PDN(anritsu_handle, sim_card, pdn2, UE_IPV4_ADDR_2, UE_IPV6_ADDR_2, False)
+        _init_PDN(anritsu_handle, sim_card, pdn3, UE_IPV4_ADDR_3, UE_IPV6_ADDR_3, True)
+        vnid1 = anritsu_handle.get_IMS(DEFAULT_VNID)
+        if sim_card == P0135Ax:
+            vnid2 = anritsu_handle.get_IMS(2)
+            vnid3 = anritsu_handle.get_IMS(3)
+            _init_IMS(
+                anritsu_handle,
+                vnid1,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR,
+                auth=True)
+            _init_IMS(
+                anritsu_handle,
+                vnid2,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_2,
+                ip_type="IPV6")
+            _init_IMS(
+                anritsu_handle,
+                vnid3,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_3,
+                ip_type="IPV6")
+        elif sim_card == VzW12349:
+            _init_IMS(anritsu_handle, vnid1, sim_card, auth=True)
+        else:
+            _init_IMS(anritsu_handle, vnid1, sim_card)
+    return [lte_bts, wcdma_bts]
+
+
+def set_system_model_lte_gsm(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for LTE and GSM simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Lte and Wcdma BTS objects
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.LTE, BtsTechnology.GSM)
+    # setting BTS parameters
+    lte_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    gsm_bts = anritsu_handle.get_BTS(BtsNumber.BTS2)
+    _init_lte_bts(lte_bts, user_params, CELL_1, sim_card)
+    _init_gsm_bts(gsm_bts, user_params, CELL_2, sim_card)
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        pdn4 = anritsu_handle.get_PDN(PDN_NO_4)
+        pdn5 = anritsu_handle.get_PDN(PDN_NO_5)
+        # Initialize PDN IP address.
+        _init_PDN(anritsu_handle, sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn2, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn3, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn4, Fi_UE_IPV4_ADDR_IMS,
+                  Fi_UE_IPV6_ADDR_IMS,
+                  True)
+        _init_PDN(anritsu_handle, sim_card, pdn5, Fi_UE_IPV4_ADDR_911,
+                  Fi_UE_IPV6_ADDR_911,
+                  True)
+        vnid1 = anritsu_handle.get_IMS(1)
+        vnid2 = anritsu_handle.get_IMS(2)
+        _init_IMS(
+            anritsu_handle,
+            vnid1,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_IMS,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_IMS,
+            auth=True)
+        _init_IMS(
+            anritsu_handle,
+            vnid2,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_911,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_911,
+            auth=False)
+        return [lte_bts, gsm_bts]
+    else:
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        # Initialize PDN IP address for internet connection sharing
+        _init_PDN(anritsu_handle, sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, True)
+        _init_PDN(anritsu_handle, sim_card, pdn2, UE_IPV4_ADDR_2, UE_IPV6_ADDR_2, False)
+        _init_PDN(anritsu_handle, sim_card, pdn3, UE_IPV4_ADDR_3, UE_IPV6_ADDR_3, True)
+        vnid1 = anritsu_handle.get_IMS(DEFAULT_VNID)
+        if sim_card == P0135Ax:
+            vnid2 = anritsu_handle.get_IMS(2)
+            vnid3 = anritsu_handle.get_IMS(3)
+            _init_IMS(
+                anritsu_handle,
+                vnid1,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR,
+                auth=True)
+            _init_IMS(
+                anritsu_handle,
+                vnid2,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_2,
+                ip_type="IPV6")
+            _init_IMS(
+                anritsu_handle,
+                vnid3,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_3,
+                ip_type="IPV6")
+        elif sim_card == VzW12349:
+            _init_IMS(anritsu_handle, vnid1, sim_card, auth=True)
+        else:
+            _init_IMS(anritsu_handle, vnid1, sim_card)
+    return [lte_bts, gsm_bts]
+
+
+def set_system_model_lte_1x(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for LTE and 1x simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Lte and 1x BTS objects
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.LTE,
+                                        BtsTechnology.CDMA1X)
+    # setting BTS parameters
+    lte_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    cdma1x_bts = anritsu_handle.get_BTS(BtsNumber.BTS2)
+    _init_lte_bts(lte_bts, user_params, CELL_1, sim_card)
+    _init_1x_bts(cdma1x_bts, user_params, CELL_2, sim_card)
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        pdn4 = anritsu_handle.get_PDN(PDN_NO_4)
+        pdn5 = anritsu_handle.get_PDN(PDN_NO_5)
+        # Initialize PDN IP address.
+        _init_PDN(anritsu_handle, sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn2, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn3, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn4, Fi_UE_IPV4_ADDR_IMS,
+                  Fi_UE_IPV6_ADDR_IMS,
+                  True)
+        _init_PDN(anritsu_handle, sim_card, pdn5, Fi_UE_IPV4_ADDR_911,
+                  Fi_UE_IPV6_ADDR_911,
+                  True)
+        vnid1 = anritsu_handle.get_IMS(1)
+        vnid2 = anritsu_handle.get_IMS(2)
+        _init_IMS(
+            anritsu_handle,
+            vnid1,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_IMS,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_IMS,
+            auth=True)
+        _init_IMS(
+            anritsu_handle,
+            vnid2,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_911,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_911,
+            auth=False)
+        return [lte_bts, cdma1x_bts]
+    else:
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        # Initialize PDN IP address for internet connection sharing
+        _init_PDN(anritsu_handle, sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, True)
+        _init_PDN(anritsu_handle, sim_card, pdn2, UE_IPV4_ADDR_2, UE_IPV6_ADDR_2, False)
+        _init_PDN(anritsu_handle, sim_card, pdn3, UE_IPV4_ADDR_3, UE_IPV6_ADDR_3, True)
+        vnid1 = anritsu_handle.get_IMS(DEFAULT_VNID)
+        if sim_card == P0135Ax:
+            vnid2 = anritsu_handle.get_IMS(2)
+            vnid3 = anritsu_handle.get_IMS(3)
+            _init_IMS(
+                anritsu_handle,
+                vnid1,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR,
+                auth=True)
+            _init_IMS(
+                anritsu_handle,
+                vnid2,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_2,
+                ip_type="IPV6")
+            _init_IMS(
+                anritsu_handle,
+                vnid3,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_3,
+                ip_type="IPV6")
+        elif sim_card == VzW12349:
+            _init_IMS(anritsu_handle, vnid1, sim_card, auth=True)
+        else:
+            _init_IMS(anritsu_handle, vnid1, sim_card)
+    return [lte_bts, cdma1x_bts]
+
+
+def set_system_model_lte_evdo(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for LTE and EVDO simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Lte and 1x BTS objects
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.LTE, BtsTechnology.EVDO)
+    # setting BTS parameters
+    lte_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    evdo_bts = anritsu_handle.get_BTS(BtsNumber.BTS2)
+    _init_lte_bts(lte_bts, user_params, CELL_1, sim_card)
+    _init_evdo_bts(evdo_bts, user_params, CELL_2, sim_card)
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        pdn4 = anritsu_handle.get_PDN(PDN_NO_4)
+        pdn5 = anritsu_handle.get_PDN(PDN_NO_5)
+        # Initialize PDN IP address.
+        _init_PDN(anritsu_handle, sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn2, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn3, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn4, Fi_UE_IPV4_ADDR_IMS,
+                  Fi_UE_IPV6_ADDR_IMS,
+                  True)
+        _init_PDN(anritsu_handle, sim_card, pdn5, Fi_UE_IPV4_ADDR_911,
+                  Fi_UE_IPV6_ADDR_911,
+                  True)
+        vnid1 = anritsu_handle.get_IMS(1)
+        vnid2 = anritsu_handle.get_IMS(2)
+        _init_IMS(
+            anritsu_handle,
+            vnid1,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_IMS,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_IMS,
+            auth=True)
+        _init_IMS(
+            anritsu_handle,
+            vnid2,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_911,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_911,
+            auth=False)
+        return [lte_bts, evdo_bts]
+    else:
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        # Initialize PDN IP address for internet connection sharing
+        _init_PDN(anritsu_handle, sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, True)
+        _init_PDN(anritsu_handle, sim_card, pdn2, UE_IPV4_ADDR_2, UE_IPV6_ADDR_2, False)
+        _init_PDN(anritsu_handle, sim_card, pdn3, UE_IPV4_ADDR_3, UE_IPV6_ADDR_3, True)
+        vnid1 = anritsu_handle.get_IMS(DEFAULT_VNID)
+        if sim_card == P0135Ax:
+            vnid2 = anritsu_handle.get_IMS(2)
+            vnid3 = anritsu_handle.get_IMS(3)
+            _init_IMS(
+                anritsu_handle,
+                vnid1,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR,
+                auth=True)
+            _init_IMS(
+                anritsu_handle,
+                vnid2,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_2,
+                ip_type="IPV6")
+            _init_IMS(
+                anritsu_handle,
+                vnid3,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_3,
+                ip_type="IPV6")
+        elif sim_card == VzW12349:
+            _init_IMS(anritsu_handle, vnid1, sim_card, auth=True)
+        else:
+            _init_IMS(anritsu_handle, vnid1, sim_card)
+    return [lte_bts, evdo_bts]
+
+
+def set_system_model_wcdma_gsm(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for WCDMA and GSM simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Wcdma and Gsm BTS objects
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.WCDMA, BtsTechnology.GSM)
+    # setting BTS parameters
+    wcdma_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    gsm_bts = anritsu_handle.get_BTS(BtsNumber.BTS2)
+    _init_wcdma_bts(wcdma_bts, user_params, CELL_1, sim_card)
+    _init_gsm_bts(gsm_bts, user_params, CELL_2, sim_card)
+    pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+    # Initialize PDN IP address for internet connection sharing
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        _init_PDN(anritsu_handle, sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+    else:
+        _init_PDN(anritsu_handle, sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, False)
+    return [wcdma_bts, gsm_bts]
+
+
+def set_system_model_gsm_gsm(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for GSM and GSM simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Wcdma and Gsm BTS objects
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.GSM, BtsTechnology.GSM)
+    # setting BTS parameters
+    gsm1_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    gsm2_bts = anritsu_handle.get_BTS(BtsNumber.BTS2)
+    _init_gsm_bts(gsm1_bts, user_params, CELL_1, sim_card)
+    _init_gsm_bts(gsm2_bts, user_params, CELL_2, sim_card)
+    pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+    # Initialize PDN IP address for internet connection sharing
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        _init_PDN(anritsu_handle,sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+    else:
+        _init_PDN(anritsu_handle,sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, False)
+    return [gsm1_bts, gsm2_bts]
+
+
+def set_system_model_lte(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for LTE simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Lte BTS object
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.LTE)
+    # setting Fi BTS parameters
+    lte_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    _init_lte_bts(lte_bts, user_params, CELL_1, sim_card)
+
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        pdn4 = anritsu_handle.get_PDN(PDN_NO_4)
+        pdn5 = anritsu_handle.get_PDN(PDN_NO_5)
+    # Initialize PDN IP address.
+        _init_PDN(anritsu_handle,sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle,sim_card, pdn2, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle,sim_card, pdn3, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+        _init_PDN(anritsu_handle, sim_card, pdn4, Fi_UE_IPV4_ADDR_IMS,
+                  Fi_UE_IPV6_ADDR_IMS,
+                  True)
+        _init_PDN(anritsu_handle, sim_card, pdn5, Fi_UE_IPV4_ADDR_911,
+                  Fi_UE_IPV6_ADDR_911,
+                  True)
+        vnid1 = anritsu_handle.get_IMS(1)
+        vnid2 = anritsu_handle.get_IMS(2)
+        _init_IMS(
+            anritsu_handle,
+            vnid1,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_IMS,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_IMS,
+            auth=True)
+        _init_IMS(
+            anritsu_handle,
+            vnid2,
+            sim_card,
+            ipv4_address=Fi_CSCF_IPV4_ADDR_911,
+            ipv6_address=Fi_CSCF_IPV6_ADDR_911,
+            auth=False)
+        return [lte_bts]
+    else:
+        # setting BTS parameters
+        pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+        pdn2 = anritsu_handle.get_PDN(PDN_NO_2)
+        pdn3 = anritsu_handle.get_PDN(PDN_NO_3)
+        # Initialize PDN IP address for internet connection sharing
+        _init_PDN(anritsu_handle,sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, True)
+        _init_PDN(anritsu_handle,sim_card, pdn2, UE_IPV4_ADDR_2, UE_IPV6_ADDR_2, False)
+        _init_PDN(anritsu_handle,sim_card, pdn3, UE_IPV4_ADDR_3, UE_IPV6_ADDR_3, True)
+        vnid1 = anritsu_handle.get_IMS(DEFAULT_VNID)
+        if sim_card == P0135Ax:
+            vnid2 = anritsu_handle.get_IMS(2)
+            vnid3 = anritsu_handle.get_IMS(3)
+            _init_IMS(
+                anritsu_handle,
+                vnid1,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR,
+                auth=True)
+            _init_IMS(
+                anritsu_handle,
+                vnid2,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_2,
+                ip_type="IPV6")
+            _init_IMS(
+                anritsu_handle,
+                vnid3,
+                sim_card,
+                ipv6_address=CSCF_IPV6_ADDR_3,
+                ip_type="IPV6")
+        elif sim_card == VzW12349:
+            _init_IMS(anritsu_handle, vnid1, sim_card, auth=True)
+        else:
+            _init_IMS(anritsu_handle, vnid1, sim_card)
+        return [lte_bts]
+
+
+def set_system_model_wcdma(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for WCDMA simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Wcdma BTS object
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.WCDMA)
+    # setting BTS parameters
+    wcdma_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    _init_wcdma_bts(wcdma_bts, user_params, CELL_1, sim_card)
+    pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+    # Initialize PDN IP address for internet connection sharing
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        _init_PDN(anritsu_handle,sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+    else:
+        _init_PDN(anritsu_handle,sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, False)
+    return [wcdma_bts]
+
+
+def set_system_model_gsm(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for GSM simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Gsm BTS object
+    """
+    anritsu_handle.set_simulation_model(BtsTechnology.GSM)
+    # setting BTS parameters
+    gsm_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    _init_gsm_bts(gsm_bts, user_params, CELL_1, sim_card)
+    pdn1 = anritsu_handle.get_PDN(PDN_NO_1)
+    # Initialize PDN IP address for internet connection sharing
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        _init_PDN(anritsu_handle,sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+    else:
+        _init_PDN(anritsu_handle,sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, False)
+    return [gsm_bts]
+
+
+def set_system_model_1x(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for CDMA 1X simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Cdma 1x BTS object
+    """
+    PDN_ONE = 1
+    anritsu_handle.set_simulation_model(BtsTechnology.CDMA1X)
+    # setting BTS parameters
+    cdma1x_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    _init_1x_bts(cdma1x_bts, user_params, CELL_1, sim_card)
+    pdn1 = anritsu_handle.get_PDN(PDN_ONE)
+    # Initialize PDN IP address for internet connection sharing
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        _init_PDN(anritsu_handle,sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+    else:
+        _init_PDN(anritsu_handle,sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, False)
+    return [cdma1x_bts]
+
+
+def set_system_model_1x_evdo(anritsu_handle, user_params, sim_card):
+    """ Configures Anritsu system for CDMA 1X simulation
+
+    Args:
+        anritsu_handle: anritusu device object.
+        user_params: pointer to user supplied parameters
+
+    Returns:
+        Cdma 1x BTS object
+    """
+    PDN_ONE = 1
+    anritsu_handle.set_simulation_model(BtsTechnology.CDMA1X,
+                                        BtsTechnology.EVDO)
+    # setting BTS parameters
+    cdma1x_bts = anritsu_handle.get_BTS(BtsNumber.BTS1)
+    evdo_bts = anritsu_handle.get_BTS(BtsNumber.BTS2)
+    _init_1x_bts(cdma1x_bts, user_params, CELL_1, sim_card)
+    _init_evdo_bts(evdo_bts, user_params, CELL_2, sim_card)
+    pdn1 = anritsu_handle.get_PDN(PDN_ONE)
+    # Initialize PDN IP address for internet connection sharing
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        _init_PDN(anritsu_handle,sim_card, pdn1, Fi_UE_IPV4_ADDR_Data,
+                  Fi_UE_IPV6_ADDR_Data, False)
+    else:
+        _init_PDN(anritsu_handle,sim_card, pdn1, UE_IPV4_ADDR_1, UE_IPV6_ADDR_1, False)
+    return [cdma1x_bts]
+
+
+def wait_for_bts_state(log, btsnumber, state, timeout=30):
+    """ Waits for BTS to be in the specified state ("IN" or "OUT")
+
+    Args:
+        btsnumber: BTS number.
+        state: expected state
+
+    Returns:
+        True for success False for failure
+    """
+    #  state value are "IN" and "OUT"
+    status = False
+    sleep_interval = 1
+    wait_time = timeout
+
+    if state == "IN":
+        service_state = BtsServiceState.SERVICE_STATE_IN
+    elif state == "OUT":
+        service_state = BtsServiceState.SERVICE_STATE_OUT
+    else:
+        log.info("wrong state value")
+        return status
+
+    if btsnumber.service_state is service_state:
+        log.info("BTS state is already in {}".format(state))
+        return True
+
+    # set to desired service state
+    btsnumber.service_state = service_state
+
+    while wait_time > 0:
+        if service_state == btsnumber.service_state:
+            status = True
+            break
+        time.sleep(sleep_interval)
+        wait_time = wait_time - sleep_interval
+
+    if not status:
+        log.info("Timeout: Expected BTS state is not received.")
+    return status
+
+
+class _CallSequenceException(Exception):
+    pass
+
+
+def call_mo_setup_teardown(
+        log,
+        ad,
+        anritsu_handle,
+        callee_number,
+        teardown_side=CALL_TEARDOWN_PHONE,
+        is_emergency=False,
+        wait_time_in_call=WAIT_TIME_IN_CALL_LONG,
+        is_ims_call=False,
+        ims_virtual_network_id=DEFAULT_IMS_VIRTUAL_NETWORK_ID):
+    """ Makes a MO call and tear down the call
+
+    Args:
+        ad: Android device object.
+        anritsu_handle: Anritsu object.
+        callee_number: Number to be called.
+        teardown_side: the side to end the call (Phone or remote).
+        is_emergency: is the call an emergency call.
+        wait_time_in_call: Time to wait when phone in call.
+        is_ims_call: is the call expected to be ims call.
+        ims_virtual_network_id: ims virtual network id.
+
+    Returns:
+        True for success False for failure
+    """
+
+    log.info("Making Call to " + callee_number)
+    virtual_phone_handle = anritsu_handle.get_VirtualPhone()
+
+    try:
+        # for an IMS call we either check CSCF or *nothing* (no virtual phone).
+        if is_ims_call:
+            # we only need pre-call registration in a non-emergency case
+            if not is_emergency:
+                if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                                ims_virtual_network_id,
+                                                ImsCscfStatus.SIPIDLE.value):
+                    raise _CallSequenceException(
+                        "Phone IMS status is not idle.")
+        else:
+            if not wait_for_virtualphone_state(log, virtual_phone_handle,
+                                               VirtualPhoneStatus.STATUS_IDLE):
+                raise _CallSequenceException("Virtual Phone not idle.")
+
+        if not initiate_call(log, ad, callee_number, is_emergency):
+            raise _CallSequenceException("Initiate call failed.")
+
+        if is_ims_call:
+            if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                            ims_virtual_network_id,
+                                            ImsCscfStatus.CALLING.value):
+                raise _CallSequenceException(
+                    "Phone IMS status is not calling.")
+            if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                            ims_virtual_network_id,
+                                            ImsCscfStatus.CONNECTED.value):
+                raise _CallSequenceException(
+                    "Phone IMS status is not connected.")
+        else:
+            # check Virtual phone answered the call
+            if not wait_for_virtualphone_state(
+                    log, virtual_phone_handle,
+                    VirtualPhoneStatus.STATUS_VOICECALL_INPROGRESS):
+                raise _CallSequenceException("Virtual Phone not in call.")
+
+        time.sleep(wait_time_in_call)
+
+        if not ad.droid.telecomIsInCall():
+            raise _CallSequenceException("Call ended before delay_in_call.")
+
+        if teardown_side is CALL_TEARDOWN_REMOTE:
+            log.info("Disconnecting the call from Remote")
+            if is_ims_call:
+                anritsu_handle.ims_cscf_call_action(ims_virtual_network_id,
+                                                    ImsCscfCall.END.value)
+            else:
+                virtual_phone_handle.set_voice_on_hook()
+            if not wait_for_droid_not_in_call(log, ad,
+                                              MAX_WAIT_TIME_CALL_DROP):
+                raise _CallSequenceException("DUT call not drop.")
+        else:
+            log.info("Disconnecting the call from DUT")
+            if not hangup_call(log, ad, is_emergency):
+                raise _CallSequenceException(
+                    "Error in Hanging-Up Call on DUT.")
+
+        if is_ims_call:
+            if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                            ims_virtual_network_id,
+                                            ImsCscfStatus.SIPIDLE.value):
+                raise _CallSequenceException("Phone IMS status is not idle.")
+        else:
+            if not wait_for_virtualphone_state(log, virtual_phone_handle,
+                                               VirtualPhoneStatus.STATUS_IDLE):
+                raise _CallSequenceException(
+                    "Virtual Phone not idle after hangup.")
+        return True
+
+    except _CallSequenceException as e:
+        log.error(e)
+        return False
+    finally:
+        try:
+            if ad.droid.telecomIsInCall():
+                ad.droid.telecomEndCall()
+        except Exception as e:
+            log.error(str(e))
+
+
+def handover_tc(log,
+                anritsu_handle,
+                wait_time=0,
+                s_bts=BtsNumber.BTS1,
+                t_bts=BtsNumber.BTS2,
+                timeout=60):
+    """ Setup and perform a handover test case in MD8475A
+
+    Args:
+        anritsu_handle: Anritsu object.
+        s_bts: Serving (originating) BTS
+        t_bts: Target (destination) BTS
+        wait_time: time to wait before handover
+
+    Returns:
+        True for success False for failure
+    """
+    log.info("Starting HO test case procedure")
+    log.info("Serving BTS = {}, Target BTS = {}".format(s_bts, t_bts))
+    time.sleep(wait_time)
+    ho_tc = anritsu_handle.get_AnritsuTestCases()
+    ho_tc.procedure = TestProcedure.PROCEDURE_HO
+    ho_tc.bts_direction = (s_bts, t_bts)
+    ho_tc.power_control = TestPowerControl.POWER_CONTROL_DISABLE
+    ho_tc.measurement_LTE = TestMeasurement.MEASUREMENT_DISABLE
+    anritsu_handle.start_testcase()
+    status = anritsu_handle.get_testcase_status()
+    timer = 0
+    while status == "0":
+        time.sleep(1)
+        status = anritsu_handle.get_testcase_status()
+        timer += 1
+        if timer > timeout:
+            return "Handover Test Case time out in {} sec!".format(timeout)
+    return status
+
+
+def make_ims_call(log,
+                  ad,
+                  anritsu_handle,
+                  callee_number,
+                  is_emergency=False,
+                  check_ims_reg=True,
+                  check_ims_calling=True,
+                  mo=True,
+                  ims_virtual_network_id=DEFAULT_IMS_VIRTUAL_NETWORK_ID):
+    """ Makes a MO call after IMS registred
+
+    Args:
+        ad: Android device object.
+        anritsu_handle: Anritsu object.
+        callee_number: Number to be called.
+        check_ims_reg: check if Anritsu cscf server state is "SIPIDLE".
+        check_ims_calling: check if Anritsu cscf server state is "CALLING".
+        mo: Mobile originated call
+        ims_virtual_network_id: ims virtual network id.
+
+    Returns:
+        True for success False for failure
+    """
+
+    try:
+        # confirm ims registration
+        if check_ims_reg:
+            if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                            ims_virtual_network_id,
+                                            ImsCscfStatus.SIPIDLE.value):
+                raise _CallSequenceException("IMS/CSCF status is not idle.")
+        if mo:  # make MO call
+            log.info("Making Call to " + callee_number)
+            if not initiate_call(log, ad, callee_number, is_emergency):
+                raise _CallSequenceException("Initiate call failed.")
+            if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                            ims_virtual_network_id,
+                                            ImsCscfStatus.CALLING.value):
+                raise _CallSequenceException(
+                    "Phone IMS status is not calling.")
+        else:  # make MT call
+            log.info("Making IMS Call to UE from MD8475A...")
+            anritsu_handle.ims_cscf_call_action(ims_virtual_network_id,
+                                                ImsCscfCall.MAKE.value)
+            if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                            ims_virtual_network_id,
+                                            ImsCscfStatus.RINGING.value):
+                raise _CallSequenceException(
+                    "Phone IMS status is not ringing.")
+            # answer the call on the UE
+            if not wait_and_answer_call(log, ad):
+                raise _CallSequenceException("UE Answer call Fail")
+
+        if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                        ims_virtual_network_id,
+                                        ImsCscfStatus.CONNECTED.value):
+            raise _CallSequenceException(
+                "MD8475A IMS status is not connected.")
+        return True
+
+    except _CallSequenceException as e:
+        log.error(e)
+        return False
+
+
+def tear_down_call(log,
+                   ad,
+                   anritsu_handle,
+                   ims_virtual_network_id=DEFAULT_IMS_VIRTUAL_NETWORK_ID):
+    """ Check and End a VoLTE call
+
+    Args:
+        ad: Android device object.
+        anritsu_handle: Anritsu object.
+        ims_virtual_network_id: ims virtual network id.
+
+    Returns:
+        True for success False for failure
+    """
+    try:
+        # end the call from phone
+        log.info("Disconnecting the call from DUT")
+        if not hangup_call(log, ad):
+            raise _CallSequenceException("Error in Hanging-Up Call on DUT.")
+        # confirm if CSCF status is back to idle
+        if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                        ims_virtual_network_id,
+                                        ImsCscfStatus.SIPIDLE.value):
+            raise _CallSequenceException("IMS/CSCF status is not idle.")
+        return True
+
+    except _CallSequenceException as e:
+        log.error(e)
+        return False
+    finally:
+        try:
+            if ad.droid.telecomIsInCall():
+                ad.droid.telecomEndCall()
+        except Exception as e:
+            log.error(str(e))
+
+
+# This procedure is for VoLTE mobility test cases
+def ims_call_ho(log,
+                ad,
+                anritsu_handle,
+                callee_number,
+                is_emergency=False,
+                check_ims_reg=True,
+                check_ims_calling=True,
+                mo=True,
+                wait_time_in_volte=WAIT_TIME_IN_CALL_FOR_IMS,
+                ims_virtual_network_id=DEFAULT_IMS_VIRTUAL_NETWORK_ID):
+    """ Makes a MO call after IMS registred, then handover
+
+    Args:
+        ad: Android device object.
+        anritsu_handle: Anritsu object.
+        callee_number: Number to be called.
+        check_ims_reg: check if Anritsu cscf server state is "SIPIDLE".
+        check_ims_calling: check if Anritsu cscf server state is "CALLING".
+        mo: Mobile originated call
+        wait_time_in_volte: Time for phone in VoLTE call, not used for SRLTE
+        ims_virtual_network_id: ims virtual network id.
+
+    Returns:
+        True for success False for failure
+    """
+
+    try:
+        # confirm ims registration
+        if check_ims_reg:
+            if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                            ims_virtual_network_id,
+                                            ImsCscfStatus.SIPIDLE.value):
+                raise _CallSequenceException("IMS/CSCF status is not idle.")
+        if mo:  # make MO call
+            log.info("Making Call to " + callee_number)
+            if not initiate_call(log, ad, callee_number, is_emergency):
+                raise _CallSequenceException("Initiate call failed.")
+            if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                            ims_virtual_network_id,
+                                            ImsCscfStatus.CALLING.value):
+                raise _CallSequenceException(
+                    "Phone IMS status is not calling.")
+        else:  # make MT call
+            log.info("Making IMS Call to UE from MD8475A...")
+            anritsu_handle.ims_cscf_call_action(ims_virtual_network_id,
+                                                ImsCscfCall.MAKE.value)
+            if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                            ims_virtual_network_id,
+                                            ImsCscfStatus.RINGING.value):
+                raise _CallSequenceException(
+                    "Phone IMS status is not ringing.")
+            # answer the call on the UE
+            if not wait_and_answer_call(log, ad):
+                raise _CallSequenceException("UE Answer call Fail")
+
+        if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                        ims_virtual_network_id,
+                                        ImsCscfStatus.CONNECTED.value):
+            raise _CallSequenceException("Phone IMS status is not connected.")
+        log.info(
+            "Wait for {} seconds before handover".format(wait_time_in_volte))
+        time.sleep(wait_time_in_volte)
+
+        # Once VoLTE call is connected, then Handover
+        log.info("Starting handover procedure...")
+        result = handover_tc(anritsu_handle, BtsNumber.BTS1, BtsNumber.BTS2)
+        log.info("Handover procedure ends with result code {}".format(result))
+        log.info(
+            "Wait for {} seconds after handover".format(wait_time_in_volte))
+        time.sleep(wait_time_in_volte)
+
+        # check if the phone stay in call
+        if not ad.droid.telecomIsInCall():
+            raise _CallSequenceException("Call ended before delay_in_call.")
+        # end the call from phone
+        log.info("Disconnecting the call from DUT")
+        if not hangup_call(log, ad, is_emergency):
+            raise _CallSequenceException("Error in Hanging-Up Call on DUT.")
+        # confirm if CSCF status is back to idle
+        if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                        ims_virtual_network_id,
+                                        ImsCscfStatus.SIPIDLE.value):
+            raise _CallSequenceException("IMS/CSCF status is not idle.")
+
+        return True
+
+    except _CallSequenceException as e:
+        log.error(e)
+        return False
+    finally:
+        try:
+            if ad.droid.telecomIsInCall():
+                ad.droid.telecomEndCall()
+        except Exception as e:
+            log.error(str(e))
+
+
+# This procedure is for SRLTE CSFB and SRVCC test cases
+def ims_call_cs_teardown(
+        log,
+        ad,
+        anritsu_handle,
+        callee_number,
+        teardown_side=CALL_TEARDOWN_PHONE,
+        is_emergency=False,
+        check_ims_reg=True,
+        check_ims_calling=True,
+        srvcc=None,
+        mo=True,
+        wait_time_in_volte=WAIT_TIME_IN_CALL_FOR_IMS,
+        wait_time_in_cs=WAIT_TIME_IN_CALL,
+        wait_time_in_alert=WAIT_TIME_IN_ALERT,
+        ims_virtual_network_id=DEFAULT_IMS_VIRTUAL_NETWORK_ID):
+    """ Makes a MO call after IMS registred, transit to CS, tear down the call
+
+    Args:
+        ad: Android device object.
+        anritsu_handle: Anritsu object.
+        callee_number: Number to be called.
+        teardown_side: the side to end the call (Phone or remote).
+        is_emergency: to make emergency call on the phone.
+        check_ims_reg: check if Anritsu cscf server state is "SIPIDLE".
+        check_ims_calling: check if Anritsu cscf server state is "CALLING".
+        srvcc: is the test case a SRVCC call.
+        mo: Mobile originated call
+        wait_time_in_volte: Time for phone in VoLTE call, not used for SRLTE
+        wait_time_in_cs: Time for phone in CS call.
+        ims_virtual_network_id: ims virtual network id.
+
+    Returns:
+        True for success False for failure
+    """
+
+    virtual_phone_handle = anritsu_handle.get_VirtualPhone()
+
+    try:
+        # confirm ims registration
+        if check_ims_reg:
+            if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                            ims_virtual_network_id,
+                                            ImsCscfStatus.SIPIDLE.value):
+                raise _CallSequenceException("IMS/CSCF status is not idle.")
+        # confirm virtual phone in idle
+        if not wait_for_virtualphone_state(log, virtual_phone_handle,
+                                           VirtualPhoneStatus.STATUS_IDLE):
+            raise _CallSequenceException("Virtual Phone not idle.")
+        if mo:  # make MO call
+            log.info("Making Call to " + callee_number)
+            if not initiate_call(log, ad, callee_number, is_emergency):
+                raise _CallSequenceException("Initiate call failed.")
+        else:  # make MT call
+            log.info("Making IMS Call to UE from MD8475A...")
+            anritsu_handle.ims_cscf_call_action(ims_virtual_network_id,
+                                                ImsCscfCall.MAKE.value)
+        # if check ims calling is required
+        if check_ims_calling:
+            if mo:
+                if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                                ims_virtual_network_id,
+                                                ImsCscfStatus.CALLING.value):
+                    raise _CallSequenceException(
+                        "Phone IMS status is not calling.")
+            else:
+                if not wait_for_ims_cscf_status(log, anritsu_handle,
+                                                ims_virtual_network_id,
+                                                ImsCscfStatus.RINGING.value):
+                    raise _CallSequenceException(
+                        "Phone IMS status is not ringing.")
+
+            # if SRVCC, check if VoLTE call is connected, then Handover
+            if srvcc != None:
+                if srvcc == "InCall":
+                    if not wait_for_ims_cscf_status(
+                            log, anritsu_handle, ims_virtual_network_id,
+                            ImsCscfStatus.CONNECTED.value):
+                        raise _CallSequenceException(
+                            "Phone IMS status is not connected.")
+                    # stay in call for "wait_time_in_volte" seconds
+                    time.sleep(wait_time_in_volte)
+                elif srvcc == "Alert":
+                    # ring for WAIT_TIME_IN_ALERT seconds
+                    time.sleep(WAIT_TIME_IN_ALERT)
+                # SRVCC by handover test case procedure
+                srvcc_tc = anritsu_handle.get_AnritsuTestCases()
+                srvcc_tc.procedure = TestProcedure.PROCEDURE_HO
+                srvcc_tc.bts_direction = (BtsNumber.BTS1, BtsNumber.BTS2)
+                srvcc_tc.power_control = TestPowerControl.POWER_CONTROL_DISABLE
+                srvcc_tc.measurement_LTE = TestMeasurement.MEASUREMENT_DISABLE
+                anritsu_handle.start_testcase()
+                time.sleep(5)
+        if not mo:
+            # answer the call on the UE
+            if not wait_and_answer_call(log, ad):
+                raise _CallSequenceException("UE Answer call Fail")
+        # check if Virtual phone in the call
+        if not wait_for_virtualphone_state(
+                log, virtual_phone_handle,
+                VirtualPhoneStatus.STATUS_VOICECALL_INPROGRESS):
+            raise _CallSequenceException("Virtual Phone not in call.")
+        # stay in call for "wait_time_in_cs" seconds
+        time.sleep(wait_time_in_cs)
+        # check if the phone stay in call
+        if not ad.droid.telecomIsInCall():
+            raise _CallSequenceException("Call ended before delay_in_call.")
+        # end the call
+        if teardown_side is CALL_TEARDOWN_REMOTE:
+            log.info("Disconnecting the call from Remote")
+            virtual_phone_handle.set_voice_on_hook()
+            if not wait_for_droid_not_in_call(log, ad,
+                                              MAX_WAIT_TIME_CALL_DROP):
+                raise _CallSequenceException("DUT call not drop.")
+        else:
+            log.info("Disconnecting the call from DUT")
+            if not hangup_call(log, ad, is_emergency):
+                raise _CallSequenceException(
+                    "Error in Hanging-Up Call on DUT.")
+        # confirm if virtual phone status is back to idle
+        if not wait_for_virtualphone_state(log, virtual_phone_handle,
+                                           VirtualPhoneStatus.STATUS_IDLE):
+            raise _CallSequenceException(
+                "Virtual Phone not idle after hangup.")
+        return True
+
+    except _CallSequenceException as e:
+        log.error(e)
+        return False
+    finally:
+        try:
+            if ad.droid.telecomIsInCall():
+                ad.droid.telecomEndCall()
+        except Exception as e:
+            log.error(str(e))
+
+
+def call_mt_setup_teardown(log,
+                           ad,
+                           virtual_phone_handle,
+                           caller_number=None,
+                           teardown_side=CALL_TEARDOWN_PHONE,
+                           rat=""):
+    """ Makes a call from Anritsu Virtual phone to device and tear down the call
+
+    Args:
+        ad: Android device object.
+        virtual_phone_handle: Anritsu virtual phone handle
+        caller_number =  Caller number
+        teardown_side = specifiy the side to end the call (Phone or remote)
+
+    Returns:
+        True for success False for failure
+    """
+    log.info("Receive MT Call - Making a call to the phone from remote")
+    try:
+        if not wait_for_virtualphone_state(log, virtual_phone_handle,
+                                           VirtualPhoneStatus.STATUS_IDLE):
+            raise Exception("Virtual Phone is not in a state to start call")
+        if caller_number is not None:
+            if rat == RAT_1XRTT:
+                virtual_phone_handle.id_c2k = caller_number
+            else:
+                virtual_phone_handle.id = caller_number
+        virtual_phone_handle.set_voice_off_hook()
+
+        if not wait_and_answer_call(log, ad, caller_number):
+            raise Exception("Answer call Fail")
+
+        time.sleep(WAIT_TIME_IN_CALL)
+
+        if not ad.droid.telecomIsInCall():
+            raise Exception("Call ended before delay_in_call.")
+    except Exception:
+        return False
+
+    if ad.droid.telecomIsInCall():
+        if teardown_side is CALL_TEARDOWN_REMOTE:
+            log.info("Disconnecting the call from Remote")
+            virtual_phone_handle.set_voice_on_hook()
+        else:
+            log.info("Disconnecting the call from Phone")
+            ad.droid.telecomEndCall()
+
+    wait_for_virtualphone_state(log, virtual_phone_handle,
+                                VirtualPhoneStatus.STATUS_IDLE)
+    ensure_phone_idle(log, ad)
+
+    return True
+
+
+def wait_for_sms_deliver_success(log, ad, time_to_wait=60):
+    sms_deliver_event = EventSmsDeliverSuccess
+    sleep_interval = 2
+    status = False
+    event = None
+
+    try:
+        event = ad.ed.pop_event(sms_deliver_event, time_to_wait)
+        status = True
+    except Empty:
+        log.info("Timeout: Expected event is not received.")
+    return status
+
+
+def wait_for_sms_sent_success(log, ad, time_to_wait=60):
+    sms_sent_event = EventSmsSentSuccess
+    sleep_interval = 2
+    status = False
+    event = None
+
+    try:
+        event = ad.ed.pop_event(sms_sent_event, time_to_wait)
+        log.info(event)
+        status = True
+    except Empty:
+        log.info("Timeout: Expected event is not received.")
+    return status
+
+
+def wait_for_incoming_sms(log, ad, time_to_wait=60):
+    sms_received_event = EventSmsReceived
+    sleep_interval = 2
+    status = False
+    event = None
+
+    try:
+        event = ad.ed.pop_event(sms_received_event, time_to_wait)
+        log.info(event)
+        status = True
+    except Empty:
+        log.info("Timeout: Expected event is not received.")
+    return status, event
+
+
+def verify_anritsu_received_sms(log, vp_handle, receiver_number, message, rat):
+    if rat == RAT_1XRTT:
+        receive_sms = vp_handle.receiveSms_c2k()
+    else:
+        receive_sms = vp_handle.receiveSms()
+
+    if receive_sms == "NONE":
+        return False
+    split = receive_sms.split('&')
+    text = ""
+    if rat == RAT_1XRTT:
+        # TODO: b/26296388 There is some problem when retrieving message with é
+        # from Anritsu.
+        return True
+    for i in range(len(split)):
+        if split[i].startswith('Text='):
+            text = split[i][5:]
+            text = AnritsuUtils.gsm_decode(text)
+            break
+    # TODO: b/26296388 Verify Phone number
+    if text != message:
+        log.error("Wrong message received")
+        return False
+    return True
+
+
+def sms_mo_send(log, ad, vp_handle, receiver_number, message, rat=""):
+    try:
+        if not wait_for_virtualphone_state(log, vp_handle,
+                                           VirtualPhoneStatus.STATUS_IDLE):
+            raise Exception("Virtual Phone is not in a state to receive SMS")
+        log.info("Sending SMS to " + receiver_number)
+        ad.droid.smsSendTextMessage(receiver_number, message, False)
+        log.info("Waiting for SMS sent event")
+        test_status = wait_for_sms_sent_success(log, ad)
+        if not test_status:
+            raise Exception("Failed to send SMS")
+        if not verify_anritsu_received_sms(log, vp_handle, receiver_number,
+                                           message, rat):
+            raise Exception("Anritsu didn't receive message")
+    except Exception as e:
+        log.error("Exception :" + str(e))
+        return False
+    return True
+
+
+def sms_mt_receive_verify(log, ad, vp_handle, sender_number, message, rat=""):
+    ad.droid.smsStartTrackingIncomingMessage()
+    try:
+        if not wait_for_virtualphone_state(log, vp_handle,
+                                           VirtualPhoneStatus.STATUS_IDLE):
+            raise Exception("Virtual Phone is not in a state to receive SMS")
+        log.info("Waiting for Incoming SMS from " + sender_number)
+        if rat == RAT_1XRTT:
+            vp_handle.sendSms_c2k(sender_number, message)
+        else:
+            vp_handle.sendSms(sender_number, message)
+        test_status, event = wait_for_incoming_sms(log, ad)
+        if not test_status:
+            raise Exception("Failed to receive SMS")
+        log.info("Incoming SMS: Sender " + event['data']['Sender'])
+        log.info("Incoming SMS: Message " + event['data']['Text'])
+        if event['data']['Sender'] != sender_number:
+            raise Exception("Wrong sender Number")
+        if event['data']['Text'] != message:
+            raise Exception("Wrong message")
+    except Exception as e:
+        log.error("exception: " + str(e))
+        return False
+    finally:
+        ad.droid.smsStopTrackingIncomingMessage()
+    return True
+
+
+def wait_for_ims_cscf_status(log,
+                             anritsu_handle,
+                             virtual_network_id,
+                             status,
+                             timeout=MAX_WAIT_TIME_IMS_CSCF_STATE):
+    """ Wait for IMS CSCF to be in expected state.
+
+    Args:
+        log: log object
+        anritsu_handle: anritsu object
+        virtual_network_id: virtual network id to be monitored
+        status: expected status
+        timeout: wait time
+    """
+    sleep_interval = 1
+    wait_time = timeout
+    while wait_time > 0:
+        if status == anritsu_handle.get_ims_cscf_status(virtual_network_id):
+            return True
+        time.sleep(sleep_interval)
+        wait_time = wait_time - sleep_interval
+    return False
+
+
+def wait_for_virtualphone_state(log,
+                                vp_handle,
+                                state,
+                                timeout=MAX_WAIT_TIME_VIRTUAL_PHONE_STATE):
+    """ Waits for Anritsu Virtual phone to be in expected state
+
+    Args:
+        ad: Android device object.
+        vp_handle: Anritus virtual phone handle
+        state =  expected state
+
+    Returns:
+        True for success False for failure
+    """
+    status = False
+    sleep_interval = 1
+    wait_time = timeout
+    while wait_time > 0:
+        if vp_handle.status == state:
+            log.info(vp_handle.status)
+            status = True
+            break
+        time.sleep(sleep_interval)
+        wait_time = wait_time - sleep_interval
+
+    if not status:
+        log.info("Timeout: Expected state is not received.")
+    return status
+
+
+# There is a difference between CMAS/ETWS message formation in LTE/WCDMA and CDMA 1X
+# LTE and CDMA : 3GPP
+# CDMA 1X: 3GPP2
+# hence different functions
+def cmas_receive_verify_message_lte_wcdma(
+        log, ad, anritsu_handle, serial_number, message_id, warning_message):
+    """ Makes Anritsu to send a CMAS message and phone and verifies phone
+        receives the message on LTE/WCDMA
+
+    Args:
+        ad: Android device object.
+        anritsu_handle: Anritus device object
+        serial_number =  serial number of CMAS message
+        message_id =  CMAS message ID
+        warning_message =  CMAS warning message
+
+    Returns:
+        True for success False for failure
+    """
+    status = False
+    event = None
+    ad.droid.smsStartTrackingGsmEmergencyCBMessage()
+    anritsu_handle.send_cmas_lte_wcdma(
+        hex(serial_number), message_id, warning_message)
+    try:
+        log.info("Waiting for CMAS Message")
+        event = ad.ed.pop_event(EventCmasReceived, 60)
+        status = True
+        log.info(event)
+        if warning_message != event['data']['message']:
+            log.info("Wrong warning messgae received")
+            status = False
+        if message_id != hex(event['data']['serviceCategory']):
+            log.info("Wrong warning messgae received")
+            status = False
+    except Empty:
+        log.info("Timeout: Expected event is not received.")
+
+    ad.droid.smsStopTrackingGsmEmergencyCBMessage()
+    return status
+
+
+def cmas_receive_verify_message_cdma1x(
+        log,
+        ad,
+        anritsu_handle,
+        message_id,
+        service_category,
+        alert_text,
+        response_type=CMAS_C2K_RESPONSETYPE_SHELTER,
+        severity=CMAS_C2K_SEVERITY_EXTREME,
+        urgency=CMAS_C2K_URGENCY_IMMEDIATE,
+        certainty=CMAS_C2K_CERTIANTY_OBSERVED):
+    """ Makes Anritsu to send a CMAS message and phone and verifies phone
+        receives the message on CDMA 1X
+
+    Args:
+        ad: Android device object.
+        anritsu_handle: Anritus device object
+        serial_number =  serial number of CMAS message
+        message_id =  CMAS message ID
+        warning_message =  CMAS warning message
+
+    Returns:
+        True for success False for failure
+    """
+    status = False
+    event = None
+    ad.droid.smsStartTrackingCdmaEmergencyCBMessage()
+    anritsu_handle.send_cmas_etws_cdma1x(message_id, service_category,
+                                         alert_text, response_type, severity,
+                                         urgency, certainty)
+    try:
+        log.info("Waiting for CMAS Message")
+        event = ad.ed.pop_event(EventCmasReceived, 60)
+        status = True
+        log.info(event)
+        if alert_text != event['data']['message']:
+            log.info("Wrong alert messgae received")
+            status = False
+
+        if event['data']['cmasResponseType'].lower() != response_type.lower():
+            log.info("Wrong response type received")
+            status = False
+
+        if event['data']['cmasUrgency'].lower() != urgency.lower():
+            log.info("Wrong cmasUrgency received")
+            status = False
+
+        if event['data']['cmasSeverity'].lower() != severity.lower():
+            log.info("Wrong cmasSeverity received")
+            status = False
+    except Empty:
+        log.info("Timeout: Expected event is not received.")
+
+    ad.droid.smsStopTrackingCdmaEmergencyCBMessage()
+    return status
+
+
+def etws_receive_verify_message_lte_wcdma(
+        log, ad, anritsu_handle, serial_number, message_id, warning_message):
+    """ Makes Anritsu to send a ETWS message and phone and verifies phone
+        receives the message on LTE/WCDMA
+
+    Args:
+        ad: Android device object.
+        anritsu_handle: Anritus device object
+        serial_number =  serial number of ETWS message
+        message_id =  ETWS message ID
+        warning_message =  ETWS warning message
+
+    Returns:
+        True for success False for failure
+    """
+    status = False
+    event = None
+    if message_id == ETWS_WARNING_EARTHQUAKE:
+        warning_type = "Earthquake"
+    elif message_id == ETWS_WARNING_EARTHQUAKETSUNAMI:
+        warning_type = "EarthquakeandTsunami"
+    elif message_id == ETWS_WARNING_TSUNAMI:
+        warning_type = "Tsunami"
+    elif message_id == ETWS_WARNING_TEST_MESSAGE:
+        warning_type = "test"
+    elif message_id == ETWS_WARNING_OTHER_EMERGENCY:
+        warning_type = "other"
+    ad.droid.smsStartTrackingGsmEmergencyCBMessage()
+    anritsu_handle.send_etws_lte_wcdma(
+        hex(serial_number), message_id, warning_type, warning_message, "ON",
+        "ON")
+    try:
+        log.info("Waiting for ETWS Message")
+        event = ad.ed.pop_event(EventEtwsReceived, 60)
+        status = True
+        log.info(event)
+        # TODO: b/26296388 Event data verification
+    except Empty:
+        log.info("Timeout: Expected event is not received.")
+
+    ad.droid.smsStopTrackingGsmEmergencyCBMessage()
+    return status
+
+
+def etws_receive_verify_message_cdma1x(log, ad, anritsu_handle, serial_number,
+                                       message_id, warning_message):
+    """ Makes Anritsu to send a ETWS message and phone and verifies phone
+        receives the message on CDMA1X
+
+    Args:
+        ad: Android device object.
+        anritsu_handle: Anritus device object
+        serial_number =  serial number of ETWS message
+        message_id =  ETWS message ID
+        warning_message =  ETWS warning message
+
+    Returns:
+        True for success False for failure
+    """
+    status = False
+    event = None
+    # TODO: b/26296388 need to add logic to check etws.
+    return status
+
+
+def read_ue_identity(log, ad, anritsu_handle, identity_type):
+    """ Get the UE identity IMSI, IMEI, IMEISV
+
+    Args:
+        ad: Android device object.
+        anritsu_handle: Anritus device object
+        identity_type: Identity type(IMSI/IMEI/IMEISV)
+
+    Returns:
+        Requested Identity value
+    """
+    return anritsu_handle.get_ue_identity(identity_type)
+
+
+def get_transmission_mode(user_params, cell_no):
+    """ Returns the TRANSMODE to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        TM to be used
+    """
+    key = "cell{}_transmission_mode".format(cell_no)
+    transmission_mode = user_params.get(key, DEFAULT_T_MODE)
+    return transmission_mode
+
+
+def get_dl_antenna(user_params, cell_no):
+    """ Returns the DL ANTENNA to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        number of DL ANTENNAS to be used
+    """
+    key = "cell{}_dl_antenna".format(cell_no)
+    dl_antenna = user_params.get(key, DEFAULT_DL_ANTENNA)
+    return dl_antenna
+
+
+def get_lte_band(user_params, cell_no, sim_card):
+    """ Returns the LTE BAND to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        LTE BAND to be used
+    """
+    key = "cell{}_lte_band".format(cell_no)
+    if sim_card == FiTMO:
+        band = Fi_LTE_TMO_BAND[cell_no - 1]
+    elif sim_card == FiSPR:
+        band = Fi_LTE_SPR_BAND[cell_no - 1]
+    elif sim_card == FiUSCC:
+        band = Fi_LTE_USCC_BAND[cell_no - 1]
+    else:
+        band = DEFAULT_LTE_BAND[cell_no - 1]
+    return user_params.get(key, band)
+
+
+def get_wcdma_band(user_params, cell_no):
+    """ Returns the WCDMA BAND to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        WCDMA BAND to be used
+    """
+    key = "cell{}_wcdma_band".format(cell_no)
+    wcdma_band = user_params.get(key, DEFAULT_WCDMA_BAND)
+    return wcdma_band
+
+
+def get_gsm_band(user_params, cell_no, sim_card):
+    """ Returns the GSM BAND to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        GSM BAND to be used
+    """
+    key = "cell{}_gsm_band".format(cell_no)
+    if sim_card == FiTMO:
+        gsm_band = Fi_GSM_TMO_BAND
+    else:
+        gsm_band = user_params.get(key, DEFAULT_GSM_BAND)
+    return gsm_band
+
+
+def get_1x_band(user_params, cell_no, sim_card):
+    """ Returns the 1X BAND to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        1X BAND to be used
+    """
+    key = "cell{}_1x_band".format(cell_no)
+    if sim_card == FiSPR:
+        band = Fi_SPR1X_BAND
+    elif sim_card == FiUSCC:
+        band = Fi_USCC1X_BAND
+    elif sim_card == VzW12349:
+        band = VzW_CDMA1x_BAND
+    else:
+        band = DEFAULT_CDMA1X_BAND
+    return user_params.get(key, band)
+
+
+def get_evdo_band(user_params, cell_no, sim_card):
+    """ Returns the EVDO BAND to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        EVDO BAND to be used
+    """
+    key = "cell{}_evdo_band".format(cell_no)
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        band = Fi_EVDO_BAND
+    elif sim_card == VzW12349:
+        band = VzW_EVDO_BAND
+    else:
+         band = DEFAULT_EVDO_BAND
+    return user_params.get(key, band)
+
+
+def get_wcdma_rac(user_params, cell_no):
+    """ Returns the WCDMA RAC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        WCDMA RAC to be used
+    """
+    key = "cell{}_wcdma_rac".format(cell_no)
+    try:
+        wcdma_rac = user_params[key]
+    except KeyError:
+        wcdma_rac = DEFAULT_RAC
+    return wcdma_rac
+
+
+def get_gsm_rac(user_params, cell_no):
+    """ Returns the GSM RAC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        GSM RAC to be used
+    """
+    key = "cell{}_gsm_rac".format(cell_no)
+    try:
+        gsm_rac = user_params[key]
+    except KeyError:
+        gsm_rac = DEFAULT_RAC
+    return gsm_rac
+
+
+def get_wcdma_lac(user_params, cell_no):
+    """ Returns the WCDMA LAC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        WCDMA LAC to be used
+    """
+    key = "cell{}_wcdma_lac".format(cell_no)
+    try:
+        wcdma_lac = user_params[key]
+    except KeyError:
+        wcdma_lac = DEFAULT_LAC
+    return wcdma_lac
+
+
+def get_gsm_lac(user_params, cell_no):
+    """ Returns the GSM LAC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        GSM LAC to be used
+    """
+    key = "cell{}_gsm_lac".format(cell_no)
+    try:
+        gsm_lac = user_params[key]
+    except KeyError:
+        gsm_lac = DEFAULT_LAC
+    return gsm_lac
+
+
+def get_lte_mcc(user_params, cell_no, sim_card):
+    """ Returns the LTE MCC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        LTE MCC to be used
+    """
+
+    key = "cell{}_lte_mcc".format(cell_no)
+    if sim_card == FiTMO:
+        mcc = Fi_TMO_MCC
+    elif sim_card == FiSPR:
+        mcc = Fi_SPR_MCC
+    elif sim_card == FiUSCC:
+        mcc = Fi_USCC_MCC
+    elif sim_card == VzW12349:
+        mcc = VzW_MCC
+    else:
+        mcc = DEFAULT_MCC
+    return user_params.get(key, mcc)
+
+
+def get_lte_mnc(user_params, cell_no, sim_card):
+    """ Returns the LTE MNC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        LTE MNC to be used
+    """
+    key = "cell{}_lte_mnc".format(cell_no)
+    if sim_card == FiTMO:
+        mnc = Fi_TMO_MNC
+    elif sim_card == FiSPR:
+        mnc = Fi_SPR_MNC
+    elif sim_card == FiUSCC:
+        mnc = Fi_USCC_MNC
+    elif sim_card == VzW12349:
+        mnc = VzW_MNC
+    else:
+        mnc = DEFAULT_MNC
+    return user_params.get(key, mnc)
+
+
+def get_wcdma_mcc(user_params, cell_no, sim_card):
+    """ Returns the WCDMA MCC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        WCDMA MCC to be used
+    """
+    key = "cell{}_wcdma_mcc".format(cell_no)
+    mcc = VzW_MCC if sim_card == VzW12349 else DEFAULT_MCC
+    return user_params.get(key, mcc)
+
+
+def get_wcdma_mnc(user_params, cell_no, sim_card):
+    """ Returns the WCDMA MNC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        WCDMA MNC to be used
+    """
+    key = "cell{}_wcdma_mnc".format(cell_no)
+    mnc = VzW_MNC if sim_card == VzW12349 else DEFAULT_MNC
+    return user_params.get(key, mnc)
+
+
+def get_gsm_mcc(user_params, cell_no, sim_card):
+    """ Returns the GSM MCC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        GSM MCC to be used
+    """
+    key = "cell{}_gsm_mcc".format(cell_no)
+    mcc = VzW_MCC if sim_card == VzW12349 else DEFAULT_MCC
+    return user_params.get(key, mcc)
+
+
+def get_gsm_mnc(user_params, cell_no, sim_card):
+    """ Returns the GSM MNC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        GSM MNC to be used
+    """
+    key = "cell{}_gsm_mnc".format(cell_no)
+    mnc = VzW_MNC if sim_card == VzW12349 else DEFAULT_MNC
+    return user_params.get(key, mnc)
+
+
+def get_1x_mcc(user_params, cell_no, sim_card):
+    """ Returns the 1X MCC to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        1X MCC to be used
+    """
+    key = "cell{}_1x_mcc".format(cell_no)
+    if sim_card == FiSPR:
+        mcc = Fi_SPR1X_MCC
+    elif sim_card == FiUSCC:
+        mcc = Fi_USCC1X_MCC
+    elif sim_card == VzW12349:
+        mcc = VzW_MCC
+    else:
+        mcc = DEFAULT_MCC
+    return user_params.get(key, mcc)
+
+
+def get_1x_channel(user_params, cell_no, sim_card):
+    """ Returns the 1X Channel to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        1X Channel to be used
+    """
+    key = "cell{}_1x_channel".format(cell_no)
+    if sim_card == FiSPR:
+        ch = Fi_SPR1X_CH
+    elif sim_card == FiUSCC:
+        ch = Fi_USCC1X_CH
+    elif sim_card == VzW12349:
+        ch = VzW_CDMA1x_CH
+    else:
+        ch = DEFAULT_CDMA1X_CH
+    return user_params.get(key, ch)
+
+
+def get_1x_sid(user_params, cell_no, sim_card):
+    """ Returns the 1X SID to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        1X SID to be used
+    """
+    key = "cell{}_1x_sid".format(cell_no)
+    if sim_card == FiSPR:
+        sid = Fi_SPR1X_SID
+    elif sim_card == FiUSCC:
+        sid = Fi_USCC1X_SID
+    elif sim_card == VzW12349:
+        sid = VzW_CDMA1X_SID
+    else:
+        sid = DEFAULT_CDMA1X_SID
+    return user_params.get(key, sid)
+
+
+def get_1x_nid(user_params, cell_no, sim_card):
+    """ Returns the 1X NID to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        1X NID to be used
+    """
+    key = "cell{}_1x_nid".format(cell_no)
+    if sim_card == FiSPR:
+        nid = Fi_SPR1X_NID
+    elif sim_card == FiUSCC:
+        nid = Fi_USCC1X_NID
+    elif sim_card == VzW12349:
+        nid = VzW_CDMA1X_NID
+    else:
+        nid = DEFAULT_CDMA1X_NID
+    return user_params.get(key, nid)
+
+
+def get_evdo_channel(user_params, cell_no, sim_card):
+    """ Returns the EVDO Channel to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        EVDO Channel to be used
+    """
+    key = "cell{}_evdo_channel".format(cell_no)
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        ch = Fi_EVDO_CH
+    elif sim_card == VzW12349:
+        ch = VzW_EVDO_CH
+    else:
+        ch = DEFAULT_EVDO_CH
+    return user_params.get(key, ch)
+
+
+def get_evdo_sid(user_params, cell_no, sim_card):
+    """ Returns the EVDO SID to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        EVDO SID to be used
+    """
+    key = "cell{}_evdo_sid".format(cell_no)
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        sid = Fi_EVDO_SECTOR_ID
+    elif sim_card == VzW12349:
+        sid = VzW_EVDO_SECTOR_ID
+    else:
+        sid = DEFAULT_EVDO_SECTOR_ID
+    return user_params.get(key, sid)
+
+
+def get_csfb_type(user_params):
+    """ Returns the CSFB Type to be used from the user specified parameters
+        or default value
+
+    Args:
+        user_params: pointer to user supplied parameters
+        cell_no: specify the cell number this BTS is configured
+        Anritsu supports two cells. so cell_1 or cell_2
+
+    Returns:
+        CSFB Type to be used
+    """
+    try:
+        csfb_type = user_params["csfb_type"]
+    except KeyError:
+        csfb_type = CsfbType.CSFB_TYPE_REDIRECTION
+    return csfb_type
+
+
+def set_post_sim_params(anritsu_handle, user_params, sim_card):
+    if sim_card == FiTMO or sim_card == FiSPR or sim_card == FiUSCC:
+        anritsu_handle.send_command("PDNCHECKAPN 1,h2g2")
+        anritsu_handle.send_command("PDNCHECKAPN 2,n.nv.ispsn")
+        anritsu_handle.send_command("PDNCHECKAPN 3,fast.t-mobile.com")
+        anritsu_handle.send_command("PDNCHECKAPN 4,ims")
+        anritsu_handle.send_command("PDNCHECKAPN 5,*")
+        anritsu_handle.send_command("PDNIMS 1,DISABLE")
+        anritsu_handle.send_command("PDNIMS 2,DISABLE")
+        anritsu_handle.send_command("PDNIMS 3,DISABLE")
+        anritsu_handle.send_command("PDNIMS 4,ENABLE")
+        anritsu_handle.send_command("PDNVNID 4,1")
+        anritsu_handle.send_command("PDNIMS 5,ENABLE")
+        anritsu_handle.send_command("PDNVNID 5,2")
+    if sim_card == P0135Ax:
+        anritsu_handle.send_command("PDNCHECKAPN 1,ims")
+        anritsu_handle.send_command("PDNCHECKAPN 2,fast.t-mobile.com")
+        anritsu_handle.send_command("PDNIMS 1,ENABLE")
+        anritsu_handle.send_command("PDNVNID 1,1")
+        anritsu_handle.send_command("PDNIMS 2,ENABLE")
+        anritsu_handle.send_command("PDNIMS 3,ENABLE")
+        anritsu_handle.send_command("PDNVNID 3,1")
+    if sim_card == VzW12349:
+        anritsu_handle.send_command("PDNCHECKAPN 1,IMS")
+        anritsu_handle.send_command("PDNCHECKAPN 2,VZWINTERNET")
+        anritsu_handle.send_command("PDNIMS 1,ENABLE")
+        anritsu_handle.send_command("PDNVNID 1,1")
+        anritsu_handle.send_command("PDNIMS 3,ENABLE")
+        anritsu_handle.send_command("PDNVNID 3,1")
diff --git a/acts_tests/acts_contrib/test_utils/tel/loggers/__init__.py b/acts_tests/acts_contrib/test_utils/tel/loggers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/loggers/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/tel/loggers/protos/__init__.py b/acts_tests/acts_contrib/test_utils/tel/loggers/protos/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/loggers/protos/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_metric.proto b/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_metric.proto
new file mode 100644
index 0000000..7984c7c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_metric.proto
@@ -0,0 +1,41 @@
+/* Note: If making any changes to this file be sure to generate a new
+   compiled *_pb2.py file by running the following command from this
+   directory:
+   $ protoc -I=. --python_out=. telephony_metric.proto
+
+   Be sure that you are compiling with protoc 3.4.0
+
+   More info can be found at:
+   https://developers.google.com/protocol-buffers/docs/pythontutorial
+*/
+
+syntax = "proto2";
+
+package wireless.android.platform.testing.telephony.metrics;
+
+message TelephonyVoiceTestResult {
+
+  enum CallResult {
+    UNAVAILABLE_NETWORK_TYPE = -2;
+    CALL_SETUP_FAILURE = -1;
+    SUCCESS = 0;
+    INITIATE_FAILED = 1;
+    NO_RING_EVENT_OR_ANSWER_FAILED = 2;
+    NO_CALL_ID_FOUND = 3;
+    CALL_STATE_NOT_ACTIVE_DURING_ESTABLISHMENT = 4;
+    AUDIO_STATE_NOT_INCALL_DURING_ESTABLISHMENT = 5;
+    AUDIO_STATE_NOT_INCALL_AFTER_CONNECTED = 6;
+    CALL_DROP_OR_WRONG_STATE_DURING_ESTABLISHMENT = 7;
+    CALL_DROP_OR_WRONG_STATE_AFTER_CONNECTED = 8;
+    CALL_HANGUP_FAIL = 9;
+    CALL_ID_CLEANUP_FAIL = 10;
+}
+
+  optional CallResult result = 1;
+  optional float call_setup_time_latency = 2;
+}
+
+message TelephonyVoiceStressResult {
+  repeated TelephonyVoiceTestResult results = 1;
+}
+
diff --git a/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_metric_pb2.py b/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_metric_pb2.py
new file mode 100644
index 0000000..e191960
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/loggers/protos/telephony_metric_pb2.py
@@ -0,0 +1,201 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: telephony_metric.proto
+"""Generated protocol buffer code."""
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='telephony_metric.proto',
+  package='wireless.android.platform.testing.telephony.metrics',
+  syntax='proto2',
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x16telephony_metric.proto\x12\x33wireless.android.platform.testing.telephony.metrics\"\xf6\x04\n\x18TelephonyVoiceTestResult\x12h\n\x06result\x18\x01 \x01(\x0e\x32X.wireless.android.platform.testing.telephony.metrics.TelephonyVoiceTestResult.CallResult\x12\x1f\n\x17\x63\x61ll_setup_time_latency\x18\x02 \x01(\x02\"\xce\x03\n\nCallResult\x12%\n\x18UNAVAILABLE_NETWORK_TYPE\x10\xfe\xff\xff\xff\xff\xff\xff\xff\xff\x01\x12\x1f\n\x12\x43\x41LL_SETUP_FAILURE\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\x01\x12\x0b\n\x07SUCCESS\x10\x00\x12\x13\n\x0fINITIATE_FAILED\x10\x01\x12\"\n\x1eNO_RING_EVENT_OR_ANSWER_FAILED\x10\x02\x12\x14\n\x10NO_CALL_ID_FOUND\x10\x03\x12.\n*CALL_STATE_NOT_ACTIVE_DURING_ESTABLISHMENT\x10\x04\x12/\n+AUDIO_STATE_NOT_INCALL_DURING_ESTABLISHMENT\x10\x05\x12*\n&AUDIO_STATE_NOT_INCALL_AFTER_CONNECTED\x10\x06\x12\x31\n-CALL_DROP_OR_WRONG_STATE_DURING_ESTABLISHMENT\x10\x07\x12,\n(CALL_DROP_OR_WRONG_STATE_AFTER_CONNECTED\x10\x08\x12\x14\n\x10\x43\x41LL_HANGUP_FAIL\x10\t\x12\x18\n\x14\x43\x41LL_ID_CLEANUP_FAIL\x10\n\"|\n\x1aTelephonyVoiceStressResult\x12^\n\x07results\x18\x01 \x03(\x0b\x32M.wireless.android.platform.testing.telephony.metrics.TelephonyVoiceTestResult'
+)
+
+
+
+_TELEPHONYVOICETESTRESULT_CALLRESULT = _descriptor.EnumDescriptor(
+  name='CallResult',
+  full_name='wireless.android.platform.testing.telephony.metrics.TelephonyVoiceTestResult.CallResult',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNAVAILABLE_NETWORK_TYPE', index=0, number=-2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='CALL_SETUP_FAILURE', index=1, number=-1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='SUCCESS', index=2, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='INITIATE_FAILED', index=3, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NO_RING_EVENT_OR_ANSWER_FAILED', index=4, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NO_CALL_ID_FOUND', index=5, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='CALL_STATE_NOT_ACTIVE_DURING_ESTABLISHMENT', index=6, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='AUDIO_STATE_NOT_INCALL_DURING_ESTABLISHMENT', index=7, number=5,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='AUDIO_STATE_NOT_INCALL_AFTER_CONNECTED', index=8, number=6,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='CALL_DROP_OR_WRONG_STATE_DURING_ESTABLISHMENT', index=9, number=7,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='CALL_DROP_OR_WRONG_STATE_AFTER_CONNECTED', index=10, number=8,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='CALL_HANGUP_FAIL', index=11, number=9,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='CALL_ID_CLEANUP_FAIL', index=12, number=10,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=248,
+  serialized_end=710,
+)
+_sym_db.RegisterEnumDescriptor(_TELEPHONYVOICETESTRESULT_CALLRESULT)
+
+
+_TELEPHONYVOICETESTRESULT = _descriptor.Descriptor(
+  name='TelephonyVoiceTestResult',
+  full_name='wireless.android.platform.testing.telephony.metrics.TelephonyVoiceTestResult',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='result', full_name='wireless.android.platform.testing.telephony.metrics.TelephonyVoiceTestResult.result', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=-2,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='call_setup_time_latency', full_name='wireless.android.platform.testing.telephony.metrics.TelephonyVoiceTestResult.call_setup_time_latency', index=1,
+      number=2, type=2, cpp_type=6, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _TELEPHONYVOICETESTRESULT_CALLRESULT,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=80,
+  serialized_end=710,
+)
+
+
+_TELEPHONYVOICESTRESSRESULT = _descriptor.Descriptor(
+  name='TelephonyVoiceStressResult',
+  full_name='wireless.android.platform.testing.telephony.metrics.TelephonyVoiceStressResult',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='results', full_name='wireless.android.platform.testing.telephony.metrics.TelephonyVoiceStressResult.results', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=712,
+  serialized_end=836,
+)
+
+_TELEPHONYVOICETESTRESULT.fields_by_name['result'].enum_type = _TELEPHONYVOICETESTRESULT_CALLRESULT
+_TELEPHONYVOICETESTRESULT_CALLRESULT.containing_type = _TELEPHONYVOICETESTRESULT
+_TELEPHONYVOICESTRESSRESULT.fields_by_name['results'].message_type = _TELEPHONYVOICETESTRESULT
+DESCRIPTOR.message_types_by_name['TelephonyVoiceTestResult'] = _TELEPHONYVOICETESTRESULT
+DESCRIPTOR.message_types_by_name['TelephonyVoiceStressResult'] = _TELEPHONYVOICESTRESSRESULT
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+TelephonyVoiceTestResult = _reflection.GeneratedProtocolMessageType('TelephonyVoiceTestResult', (_message.Message,), {
+  'DESCRIPTOR' : _TELEPHONYVOICETESTRESULT,
+  '__module__' : 'telephony_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.telephony.metrics.TelephonyVoiceTestResult)
+  })
+_sym_db.RegisterMessage(TelephonyVoiceTestResult)
+
+TelephonyVoiceStressResult = _reflection.GeneratedProtocolMessageType('TelephonyVoiceStressResult', (_message.Message,), {
+  'DESCRIPTOR' : _TELEPHONYVOICESTRESSRESULT,
+  '__module__' : 'telephony_metric_pb2'
+  # @@protoc_insertion_point(class_scope:wireless.android.platform.testing.telephony.metrics.TelephonyVoiceStressResult)
+  })
+_sym_db.RegisterMessage(TelephonyVoiceStressResult)
+
+
+# @@protoc_insertion_point(module_scope)
diff --git a/acts_tests/acts_contrib/test_utils/tel/loggers/telephony_metric_logger.py b/acts_tests/acts_contrib/test_utils/tel/loggers/telephony_metric_logger.py
new file mode 100644
index 0000000..9869390
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/loggers/telephony_metric_logger.py
@@ -0,0 +1,49 @@
+# /usr/bin/env python3
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import base64
+import os
+import time
+
+from acts.metrics.core import ProtoMetric
+from acts.metrics.logger import MetricLogger
+from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
+
+# Initializes the path to the protobuf
+PROTO_PATH = os.path.join(os.path.dirname(__file__),
+                          'protos',
+                          'telephony_metric.proto')
+
+
+class TelephonyMetricLogger(MetricLogger):
+    """A logger for gathering Telephony test metrics
+
+    Attributes:
+        proto: Module used to store Telephony metrics in a proto
+    """
+
+    def __init__(self, event):
+        super().__init__(event=event)
+        self.proto = TelephonyVoiceTestResult()
+
+    def set_result(self, result_type):
+        self.proto.result = result_type
+
+    def end(self, event):
+        metric = ProtoMetric(name='telephony_voice_test_result',
+                             data=self.proto)
+        return self.publisher.publish(metric)
+
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
new file mode 100644
index 0000000..8e47a89
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_atten_utils.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+import time
+import math
+from acts_contrib.test_utils.tel.tel_defines import ATTEN_MAX_VALUE
+from acts_contrib.test_utils.tel.tel_defines import ATTEN_MIN_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
+
+
+def get_atten(log, atten_obj):
+    """Get attenuator current attenuation value.
+
+    Args:
+        log: log object.
+        atten_obj: attenuator object.
+    Returns:
+        Current attenuation value.
+    """
+    return atten_obj.get_atten()
+
+
+def set_atten(log, atten_obj, target_atten, step_size=0, time_per_step=0):
+    """Set attenuator attenuation value.
+
+    Args:
+        log: log object.
+        atten_obj: attenuator object.
+        target_atten: target attenuation value.
+        step_size: step size (in unit of dBm) for 'attenuation value setting'.
+            This is optional. Default value is 0. If step_size is 0, it means
+            the setting will be done in only one step.
+        time_per_step: delay time (in unit of second) per step when setting
+            the attenuation value.
+            This is optional. Default value is 0.
+    Returns:
+        True is no error happened. Otherwise false.
+    """
+    try:
+        print_name = atten_obj.path
+    except AttributeError:
+        print_name = str(atten_obj)
+
+    current_atten = get_atten(log, atten_obj)
+    info = "set_atten {} from {} to {}".format(print_name, current_atten,
+                                               target_atten)
+    if step_size > 0:
+        info += ", step size {}, time per step {}s.".format(step_size,
+                                                            time_per_step)
+    log.info(info)
+    try:
+        delta = target_atten - current_atten
+        if step_size > 0:
+            number_of_steps = int(abs(delta) / step_size)
+            while number_of_steps > 0:
+                number_of_steps -= 1
+                current_atten += math.copysign(step_size,
+                                               (target_atten - current_atten))
+                atten_obj.set_atten(current_atten)
+                time.sleep(time_per_step)
+        atten_obj.set_atten(target_atten)
+    except Exception as e:
+        log.error("set_atten error happened: {}".format(e))
+        return False
+    return True
+
+
+def set_rssi(log,
+             atten_obj,
+             calibration_rssi,
+             target_rssi,
+             step_size=0,
+             time_per_step=0):
+    """Set RSSI value by changing attenuation.
+
+    Args:
+        log: log object.
+        atten_obj: attenuator object.
+        calibration_rssi: RSSI calibration information.
+        target_rssi: target RSSI value.
+        step_size: step size (in unit of dBm) for 'RSSI value setting'.
+            This is optional. Default value is 0. If step_size is 0, it means
+            the setting will be done in only one step.
+        time_per_step: delay time (in unit of second) per step when setting
+            the attenuation value.
+            This is optional. Default value is 0.
+    Returns:
+        True is no error happened. Otherwise false.
+    """
+    try:
+        print_name = atten_obj.path
+    except AttributeError:
+        print_name = str(atten_obj)
+
+    if target_rssi == MAX_RSSI_RESERVED_VALUE:
+        target_atten = ATTEN_MIN_VALUE
+    elif target_rssi == MIN_RSSI_RESERVED_VALUE:
+        target_atten = ATTEN_MAX_VALUE
+    else:
+        log.info("set_rssi {} to {}.".format(print_name, target_rssi))
+        target_atten = calibration_rssi - target_rssi
+
+    if target_atten < 0:
+        log.info("set_rssi: WARNING - you are setting an unreachable RSSI.")
+        log.info(
+            "max RSSI value on {} is {}. Setting attenuation to 0.".format(
+                print_name, calibration_rssi))
+        target_atten = 0
+    if not set_atten(log, atten_obj, target_atten, step_size, time_per_step):
+        log.error("set_rssi to {}failed".format(target_rssi))
+        return False
+    return True
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
new file mode 100644
index 0000000..bac587e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_data_utils.py
@@ -0,0 +1,755 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+import time
+import random
+import re
+
+from acts.utils import rand_ascii_str
+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_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_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 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_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_AFTER_REBOOT
+from acts_contrib.test_utils.tel.tel_defines import RAT_UNKNOWN
+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_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_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 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_wifi_connected
+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 is_droid_in_network_generation_for_subscription
+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 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 wait_for_data_attach_for_subscription
+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 get_service_state_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 get_mobile_data_usage
+from acts_contrib.test_utils.tel.tel_test_utils import get_wifi_usage
+from acts_contrib.test_utils.tel.tel_test_utils import check_is_wifi_connected
+
+def wifi_tethering_cleanup(log, provider, client_list):
+    """Clean up steps for WiFi Tethering.
+
+    Make sure provider turn off tethering.
+    Make sure clients reset WiFi and turn on cellular data.
+
+    Args:
+        log: log object.
+        provider: android object provide WiFi tethering.
+        client_list: a list of clients using tethered WiFi.
+
+    Returns:
+        True if no error happened. False otherwise.
+    """
+    for client in client_list:
+        client.droid.telephonyToggleDataConnection(True)
+        set_wifi_to_default(log, client)
+    if not stop_wifi_tethering(log, provider):
+        provider.log.error("Provider stop WiFi tethering failed.")
+        return False
+    if provider.droid.wifiIsApEnabled():
+        provider.log.error("Provider WiFi tethering is still enabled.")
+        return False
+    return True
+
+
+def wifi_tethering_setup_teardown(log,
+                                  provider,
+                                  client_list,
+                                  ap_band=WIFI_CONFIG_APBAND_2G,
+                                  check_interval=30,
+                                  check_iteration=4,
+                                  do_cleanup=True,
+                                  ssid=None,
+                                  password=None):
+    """Test WiFi Tethering.
+
+    Turn off WiFi on clients.
+    Turn off data and reset WiFi on clients.
+    Verify no Internet access on clients.
+    Turn on WiFi tethering on provider.
+    Clients connect to provider's WiFI.
+    Verify Internet on provider and clients.
+    Tear down WiFi tethering setup and clean up.
+
+    Args:
+        log: log object.
+        provider: android object provide WiFi tethering.
+        client_list: a list of clients using tethered WiFi.
+        ap_band: setup WiFi tethering on 2G or 5G.
+            This is optional, default value is WIFI_CONFIG_APBAND_2G
+        check_interval: delay time between each around of Internet connection check.
+            This is optional, default value is 30 (seconds).
+        check_iteration: check Internet connection for how many times in total.
+            This is optional, default value is 4 (4 times).
+        do_cleanup: after WiFi tethering test, do clean up to tear down tethering
+            setup or not. This is optional, default value is True.
+        ssid: use this string as WiFi SSID to setup tethered WiFi network.
+            This is optional. Default value is None.
+            If it's None, a random string will be generated.
+        password: use this string as WiFi password to setup tethered WiFi network.
+            This is optional. Default value is None.
+            If it's None, a random string will be generated.
+
+    Returns:
+        True if no error happened. False otherwise.
+    """
+    log.info("--->Start wifi_tethering_setup_teardown<---")
+    log.info("Provider: {}".format(provider.serial))
+    if not provider.droid.connectivityIsTetheringSupported():
+        provider.log.error(
+            "Provider does not support tethering. Stop tethering test.")
+        return False
+
+    if ssid is None:
+        ssid = rand_ascii_str(10)
+    if password is None:
+        password = rand_ascii_str(8)
+
+    # No password
+    if password == "":
+        password = None
+
+    try:
+        for client in client_list:
+            log.info("Client: {}".format(client.serial))
+            wifi_toggle_state(log, client, False)
+            client.droid.telephonyToggleDataConnection(False)
+        log.info("WiFI Tethering: Verify client have no Internet access.")
+        for client in client_list:
+            if not verify_internet_connection(
+                    log, client, expected_state=False):
+                client.log.error("Turn off Data on client fail")
+                return False
+
+        provider.log.info(
+            "Provider turn on WiFi tethering. SSID: %s, password: %s", ssid,
+            password)
+
+        if not start_wifi_tethering(log, provider, ssid, password, ap_band):
+            provider.log.error("Provider start WiFi tethering failed.")
+            return False
+        time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+
+        provider.log.info("Provider check Internet connection.")
+        if not verify_internet_connection(log, provider):
+            return False
+        for client in client_list:
+            client.log.info(
+                "Client connect to WiFi and verify AP band correct.")
+            if not ensure_wifi_connected(log, client, ssid, password):
+                client.log.error("Client connect to WiFi failed.")
+                return False
+
+            wifi_info = client.droid.wifiGetConnectionInfo()
+            if ap_band == WIFI_CONFIG_APBAND_5G:
+                if wifi_info["is_24ghz"]:
+                    client.log.error("Expected 5g network. WiFi Info: %s",
+                                     wifi_info)
+                    return False
+            else:
+                if wifi_info["is_5ghz"]:
+                    client.log.error("Expected 2g network. WiFi Info: %s",
+                                     wifi_info)
+                    return False
+
+            client.log.info("Client check Internet connection.")
+            if (not wait_for_wifi_data_connection(log, client, True)
+                    or not verify_internet_connection(log, client)):
+                client.log.error("No WiFi Data on client")
+                return False
+
+        if not tethering_check_internet_connection(
+                log, provider, client_list, check_interval, check_iteration):
+            return False
+
+    finally:
+        if (do_cleanup
+                and (not wifi_tethering_cleanup(log, provider, client_list))):
+            return False
+    return True
+
+
+def tethering_check_internet_connection(log, provider, client_list,
+                                        check_interval, check_iteration):
+    """During tethering test, check client(s) and provider Internet connection.
+
+    Do the following for <check_iteration> times:
+        Delay <check_interval> seconds.
+        Check Tethering provider's Internet connection.
+        Check each client's Internet connection.
+
+    Args:
+        log: log object.
+        provider: android object provide WiFi tethering.
+        client_list: a list of clients using tethered WiFi.
+        check_interval: delay time between each around of Internet connection check.
+        check_iteration: check Internet connection for how many times in total.
+
+    Returns:
+        True if no error happened. False otherwise.
+    """
+    for i in range(1, check_iteration + 1):
+        result = True
+        time.sleep(check_interval)
+        provider.log.info(
+            "Provider check Internet connection after %s seconds.",
+            check_interval * i)
+        if not verify_internet_connection(log, provider):
+            result = False
+            continue
+        for client in client_list:
+            client.log.info(
+                "Client check Internet connection after %s seconds.",
+                check_interval * i)
+            if not verify_internet_connection(log, client):
+                result = False
+                break
+        if result: return result
+    return False
+
+
+def wifi_cell_switching(log, ad, wifi_network_ssid, wifi_network_pass, nw_gen):
+    """Test data connection network switching when phone on <nw_gen>.
+
+    Ensure phone is on <nw_gen>
+    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.
+
+    Args:
+        log: log object.
+        ad: android object.
+        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.
+
+    Returns:
+        True if pass.
+    """
+    try:
+
+        if not ensure_network_generation_for_subscription(
+                log, ad, get_default_data_sub_id(ad), nw_gen,
+                MAX_WAIT_TIME_NW_SELECTION, NETWORK_SERVICE_DATA):
+            ad.log.error("Device failed to register in %s", nw_gen)
+            return False
+
+        start_youtube_video(ad)
+        # Ensure WiFi can connect to live network
+        ad.log.info("Make sure phone can connect to live network by WIFI")
+        if not ensure_wifi_connected(log, ad, wifi_network_ssid,
+                                     wifi_network_pass):
+            ad.log.error("WiFi connect fail.")
+            return False
+        log.info("Phone connected to WIFI.")
+
+        log.info("Step1 Airplane Off, WiFi On, Data On.")
+        toggle_airplane_mode(log, ad, False)
+        wifi_toggle_state(log, ad, True)
+        ad.droid.telephonyToggleDataConnection(True)
+        if (not wait_for_wifi_data_connection(log, ad, True)
+                or not verify_internet_connection(log, ad)):
+            ad.log.error("Data is not on WiFi")
+            return False
+
+        log.info("Step2 WiFi is Off, Data is on Cell.")
+        wifi_toggle_state(log, ad, False)
+        if (not wait_for_cell_data_connection(log, ad, True)
+                or not verify_internet_connection(log, ad)):
+            ad.log.error("Data did not return to cell")
+            return False
+
+        log.info("Step3 WiFi is On, Data is on WiFi.")
+        wifi_toggle_state(log, ad, True)
+        if (not wait_for_wifi_data_connection(log, ad, True)
+                or not verify_internet_connection(log, ad)):
+            ad.log.error("Data did not return to WiFi")
+            return False
+
+        log.info("Step4 WiFi is Off, Data is on Cell.")
+        wifi_toggle_state(log, ad, False)
+        if (not wait_for_cell_data_connection(log, ad, True)
+                or not verify_internet_connection(log, ad)):
+            ad.log.error("Data did not return to cell")
+            return False
+        return True
+
+    finally:
+        ad.force_stop_apk("com.google.android.youtube")
+        wifi_toggle_state(log, ad, False)
+
+
+def airplane_mode_test(log, ad, retries=3):
+    """ Test airplane mode basic on Phone and Live SIM.
+
+    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.
+
+    Args:
+        log: log object.
+        ad: android object.
+
+    Returns:
+        True if pass; False if fail.
+    """
+    if not ensure_phones_idle(log, [ad]):
+        log.error("Failed to return phones to idle.")
+        return False
+
+    try:
+        ad.droid.telephonyToggleDataConnection(True)
+        wifi_toggle_state(log, ad, False)
+
+        ad.log.info("Step1: disable airplane mode and ensure attach")
+        if not toggle_airplane_mode(log, ad, False):
+            ad.log.error("Failed initial attach")
+            return False
+
+        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'.")
+            return False
+
+        time.sleep(30)
+        if not ad.droid.connectivityNetworkIsConnected():
+            ad.log.error("Network is NOT connected!")
+            return False
+
+        if not wait_for_cell_data_connection(log, ad, True):
+            ad.log.error("Failed to enable data connection.")
+            return False
+
+        if not verify_internet_connection(log, ad, retries=3):
+            ad.log.error("Data not available on cell.")
+            return False
+
+        log.info("Step2: enable airplane mode and ensure detach")
+        if not toggle_airplane_mode(log, ad, True):
+            ad.log.error("Failed to enable Airplane Mode")
+            return False
+        if not wait_for_cell_data_connection(log, ad, False):
+            ad.log.error("Failed to disable cell data connection")
+            return False
+
+        if not verify_internet_connection(log, ad, expected_state=False):
+            ad.log.error("Data available in airplane mode.")
+            return False
+
+        log.info("Step3: disable airplane mode and ensure attach")
+        if not toggle_airplane_mode(log, ad, False):
+            ad.log.error("Failed to disable Airplane Mode")
+            return False
+
+        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'.")
+            return False
+
+        time.sleep(30)
+        if not ad.droid.connectivityNetworkIsConnected():
+            ad.log.warning("Network is NOT connected!")
+
+        if not wait_for_cell_data_connection(log, ad, True):
+            ad.log.error("Failed to enable cell data connection")
+            return False
+
+        if not verify_internet_connection(log, ad, retries=3):
+            ad.log.warning("Data not available on cell")
+            return False
+        return True
+    finally:
+        toggle_airplane_mode(log, ad, False)
+
+
+def data_connectivity_single_bearer(log, ad, nw_gen):
+    """Test data connection: single-bearer (no voice).
+
+    Turn off airplane mode, enable Cellular Data.
+    Ensure phone data generation is expected.
+    Verify Internet.
+    Disable Cellular Data, verify Internet is inaccessible.
+    Enable Cellular Data, verify Internet.
+
+    Args:
+        log: log object.
+        ad: android object.
+        nw_gen: network generation the phone should on.
+
+    Returns:
+        True if success.
+        False if failed.
+    """
+    ensure_phones_idle(log, [ad])
+    wait_time = MAX_WAIT_TIME_NW_SELECTION
+    if getattr(ad, 'roaming', False):
+        wait_time = 2 * wait_time
+    if not ensure_network_generation_for_subscription(
+            log, ad, get_default_data_sub_id(ad), nw_gen,
+            MAX_WAIT_TIME_NW_SELECTION, NETWORK_SERVICE_DATA):
+        ad.log.error("Device failed to connect to %s in %s seconds.", nw_gen,
+                     wait_time)
+        return False
+
+    try:
+        log.info("Step1 Airplane Off, Data On.")
+        toggle_airplane_mode(log, ad, False)
+        ad.droid.telephonyToggleDataConnection(True)
+        if not wait_for_cell_data_connection(log, ad, True, timeout_value=wait_time):
+            ad.log.error("Failed to enable data connection.")
+            return False
+
+        log.info("Step2 Verify internet")
+        if not verify_internet_connection(log, ad, retries=3):
+            ad.log.error("Data not available on cell.")
+            return False
+
+        log.info("Step3 Turn off data and verify not connected.")
+        ad.droid.telephonyToggleDataConnection(False)
+        if not wait_for_cell_data_connection(log, ad, False):
+            ad.log.error("Step3 Failed to disable data connection.")
+            return False
+
+        if not verify_internet_connection(log, ad, expected_state=False):
+            ad.log.error("Step3 Data still available when disabled.")
+            return False
+
+        log.info("Step4 Re-enable data.")
+        ad.droid.telephonyToggleDataConnection(True)
+        if not wait_for_cell_data_connection(log, ad, True, timeout_value=wait_time):
+            ad.log.error("Step4 failed to re-enable data.")
+            return False
+        if not verify_internet_connection(log, ad, retries=3):
+            ad.log.error("Data not available on cell.")
+            return False
+
+        if not is_droid_in_network_generation_for_subscription(
+                log, ad, get_default_data_sub_id(ad), nw_gen,
+                NETWORK_SERVICE_DATA):
+            ad.log.error("Failed: droid is no longer on correct network")
+            ad.log.info("Expected:%s, Current:%s", nw_gen,
+                        rat_generation_from_rat(
+                            get_network_rat_for_subscription(
+                                log, ad, get_default_data_sub_id(ad),
+                                NETWORK_SERVICE_DATA)))
+            return False
+        return True
+    finally:
+        ad.droid.telephonyToggleDataConnection(True)
+
+
+def change_data_sim_and_verify_data(log, ad, sim_slot):
+    """Change Data SIM and verify Data attach and Internet access
+
+    Args:
+        log: log object.
+        ad: android device object.
+        sim_slot: SIM slot index.
+
+    Returns:
+        Data SIM changed successfully, data attached and Internet access is OK.
+    """
+    sub_id = get_subid_from_slot_index(log, ad, sim_slot)
+    ad.log.info("Change Data to subId: %s, SIM slot: %s", sub_id, sim_slot)
+    set_subid_for_data(ad, sub_id)
+    if not wait_for_data_attach_for_subscription(log, ad, sub_id,
+                                                 MAX_WAIT_TIME_NW_SELECTION):
+        ad.log.error("Failed to attach data on subId:%s", sub_id)
+        return False
+    if not verify_internet_connection(log, ad):
+        ad.log.error("No Internet access after changing Data SIM.")
+        return False
+    return True
+
+def browsing_test(log, ad, wifi_ssid=None, pass_threshold_in_mb = 1.0):
+    """ Ramdomly browse 6 among 23 selected web sites. The idle time is used to
+    simulate visit duration and normally distributed with the mean 35 seconds
+    and std dev 15 seconds, which means 95% of visit duration will be between
+    5 and 65 seconds. DUT will enter suspend mode when idle time is greater than
+    35 seconds.
+
+    Args:
+        log: log object.
+        ad: android object.
+        pass_threshold_in_mb: minimum traffic of browsing 6 web sites in MB for
+            test pass
+
+    Returns:
+        True if the total traffic of Chrome for browsing 6 web sites is greater
+        than pass_threshold_in_mb. Otherwise False.
+    """
+    web_sites = [
+        "http://tw.yahoo.com",
+        "http://24h.pchome.com.tw",
+        "http://www.mobile01.com",
+        "https://www.android.com/phones/",
+        "http://www.books.com.tw",
+        "http://www.udn.com.tw",
+        "http://news.baidu.com",
+        "http://www.google.com",
+        "http://www.cnn.com",
+        "http://www.nytimes.com",
+        "http://www.amazon.com",
+        "http://www.wikipedia.com",
+        "http://www.ebay.com",
+        "http://www.youtube.com",
+        "http://espn.go.com",
+        "http://www.sueddeutsche.de",
+        "http://www.bild.de",
+        "http://www.welt.de",
+        "http://www.lefigaro.fr",
+        "http://www.accuweather.com",
+        "https://www.flickr.com",
+        "http://world.taobao.com",
+        "http://www.theguardian.com",
+        "http://www.abc.net.au",
+        "http://www.gumtree.com.au",
+        "http://www.commbank.com.au",
+        "http://www.news.com.au",
+        "http://rakuten.co.jp",
+        "http://livedoor.jp",
+        "http://yahoo.co.jp"]
+
+    wifi_connected = False
+    if wifi_ssid and check_is_wifi_connected(ad.log, ad, wifi_ssid):
+        wifi_connected = True
+        usage_level_at_start = get_wifi_usage(ad, apk="com.android.chrome")
+    else:
+        usage_level_at_start = get_mobile_data_usage(ad, apk="com.android.chrome")
+
+    for web_site in random.sample(web_sites, 6):
+        ad.log.info("Browsing %s..." % web_site)
+        ad.adb.shell(
+            "am start -a android.intent.action.VIEW -d %s --es "
+            "com.android.browser.application_id com.android.browser" % web_site)
+
+        idle_time = round(random.normalvariate(35, 15))
+        if idle_time < 2:
+            idle_time = 2
+        elif idle_time > 90:
+            idle_time = 90
+
+        ad.log.info(
+            "Idle time before browsing next web site: %s sec." % idle_time)
+
+        if idle_time > 35:
+            time.sleep(35)
+            rest_idle_time = idle_time-35
+            if rest_idle_time < 3:
+                rest_idle_time = 3
+            ad.log.info("Let device go to sleep for %s sec." % rest_idle_time)
+            ad.droid.wakeLockRelease()
+            ad.droid.goToSleepNow()
+            time.sleep(rest_idle_time)
+            ad.log.info("Wake up device.")
+            ad.wakeup_screen()
+            ad.adb.shell("input keyevent 82")
+            time.sleep(3)
+        else:
+            time.sleep(idle_time)
+
+    if wifi_connected:
+        usage_level = get_wifi_usage(ad, apk="com.android.chrome")
+    else:
+        usage_level = get_mobile_data_usage(ad, apk="com.android.chrome")
+
+    try:
+        usage = round((usage_level - usage_level_at_start)/1024/1024, 2)
+        if usage < pass_threshold_in_mb:
+            ad.log.error(
+                "Usage of browsing '%s MB' is smaller than %s " % (
+                    usage, pass_threshold_in_mb))
+            return False
+        else:
+            ad.log.info("Usage of browsing: %s MB" % usage)
+            return True
+    except Exception as e:
+        ad.log.error(e)
+        usage = "unknown"
+        ad.log.info("Usage of browsing: %s MB" % usage)
+        return False
+
+def reboot_test(log, ad, wifi_ssid=None):
+    """ Reboot test to verify the service availability after reboot.
+
+    Test procedure:
+    1. Reboot
+    2. Wait WAIT_TIME_AFTER_REBOOT for reboot complete.
+    3. Check service state. False will be returned if service state is not "IN_SERVICE".
+    4. Check if network is connected. False will be returned if not.
+    5. Check if cellular data or Wi-Fi connection is available. False will be returned if not.
+    6. Check if internet connection is available. False will be returned if not.
+    7. Check if DSDS mode, data sub ID, voice sub ID and message sub ID still keep the same.
+    8. Check if voice and data RAT keep the same.
+
+    Args:
+        log: log object.
+        ad: android object.
+        wifi_ssid: SSID of Wi-Fi AP for Wi-Fi connection.
+
+    Returns:
+        True if pass; False if fail.
+    """
+    try:
+        wifi_connected = False
+        if wifi_ssid and check_is_wifi_connected(ad.log, ad, wifi_ssid):
+            wifi_connected = True
+
+        data_subid = get_default_data_sub_id(ad)
+        voice_subid = get_outgoing_voice_sub_id(ad)
+        sms_subid = get_outgoing_message_sub_id(ad)
+
+        data_rat_before_reboot = get_network_rat_for_subscription(
+            log, ad, data_subid, NETWORK_SERVICE_DATA)
+        voice_rat_before_reboot = get_network_rat_for_subscription(
+            log, ad, voice_subid, NETWORK_SERVICE_VOICE)
+
+        ad.reboot()
+        time.sleep(WAIT_TIME_AFTER_REBOOT)
+
+        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: %s" % service_state)
+            return False
+
+        if not ad.droid.connectivityNetworkIsConnected():
+            ad.log.error("Network is NOT connected!")
+            return False
+
+        if wifi_connected:
+            if not check_is_wifi_connected(ad.log, ad, wifi_ssid):
+                return False
+        else:
+            if not wait_for_cell_data_connection(log, ad, True):
+                ad.log.error("Failed to enable data connection.")
+                return False
+
+        if not verify_internet_connection(log, ad):
+            ad.log.error("Internet connection is not available")
+            return False
+
+        sim_mode = ad.droid.telephonyGetPhoneCount()
+        if hasattr(ad, "dsds"):
+            if sim_mode == 1:
+                ad.log.error("Phone is in single SIM mode after reboot.")
+                return False
+            elif sim_mode == 2:
+                ad.log.info("Phone keeps being in dual SIM mode after reboot.")
+        else:
+            if sim_mode == 1:
+                ad.log.info("Phone keeps being in single SIM mode after reboot.")
+            elif sim_mode == 2:
+                ad.log.error("Phone is in dual SIM mode after reboot.")
+                return False
+
+        data_subid_after_reboot = get_default_data_sub_id(ad)
+        if data_subid_after_reboot != data_subid:
+            ad.log.error(
+                "Data sub ID changed! (Before reboot: %s; after reboot: %s)",
+                data_subid, data_subid_after_reboot)
+            return False
+        else:
+            ad.log.info("Data sub ID does not change after reboot.")
+
+        voice_subid_after_reboot = get_outgoing_voice_sub_id(ad)
+        if voice_subid_after_reboot != voice_subid:
+            ad.log.error(
+                "Voice sub ID changed! (Before reboot: %s; after reboot: %s)",
+                voice_subid, voice_subid_after_reboot)
+            return False
+        else:
+            ad.log.info("Voice sub ID does not change after reboot.")
+
+        sms_subid_after_reboot = get_outgoing_message_sub_id(ad)
+        if sms_subid_after_reboot != sms_subid:
+            ad.log.error(
+                "Message sub ID changed! (Before reboot: %s; after reboot: %s)",
+                sms_subid, sms_subid_after_reboot)
+            return False
+        else:
+            ad.log.info("Message sub ID does not change after reboot.")
+
+        data_rat_after_reboot = get_network_rat_for_subscription(
+            log, ad, data_subid_after_reboot, NETWORK_SERVICE_DATA)
+        voice_rat_after_reboot = get_network_rat_for_subscription(
+            log, ad, voice_subid_after_reboot, NETWORK_SERVICE_VOICE)
+
+        if data_rat_after_reboot == data_rat_before_reboot:
+            ad.log.info(
+                "Data RAT (%s) does not change after reboot.",
+                data_rat_after_reboot)
+        else:
+            ad.log.error(
+                "Data RAT changed! (Before reboot: %s; after reboot: %s)",
+                data_rat_before_reboot,
+                data_rat_after_reboot)
+            return False
+
+        if voice_rat_after_reboot == voice_rat_before_reboot:
+            ad.log.info(
+                "Voice RAT (%s) does not change after reboot.",
+                voice_rat_after_reboot)
+        else:
+            ad.log.error(
+                "Voice RAT changed! (Before reboot: %s; after reboot: %s)",
+                voice_rat_before_reboot,
+                voice_rat_after_reboot)
+            return False
+
+    except Exception as e:
+        ad.log.error(e)
+        return False
+
+    return True
\ 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
new file mode 100644
index 0000000..e5d86f4
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_defines.py
@@ -0,0 +1,863 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+###############################################
+# TIMERS
+###############################################
+# Max time to wait for phone data/network connection state update
+MAX_WAIT_TIME_CONNECTION_STATE_UPDATE = 60
+
+# Max time to wait for network reselection
+MAX_WAIT_TIME_NW_SELECTION = 180
+
+# Max time to wait for call drop
+MAX_WAIT_TIME_CALL_DROP = 60
+
+# Wait time between state check retry
+WAIT_TIME_BETWEEN_STATE_CHECK = 5
+
+# Max wait time for state change
+MAX_WAIT_TIME_FOR_STATE_CHANGE = 60
+
+# Max time to wait after caller make a call and before
+# callee start ringing
+MAX_WAIT_TIME_CALLEE_RINGING = 90
+
+# country code list
+COUNTRY_CODE_LIST = [
+    "+1", "+44", "+39", "+61", "+49", "+34", "+33", "+47", "+246", "+86",
+    "+850", "+81"
+]
+
+# default pin/password
+DEFAULT_DEVICE_PASSWORD = "1111"
+
+# Wait time after enterring puk code
+WAIT_TIME_SUPPLY_PUK_CODE = 30
+
+# Max time to wait after caller make a call and before
+# callee start ringing
+MAX_WAIT_TIME_ACCEPT_CALL_TO_OFFHOOK_EVENT = 30
+
+# Max time to wait for "onCallStatehangedIdle" event after reject or ignore
+# incoming call
+MAX_WAIT_TIME_CALL_IDLE_EVENT = 60
+
+# Max time to wait after initiating a call for telecom to report in-call
+MAX_WAIT_TIME_CALL_INITIATION = 90
+
+# Time to wait after change Mode Pref for Stress Test
+WAIT_TIME_AFTER_MODE_CHANGE = 60
+
+# Max time to wait for Carrier Config Version to Update in mins
+WAIT_TIME_FOR_CARRIERCONFIG_CHANGE = 20
+
+# Max time to wait for Emergency DB Version to Update in mins
+WAIT_TIME_FOR_ER_DB_CHANGE = 10
+
+# Max time to wait after toggle airplane mode and before
+# get expected event
+MAX_WAIT_TIME_AIRPLANEMODE_EVENT = 90
+
+# Max time to wait after device sent an SMS and before
+# get "onSmsSentSuccess" event
+MAX_WAIT_TIME_SMS_SENT_SUCCESS = 60
+
+# Max time to wait after device sent an SMS and before
+# get "onSmsSentSuccess" event in case of collision.
+MAX_WAIT_TIME_SMS_SENT_SUCCESS_IN_COLLISION = 60
+
+# Max time to wait after MT SMS was sent and before device
+# actually receive this MT SMS.
+MAX_WAIT_TIME_SMS_RECEIVE = 120
+
+# Max time to wait after MT SMS was sent and before device
+# actually receive this MT SMS in case of collision.
+MAX_WAIT_TIME_SMS_RECEIVE_IN_COLLISION = 1200
+
+# Max time to wait for IMS registration
+MAX_WAIT_TIME_IMS_REGISTRATION = 120
+
+# TODO: b/26338156 MAX_WAIT_TIME_VOLTE_ENABLED and MAX_WAIT_TIME_WFC_ENABLED should only
+# be used for wait after IMS registration.
+
+# Max time to wait for VoLTE enabled flag to be True
+MAX_WAIT_TIME_VOLTE_ENABLED = MAX_WAIT_TIME_IMS_REGISTRATION + 60
+
+# Max time to wait for WFC enabled flag to be True
+MAX_WAIT_TIME_WFC_ENABLED = MAX_WAIT_TIME_IMS_REGISTRATION + 120
+
+# Max time to wait for WFC enabled flag to be False
+MAX_WAIT_TIME_WFC_DISABLED = 60
+
+# Max time to wait for WiFi Manager to Connect to an AP
+MAX_WAIT_TIME_WIFI_CONNECTION = 30
+
+# Max time to wait for Video Session Modify Messaging
+MAX_WAIT_TIME_VIDEO_SESSION_EVENT = 10
+
+# Max time to wait after a network connection for ConnectivityManager to
+# report a working user plane data connection
+MAX_WAIT_TIME_USER_PLANE_DATA = 20
+
+# Max time to wait for tethering entitlement check
+MAX_WAIT_TIME_TETHERING_ENTITLEMENT_CHECK = 60
+
+# Max time to wait for voice mail count report correct result.
+MAX_WAIT_TIME_VOICE_MAIL_COUNT = 90
+
+# Max time to wait for data SIM change
+MAX_WAIT_TIME_DATA_SUB_CHANGE = 150
+
+# Max time to wait for telecom Ringing status after receive ringing event
+MAX_WAIT_TIME_TELECOM_RINGING = 5
+
+# Max time to wait for phone get provisioned.
+MAX_WAIT_TIME_PROVISIONING = 300
+
+# Time to wait after call setup before declaring
+# that the call is actually successful
+WAIT_TIME_IN_CALL = 30
+
+# Time to wait after call setup before declaring
+# that the call is actually successful
+WAIT_TIME_IN_CALL_LONG = 60
+
+# (For IMS, e.g. VoLTE-VoLTE, WFC-WFC, VoLTE-WFC test only)
+# Time to wait after call setup before declaring
+# that the call is actually successful
+WAIT_TIME_IN_CALL_FOR_IMS = 30
+
+# Time to wait after phone receive incoming call before phone reject this call.
+WAIT_TIME_REJECT_CALL = 2
+
+# Time to leave a voice message after callee reject the incoming call
+WAIT_TIME_LEAVE_VOICE_MAIL = 30
+
+# Time to wait after accept video call and before checking state
+WAIT_TIME_ACCEPT_VIDEO_CALL_TO_CHECK_STATE = 2
+
+# Time delay to ensure user actions are performed in
+# 'human' time rather than at the speed of the script
+WAIT_TIME_ANDROID_STATE_SETTLING = 1
+
+# Time to wait after registration to ensure the phone
+# has sufficient time to reconfigure based on new network
+WAIT_TIME_BETWEEN_REG_AND_CALL = 5
+
+# Wait time for data pdn to be up on CBRS
+WAIT_TIME_FOR_CBRS_DATA_SWITCH = 60
+
+# Time to wait for 1xrtt voice attach check
+# After DUT voice network type report 1xrtt (from unknown), it need to wait for
+# several seconds before the DUT can receive incoming call.
+WAIT_TIME_1XRTT_VOICE_ATTACH = 30
+
+# Time to wait for data status change during wifi tethering,.
+WAIT_TIME_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING = 30
+
+# Time to wait for rssi calibration.
+# This is the delay between <WiFi Connected> and <Turn on Screen to get RSSI>.
+WAIT_TIME_WIFI_RSSI_CALIBRATION_WIFI_CONNECTED = 10
+# This is the delay between <Turn on Screen> and <Call API to get WiFi RSSI>.
+WAIT_TIME_WIFI_RSSI_CALIBRATION_SCREEN_ON = 2
+
+# Time to wait for each operation on voice mail box.
+WAIT_TIME_VOICE_MAIL_SERVER_RESPONSE = 10
+
+# Time to wait for radio to up and running after reboot
+WAIT_TIME_AFTER_REBOOT = 10
+
+# Time to wait for radio to up and running after force crash
+WAIT_TIME_AFTER_CRASH = 60
+
+# Time to wait for factory data reset
+WAIT_TIME_AFTER_FDR = 60
+
+# Time to wait for boot complete after reboot
+WAIT_TIME_FOR_BOOT_COMPLETE = 75
+
+# Time to wait for tethering test after reboot
+WAIT_TIME_TETHERING_AFTER_REBOOT = 10
+
+# Time to wait after changing data sub id
+WAIT_TIME_CHANGE_DATA_SUB_ID = 30
+
+# Time to wait after changing voice sub id
+WAIT_TIME_CHANGE_VOICE_SUB_ID = 5
+
+# Time to wait after changing message sub id
+WAIT_TIME_CHANGE_MESSAGE_SUB_ID = 5
+
+# Wait time for Data Stall to detect
+WAIT_TIME_FOR_DATA_STALL = 300
+
+# Wait time for Network Validation Failed detection
+WAIT_TIME_FOR_NW_VALID_FAIL = 300
+
+# Wait time for Data Stall to recover
+WAIT_TIME_FOR_DATA_STALL_RECOVERY = 360
+
+# Callbox Power level which will cause no service on device
+POWER_LEVEL_OUT_OF_SERVICE = -100
+
+# Callbox Power level which will ensure full service on device
+POWER_LEVEL_FULL_SERVICE = -20
+
+# set a fake time to test time recovering from network
+FAKE_DATE_TIME = "010203042019.05"
+FAKE_YEAR = "2019"
+WAIT_TIME_SYNC_DATE_TIME_FROM_NETWORK = 2
+
+# These are used in phone_number_formatter
+PHONE_NUMBER_STRING_FORMAT_7_DIGIT = 7
+PHONE_NUMBER_STRING_FORMAT_10_DIGIT = 10
+PHONE_NUMBER_STRING_FORMAT_11_DIGIT = 11
+PHONE_NUMBER_STRING_FORMAT_12_DIGIT = 12
+
+# MAX screen-on time during test (in unit of second)
+MAX_SCREEN_ON_TIME = 1800
+
+# In Voice Mail box, press this digit to delete one message.
+VOICEMAIL_DELETE_DIGIT = '7'
+
+# MAX number of saved voice mail in voice mail box.
+MAX_SAVED_VOICE_MAIL = 25
+
+# SIM1 slot index
+SIM1_SLOT_INDEX = 0
+
+# SIM2 slot index
+SIM2_SLOT_INDEX = 1
+
+# invalid Subscription ID
+INVALID_SUB_ID = -1
+
+# invalid SIM slot index
+INVALID_SIM_SLOT_INDEX = -1
+
+# WiFI RSSI is -127 if WiFi is not connected
+INVALID_WIFI_RSSI = -127
+
+# MAX and MIN value for attenuator settings
+ATTEN_MAX_VALUE = 95
+ATTEN_MIN_VALUE = 0
+
+MAX_RSSI_RESERVED_VALUE = 100
+MIN_RSSI_RESERVED_VALUE = -200
+
+# cellular weak RSSI value
+CELL_WEAK_RSSI_VALUE = -105
+# cellular strong RSSI value
+CELL_STRONG_RSSI_VALUE = -70
+# WiFi weak RSSI value
+WIFI_WEAK_RSSI_VALUE = -63
+
+# Emergency call number
+DEFAULT_EMERGENCY_CALL_NUMBER = "911"
+
+EMERGENCY_CALL_NUMBERS = [
+    "08", "000", "110", "112", "118", "119", "911", "999", "*911", "#911"
+]
+
+AOSP_PREFIX = "aosp_"
+
+INCALL_UI_DISPLAY_FOREGROUND = "foreground"
+INCALL_UI_DISPLAY_BACKGROUND = "background"
+INCALL_UI_DISPLAY_DEFAULT = "default"
+
+NETWORK_CONNECTION_TYPE_WIFI = 'wifi'
+NETWORK_CONNECTION_TYPE_CELL = 'cell'
+NETWORK_CONNECTION_TYPE_MMS = 'mms'
+NETWORK_CONNECTION_TYPE_HIPRI = 'hipri'
+NETWORK_CONNECTION_TYPE_UNKNOWN = 'unknown'
+
+TETHERING_MODE_WIFI = 'wifi'
+
+# Tether interface types defined in ConnectivityManager
+TETHERING_INVALID = -1
+TETHERING_WIFI = 0
+TETHERING_USB = 1
+TETHERING_BLUETOOTH = 2
+
+NETWORK_SERVICE_VOICE = 'voice'
+NETWORK_SERVICE_DATA = 'data'
+
+CARRIER_VZW = 'vzw'
+CARRIER_ATT = 'att'
+CARRIER_TMO = 'tmo'
+CARRIER_SPT = 'spt'
+CARRIER_EEUK = 'eeuk'
+CARRIER_VFUK = 'vfuk'
+CARRIER_UNKNOWN = 'unknown'
+CARRIER_GMBH = 'gmbh'
+CARRIER_ITA = 'ita'
+CARRIER_ESP = 'esp'
+CARRIER_ORG = 'org'
+CARRIER_TEL = 'tel'
+CARRIER_TSA = 'tsa'
+CARRIER_SING = 'singtel'
+CARRIER_USCC = 'uscc'
+CARRIER_ROGERS = 'ROGERS'
+CARRIER_TELUS = 'tls'
+CARRIER_KOODO = 'kdo'
+CARRIER_VIDEOTRON = 'vtrn'
+CARRIER_BELL = 'bell'
+CARRIER_FRE = 'fre'
+CARRIER_FI = 'fi'
+CARRIER_NTT_DOCOMO = 'ntt_docomo'
+CARRIER_KDDI = 'kddi'
+CARRIER_RAKUTEN = 'rakuten'
+CARRIER_SBM = 'sbm'
+
+RAT_FAMILY_CDMA = 'cdma'
+RAT_FAMILY_CDMA2000 = 'cdma2000'
+RAT_FAMILY_IDEN = 'iden'
+RAT_FAMILY_GSM = 'gsm'
+RAT_FAMILY_WCDMA = 'wcdma'
+RAT_FAMILY_UMTS = RAT_FAMILY_WCDMA
+RAT_FAMILY_WLAN = 'wlan'
+RAT_FAMILY_LTE = 'lte'
+RAT_FAMILY_NR = 'nr'
+RAT_FAMILY_TDSCDMA = 'tdscdma'
+RAT_FAMILY_UNKNOWN = 'unknown'
+
+CAPABILITY_PHONE = 'phone'
+CAPABILITY_VOLTE = 'volte'
+CAPABILITY_VT = 'vt'
+CAPABILITY_WFC = 'wfc'
+CAPABILITY_MSIM = 'msim'
+CAPABILITY_OMADM = 'omadm'
+CAPABILITY_WFC_MODE_CHANGE = 'wfc_mode_change'
+CAPABILITY_CONFERENCE = 'conference'
+CAPABILITY_VOLTE_PROVISIONING = 'volte_provisioning'
+CAPABILITY_VOLTE_OVERRIDE_WFC_PROVISIONING = 'volte_override_wfc_provisioning'
+CAPABILITY_HIDE_ENHANCED_4G_LTE_BOOL = 'hide_enhanced_4g_lte'
+
+# Carrier Config Versions
+VZW_CARRIER_CONFIG_VERSION = "29999999999.1"
+ATT_CARRIER_CONFIG_VERSION = "28888888888.1"
+
+# Constant for operation direction
+DIRECTION_MOBILE_ORIGINATED = "MO"
+DIRECTION_MOBILE_TERMINATED = "MT"
+
+# Constant for call teardown side
+CALL_TEARDOWN_PHONE = "PHONE"
+CALL_TEARDOWN_REMOTE = "REMOTE"
+
+WIFI_VERBOSE_LOGGING_ENABLED = 1
+WIFI_VERBOSE_LOGGING_DISABLED = 0
+"""
+Begin shared constant define for both Python and Java
+"""
+
+# Constant for WiFi Calling WFC mode
+WFC_MODE_WIFI_ONLY = "WIFI_ONLY"
+WFC_MODE_CELLULAR_PREFERRED = "CELLULAR_PREFERRED"
+WFC_MODE_WIFI_PREFERRED = "WIFI_PREFERRED"
+WFC_MODE_DISABLED = "DISABLED"
+WFC_MODE_UNKNOWN = "UNKNOWN"
+
+# Constant for Video Telephony VT state
+VT_STATE_AUDIO_ONLY = "AUDIO_ONLY"
+VT_STATE_TX_ENABLED = "TX_ENABLED"
+VT_STATE_RX_ENABLED = "RX_ENABLED"
+VT_STATE_BIDIRECTIONAL = "BIDIRECTIONAL"
+VT_STATE_TX_PAUSED = "TX_PAUSED"
+VT_STATE_RX_PAUSED = "RX_PAUSED"
+VT_STATE_BIDIRECTIONAL_PAUSED = "BIDIRECTIONAL_PAUSED"
+VT_STATE_STATE_INVALID = "INVALID"
+
+# Constant for Video Telephony Video quality
+VT_VIDEO_QUALITY_DEFAULT = "DEFAULT"
+VT_VIDEO_QUALITY_UNKNOWN = "UNKNOWN"
+VT_VIDEO_QUALITY_HIGH = "HIGH"
+VT_VIDEO_QUALITY_MEDIUM = "MEDIUM"
+VT_VIDEO_QUALITY_LOW = "LOW"
+VT_VIDEO_QUALITY_INVALID = "INVALID"
+
+# Constant for Call State (for call object)
+CALL_STATE_ACTIVE = "ACTIVE"
+CALL_STATE_NEW = "NEW"
+CALL_STATE_DIALING = "DIALING"
+CALL_STATE_RINGING = "RINGING"
+CALL_STATE_HOLDING = "HOLDING"
+CALL_STATE_DISCONNECTED = "DISCONNECTED"
+CALL_STATE_PRE_DIAL_WAIT = "PRE_DIAL_WAIT"
+CALL_STATE_CONNECTING = "CONNECTING"
+CALL_STATE_DISCONNECTING = "DISCONNECTING"
+CALL_STATE_UNKNOWN = "UNKNOWN"
+CALL_STATE_INVALID = "INVALID"
+
+# Constant for PRECISE Call State (for call object)
+PRECISE_CALL_STATE_ACTIVE = "ACTIVE"
+PRECISE_CALL_STATE_ALERTING = "ALERTING"
+PRECISE_CALL_STATE_DIALING = "DIALING"
+PRECISE_CALL_STATE_INCOMING = "INCOMING"
+PRECISE_CALL_STATE_HOLDING = "HOLDING"
+PRECISE_CALL_STATE_DISCONNECTED = "DISCONNECTED"
+PRECISE_CALL_STATE_WAITING = "WAITING"
+PRECISE_CALL_STATE_DISCONNECTING = "DISCONNECTING"
+PRECISE_CALL_STATE_IDLE = "IDLE"
+PRECISE_CALL_STATE_UNKNOWN = "UNKNOWN"
+PRECISE_CALL_STATE_INVALID = "INVALID"
+
+# Constant for DC POWER STATE
+DC_POWER_STATE_LOW = "LOW"
+DC_POWER_STATE_HIGH = "HIGH"
+DC_POWER_STATE_MEDIUM = "MEDIUM"
+DC_POWER_STATE_UNKNOWN = "UNKNOWN"
+
+# Constant for Audio Route
+AUDIO_ROUTE_EARPIECE = "EARPIECE"
+AUDIO_ROUTE_BLUETOOTH = "BLUETOOTH"
+AUDIO_ROUTE_SPEAKER = "SPEAKER"
+AUDIO_ROUTE_WIRED_HEADSET = "WIRED_HEADSET"
+AUDIO_ROUTE_WIRED_OR_EARPIECE = "WIRED_OR_EARPIECE"
+
+# Constant for Call Capability
+CALL_CAPABILITY_HOLD = "HOLD"
+CALL_CAPABILITY_SUPPORT_HOLD = "SUPPORT_HOLD"
+CALL_CAPABILITY_MERGE_CONFERENCE = "MERGE_CONFERENCE"
+CALL_CAPABILITY_SWAP_CONFERENCE = "SWAP_CONFERENCE"
+CALL_CAPABILITY_UNUSED_1 = "UNUSED_1"
+CALL_CAPABILITY_RESPOND_VIA_TEXT = "RESPOND_VIA_TEXT"
+CALL_CAPABILITY_MUTE = "MUTE"
+CALL_CAPABILITY_MANAGE_CONFERENCE = "MANAGE_CONFERENCE"
+CALL_CAPABILITY_SUPPORTS_VT_LOCAL_RX = "SUPPORTS_VT_LOCAL_RX"
+CALL_CAPABILITY_SUPPORTS_VT_LOCAL_TX = "SUPPORTS_VT_LOCAL_TX"
+CALL_CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = "SUPPORTS_VT_LOCAL_BIDIRECTIONAL"
+CALL_CAPABILITY_SUPPORTS_VT_REMOTE_RX = "SUPPORTS_VT_REMOTE_RX"
+CALL_CAPABILITY_SUPPORTS_VT_REMOTE_TX = "SUPPORTS_VT_REMOTE_TX"
+CALL_CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL = "SUPPORTS_VT_REMOTE_BIDIRECTIONAL"
+CALL_CAPABILITY_SEPARATE_FROM_CONFERENCE = "SEPARATE_FROM_CONFERENCE"
+CALL_CAPABILITY_DISCONNECT_FROM_CONFERENCE = "DISCONNECT_FROM_CONFERENCE"
+CALL_CAPABILITY_SPEED_UP_MT_AUDIO = "SPEED_UP_MT_AUDIO"
+CALL_CAPABILITY_CAN_UPGRADE_TO_VIDEO = "CAN_UPGRADE_TO_VIDEO"
+CALL_CAPABILITY_CAN_PAUSE_VIDEO = "CAN_PAUSE_VIDEO"
+CALL_CAPABILITY_UNKOWN = "UNKOWN"
+
+# Constant for Call Property
+CALL_PROPERTY_HIGH_DEF_AUDIO = "HIGH_DEF_AUDIO"
+CALL_PROPERTY_CONFERENCE = "CONFERENCE"
+CALL_PROPERTY_GENERIC_CONFERENCE = "GENERIC_CONFERENCE"
+CALL_PROPERTY_WIFI = "WIFI"
+CALL_PROPERTY_EMERGENCY_CALLBACK_MODE = "EMERGENCY_CALLBACK_MODE"
+CALL_PROPERTY_UNKNOWN = "UNKNOWN"
+
+# Constant for Call Presentation
+CALL_PRESENTATION_ALLOWED = "ALLOWED"
+CALL_PRESENTATION_RESTRICTED = "RESTRICTED"
+CALL_PRESENTATION_PAYPHONE = "PAYPHONE"
+CALL_PRESENTATION_UNKNOWN = "UNKNOWN"
+
+# Constant for Network Generation
+GEN_2G = "2G"
+GEN_3G = "3G"
+GEN_4G = "4G"
+GEN_5G = "5G"
+GEN_UNKNOWN = "UNKNOWN"
+
+# Constant for Network RAT
+RAT_IWLAN = "IWLAN"
+RAT_NR = "NR"
+RAT_LTE = "LTE"
+RAT_5G = "5G"
+RAT_4G = "4G"
+RAT_3G = "3G"
+RAT_2G = "2G"
+RAT_WCDMA = "WCDMA"
+RAT_UMTS = "UMTS"
+RAT_1XRTT = "1XRTT"
+RAT_EDGE = "EDGE"
+RAT_GPRS = "GPRS"
+RAT_HSDPA = "HSDPA"
+RAT_HSUPA = "HSUPA"
+RAT_CDMA = "CDMA"
+RAT_EVDO = "EVDO"
+RAT_EVDO_0 = "EVDO_0"
+RAT_EVDO_A = "EVDO_A"
+RAT_EVDO_B = "EVDO_B"
+RAT_IDEN = "IDEN"
+RAT_EHRPD = "EHRPD"
+RAT_HSPA = "HSPA"
+RAT_HSPAP = "HSPAP"
+RAT_GSM = "GSM"
+RAT_TD_SCDMA = "TD_SCDMA"
+RAT_GLOBAL = "GLOBAL"
+RAT_LTE_CA = "LTE_CA"  # LTE Carrier Aggregation
+RAT_UNKNOWN = "UNKNOWN"
+
+# Constant for Phone Type
+PHONE_TYPE_GSM = "GSM"
+PHONE_TYPE_NONE = "NONE"
+PHONE_TYPE_CDMA = "CDMA"
+PHONE_TYPE_SIP = "SIP"
+
+# Constant for SIM Power State
+CARD_POWER_DOWN = 0
+CARD_POWER_UP = 1
+CARD_POWER_UP_PASS_THROUGH = 2
+
+# Constant for SIM State
+SIM_STATE_READY = "READY"
+SIM_STATE_UNKNOWN = "UNKNOWN"
+SIM_STATE_ABSENT = "ABSENT"
+SIM_STATE_PUK_REQUIRED = "PUK_REQUIRED"
+SIM_STATE_PIN_REQUIRED = "PIN_REQUIRED"
+SIM_STATE_NETWORK_LOCKED = "NETWORK_LOCKED"
+SIM_STATE_NOT_READY = "NOT_READY"
+SIM_STATE_PERM_DISABLED = "PERM_DISABLED"
+SIM_STATE_CARD_IO_ERROR = "CARD_IO_ERROR"
+SIM_STATE_LOADED = "LOADED"
+
+SINGLE_SIM_CONFIG = "ssss"
+MULTI_SIM_CONFIG = "dsds"
+
+# Constant for Data Connection State
+DATA_STATE_CONNECTED = "CONNECTED"
+DATA_STATE_DISCONNECTED = "DISCONNECTED"
+DATA_STATE_CONNECTING = "CONNECTING"
+DATA_STATE_SUSPENDED = "SUSPENDED"
+DATA_STATE_UNKNOWN = "UNKNOWN"
+
+# Constant for Data Roaming State
+DATA_ROAMING_ENABLE = 1
+DATA_ROAMING_DISABLE = 0
+
+# Constant for ConnectivityManager Data Connection
+TYPE_MOBILE = 0
+TYPE_WIFI = 1
+
+# Constant for Telephony Manager Call State
+TELEPHONY_STATE_RINGING = "RINGING"
+TELEPHONY_STATE_IDLE = "IDLE"
+TELEPHONY_STATE_OFFHOOK = "OFFHOOK"
+TELEPHONY_STATE_UNKNOWN = "UNKNOWN"
+
+# Constant for TTY Mode
+TTY_MODE_FULL = "FULL"
+TTY_MODE_HCO = "HCO"
+TTY_MODE_OFF = "OFF"
+TTY_MODE_VCO = "VCO"
+
+# Constant for Service State
+SERVICE_STATE_EMERGENCY_ONLY = "EMERGENCY_ONLY"
+SERVICE_STATE_IN_SERVICE = "IN_SERVICE"
+SERVICE_STATE_OUT_OF_SERVICE = "OUT_OF_SERVICE"
+SERVICE_STATE_POWER_OFF = "POWER_OFF"
+SERVICE_STATE_UNKNOWN = "UNKNOWN"
+
+# Service State Mapping
+SERVICE_STATE_MAPPING = {
+    "-1": SERVICE_STATE_UNKNOWN,
+    "0": SERVICE_STATE_IN_SERVICE,
+    "1": SERVICE_STATE_OUT_OF_SERVICE,
+    "2": SERVICE_STATE_EMERGENCY_ONLY,
+    "3": SERVICE_STATE_POWER_OFF
+}
+
+# Constant for VoLTE Hand-over Service State
+VOLTE_SERVICE_STATE_HANDOVER_STARTED = "STARTED"
+VOLTE_SERVICE_STATE_HANDOVER_COMPLETED = "COMPLETED"
+VOLTE_SERVICE_STATE_HANDOVER_FAILED = "FAILED"
+VOLTE_SERVICE_STATE_HANDOVER_CANCELED = "CANCELED"
+VOLTE_SERVICE_STATE_HANDOVER_UNKNOWN = "UNKNOWN"
+
+# Constant for precise call state state listen level
+PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND = "FOREGROUND"
+PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING = "RINGING"
+PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND = "BACKGROUND"
+
+# Constants used to register or de-register for call callback events
+EVENT_CALL_STATE_CHANGED = "EVENT_STATE_CHANGED"
+EVENT_CALL_CHILDREN_CHANGED = "EVENT_CHILDREN_CHANGED"
+
+# Constants used to register or de-register for video call callback events
+EVENT_VIDEO_SESSION_MODIFY_REQUEST_RECEIVED = "EVENT_VIDEO_SESSION_MODIFY_REQUEST_RECEIVED"
+EVENT_VIDEO_SESSION_MODIFY_RESPONSE_RECEIVED = "EVENT_VIDEO_SESSION_MODIFY_RESPONSE_RECEIVED"
+EVENT_VIDEO_SESSION_EVENT = "EVENT_VIDEO_SESSION_EVENT"
+EVENT_VIDEO_PEER_DIMENSIONS_CHANGED = "EVENT_VIDEO_PEER_DIMENSIONS_CHANGED"
+EVENT_VIDEO_QUALITY_CHANGED = "EVENT_VIDEO_QUALITY_CHANGED"
+EVENT_VIDEO_DATA_USAGE_CHANGED = "EVENT_VIDEO_DATA_USAGE_CHANGED"
+EVENT_VIDEO_CAMERA_CAPABILITIES_CHANGED = "EVENT_VIDEO_CAMERA_CAPABILITIES_CHANGED"
+EVENT_VIDEO_INVALID = "EVENT_VIDEO_INVALID"
+
+# Constant for Video Call Session Event Name
+SESSION_EVENT_RX_PAUSE = "SESSION_EVENT_RX_PAUSE"
+SESSION_EVENT_RX_RESUME = "SESSION_EVENT_RX_RESUME"
+SESSION_EVENT_TX_START = "SESSION_EVENT_TX_START"
+SESSION_EVENT_TX_STOP = "SESSION_EVENT_TX_STOP"
+SESSION_EVENT_CAMERA_FAILURE = "SESSION_EVENT_CAMERA_FAILURE"
+SESSION_EVENT_CAMERA_READY = "SESSION_EVENT_CAMERA_READY"
+SESSION_EVENT_UNKNOWN = "SESSION_EVENT_UNKNOWN"
+
+NETWORK_MODE_WCDMA_PREF = "NETWORK_MODE_WCDMA_PREF"
+NETWORK_MODE_GSM_ONLY = "NETWORK_MODE_GSM_ONLY"
+NETWORK_MODE_WCDMA_ONLY = "NETWORK_MODE_WCDMA_ONLY"
+NETWORK_MODE_GSM_UMTS = "NETWORK_MODE_GSM_UMTS"
+NETWORK_MODE_CDMA = "NETWORK_MODE_CDMA"
+NETWORK_MODE_CDMA_NO_EVDO = "NETWORK_MODE_CDMA_NO_EVDO"
+NETWORK_MODE_EVDO_NO_CDMA = "NETWORK_MODE_EVDO_NO_CDMA"
+NETWORK_MODE_GLOBAL = "NETWORK_MODE_GLOBAL"
+NETWORK_MODE_LTE_CDMA_EVDO = "NETWORK_MODE_LTE_CDMA_EVDO"
+NETWORK_MODE_LTE_GSM_WCDMA = "NETWORK_MODE_LTE_GSM_WCDMA"
+NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA = "NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA"
+NETWORK_MODE_LTE_ONLY = "NETWORK_MODE_LTE_ONLY"
+NETWORK_MODE_LTE_WCDMA = "NETWORK_MODE_LTE_WCDMA"
+NETWORK_MODE_TDSCDMA_ONLY = "NETWORK_MODE_TDSCDMA_ONLY"
+NETWORK_MODE_TDSCDMA_WCDMA = "NETWORK_MODE_TDSCDMA_WCDMA"
+NETWORK_MODE_LTE_TDSCDMA = "NETWORK_MODE_LTE_TDSCDMA"
+NETWORK_MODE_TDSCDMA_GSM = "NETWORK_MODE_TDSCDMA_GSM"
+NETWORK_MODE_LTE_TDSCDMA_GSM = "NETWORK_MODE_LTE_TDSCDMA_GSM"
+NETWORK_MODE_TDSCDMA_GSM_WCDMA = "NETWORK_MODE_TDSCDMA_GSM_WCDMA"
+NETWORK_MODE_LTE_TDSCDMA_WCDMA = "NETWORK_MODE_LTE_TDSCDMA_WCDMA"
+NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA = "NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA"
+NETWORK_MODE_TDSCDMA_CDMA_EVDO_WCDMA = "NETWORK_MODE_TDSCDMA_CDMA_EVDO_WCDMA"
+NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = "NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA"
+NETWORK_MODE_NR_LTE_GSM_WCDMA = "NETWORK_MODE_NR_LTE_GSM_WCDMA"
+NETWORK_MODE_NR_ONLY = "NETWORK_MODE_NR_ONLY"
+NETWORK_MODE_NR_LTE = "NETWORK_MODE_NR_LTE"
+NETWORK_MODE_NR_LTE_CDMA_EVDO = "NETWORK_MODE_NR_LTE_CDMA_EVDO"
+NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA = "NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA"
+NETWORK_MODE_NR_LTE_WCDMA = "NETWORK_MODE_NR_LTE_WCDMA"
+NETWORK_MODE_NR_LTE_TDSCDMA = "NETWORK_MODE_NR_LTE_TDSCDMA"
+NETWORK_MODE_NR_LTE_TDSCDMA_GSM = "NETWORK_MODE_NR_LTE_TDSCDMA_GSM"
+NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA = "NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA"
+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"
+
+# Carrier Config Update
+CARRIER_ID_VERSION = "3"
+ER_DB_ID_VERSION = "99999"
+
+CARRIER_ID_VERSION_P = "5"
+WAIT_TIME_FOR_CARRIERID_CHANGE = 6
+CARRIER_ID_METADATA_URL = "am broadcast -a com.google.android.gms." \
+     "phenotype.FLAG_OVERRIDE --es package 'com.google.android.configupdater'" \
+     " --es user '\*' --esa flags 'CarrierIdentification__metadata_url' " \
+     "--esa values 'https://www.gstatic.com/android/config_update/110618-" \
+     "carrier-id-metadata.txt' --esa types 'string' com.google.android.gms"
+
+CARRIER_ID_METADATA_URL_P = "am broadcast -a com.google.android.gms." \
+     "phenotype.FLAG_OVERRIDE --es package 'com.google.android.configupdater'" \
+     " --es user '\*' --esa flags 'CarrierIdentification__metadata_url' " \
+     "--esa values 'https://www.gstatic.com/android/telephony/carrierid/" \
+     "030419-p-carrier-id-metadata.txt' --esa types 'string' com.google.android.gms"
+
+CARRIER_ID_CONTENT_URL = "am broadcast -a com.google.android.gms." \
+     "phenotype.FLAG_OVERRIDE --es package 'com.google.android.configupdater'" \
+     " --es user '\*' --esa flags 'CarrierIdentification__content_url' " \
+     "--esa values 'https://www.gstatic.com/android/config_update/110618-" \
+     "carrier-id.pb' --esa types 'string' com.google.android.gms"
+
+CARRIER_ID_CONTENT_URL_P = "am broadcast -a com.google.android.gms." \
+     "phenotype.FLAG_OVERRIDE --es package 'com.google.android.configupdater'" \
+     " --es user '\*' --esa flags 'CarrierIdentification__content_url' " \
+     "--esa values 'https://www.gstatic.com/android/telephony/carrierid/" \
+     "030419-p-carrier-id.pb' --esa types 'string' com.google.android.gms"
+
+# Constant for Messaging Event Name
+EventSmsDeliverSuccess = "SmsDeliverSuccess"
+EventSmsDeliverFailure = "SmsDeliverFailure"
+EventSmsSentSuccess = "SmsSentSuccess"
+EventSmsSentFailure = "SmsSentFailure"
+EventSmsReceived = "SmsReceived"
+EventMmsSentSuccess = "MmsSentSuccess"
+EventMmsSentFailure = "MmsSentFailure"
+EventMmsDownloaded = "MmsDownloaded"
+EventWapPushReceived = "WapPushReceived"
+EventDataSmsReceived = "DataSmsReceived"
+EventCmasReceived = "CmasReceived"
+EventEtwsReceived = "EtwsReceived"
+
+# Constants for Telecom Call Management Event Name (see InCallService.java).
+EventTelecomCallAdded = "TelecomCallAdded"
+EventTelecomCallRemoved = "TelecomCallRemoved"
+
+# Constant for Telecom Call Event Name (see Call.java)
+EventTelecomCallStateChanged = "TelecomCallStateChanged"
+EventTelecomCallParentChanged = "TelecomCallParentChanged"
+EventTelecomCallChildrenChanged = "TelecomCallChildrenChanged"
+EventTelecomCallDetailsChanged = "TelecomCallDetailsChanged"
+EventTelecomCallCannedTextResponsesLoaded = "TelecomCallCannedTextResponsesLoaded"
+EventTelecomCallPostDialWait = "TelecomCallPostDialWait"
+EventTelecomCallVideoCallChanged = "TelecomCallVideoCallChanged"
+EventTelecomCallDestroyed = "TelecomCallDestroyed"
+EventTelecomCallConferenceableCallsChanged = "TelecomCallConferenceableCallsChanged"
+
+# Constant for Video Call Event Name
+EventTelecomVideoCallSessionModifyRequestReceived = "TelecomVideoCallSessionModifyRequestReceived"
+EventTelecomVideoCallSessionModifyResponseReceived = "TelecomVideoCallSessionModifyResponseReceived"
+EventTelecomVideoCallSessionEvent = "TelecomVideoCallSessionEvent"
+EventTelecomVideoCallPeerDimensionsChanged = "TelecomVideoCallPeerDimensionsChanged"
+EventTelecomVideoCallVideoQualityChanged = "TelecomVideoCallVideoQualityChanged"
+EventTelecomVideoCallDataUsageChanged = "TelecomVideoCallDataUsageChanged"
+EventTelecomVideoCallCameraCapabilities = "TelecomVideoCallCameraCapabilities"
+
+# Constant for Other Event Name
+EventCallStateChanged = "CallStateChanged"
+EventPreciseStateChanged = "PreciseStateChanged"
+EventDataConnectionRealTimeInfoChanged = "DataConnectionRealTimeInfoChanged"
+EventDataConnectionStateChanged = "DataConnectionStateChanged"
+EventServiceStateChanged = "ServiceStateChanged"
+EventSignalStrengthChanged = "SignalStrengthChanged"
+EventVolteServiceStateChanged = "VolteServiceStateChanged"
+EventMessageWaitingIndicatorChanged = "MessageWaitingIndicatorChanged"
+EventConnectivityChanged = "ConnectivityChanged"
+EventActiveDataSubIdChanged = "ActiveDataSubIdChanged"
+EventDisplayInfoChanged = "DisplayInfoChanged"
+
+# Constant for Packet Keep Alive Call Back
+EventPacketKeepaliveCallback = "PacketKeepaliveCallback"
+PacketKeepaliveCallbackStarted = "Started"
+PacketKeepaliveCallbackStopped = "Stopped"
+PacketKeepaliveCallbackError = "Error"
+PacketKeepaliveCallbackInvalid = "Invalid"
+
+# Constant for Network Call Back
+EventNetworkCallback = "NetworkCallback"
+NetworkCallbackPreCheck = "PreCheck"
+NetworkCallbackAvailable = "Available"
+NetworkCallbackLosing = "Losing"
+NetworkCallbackLost = "Lost"
+NetworkCallbackUnavailable = "Unavailable"
+NetworkCallbackCapabilitiesChanged = "CapabilitiesChanged"
+NetworkCallbackSuspended = "Suspended"
+NetworkCallbackResumed = "Resumed"
+NetworkCallbackLinkPropertiesChanged = "LinkPropertiesChanged"
+NetworkCallbackInvalid = "Invalid"
+
+class SignalStrengthContainer:
+    SIGNAL_STRENGTH_GSM = "gsmSignalStrength"
+    SIGNAL_STRENGTH_GSM_DBM = "gsmDbm"
+    SIGNAL_STRENGTH_GSM_LEVEL = "gsmLevel"
+    SIGNAL_STRENGTH_GSM_ASU_LEVEL = "gsmAsuLevel"
+    SIGNAL_STRENGTH_GSM_BIT_ERROR_RATE = "gsmBitErrorRate"
+    SIGNAL_STRENGTH_CDMA_DBM = "cdmaDbm"
+    SIGNAL_STRENGTH_CDMA_LEVEL = "cdmaLevel"
+    SIGNAL_STRENGTH_CDMA_ASU_LEVEL = "cdmaAsuLevel"
+    SIGNAL_STRENGTH_CDMA_ECIO = "cdmaEcio"
+    SIGNAL_STRENGTH_EVDO_DBM = "evdoDbm"
+    SIGNAL_STRENGTH_EVDO_ECIO = "evdoEcio"
+    SIGNAL_STRENGTH_LTE = "lteSignalStrength"
+    SIGNAL_STRENGTH_LTE_DBM = "lteDbm"
+    SIGNAL_STRENGTH_LTE_LEVEL = "lteLevel"
+    SIGNAL_STRENGTH_LTE_ASU_LEVEL = "lteAsuLevel"
+    SIGNAL_STRENGTH_DBM = "dbm"
+    SIGNAL_STRENGTH_LEVEL = "level"
+    SIGNAL_STRENGTH_ASU_LEVEL = "asuLevel"
+
+
+class MessageWaitingIndicatorContainer:
+    IS_MESSAGE_WAITING = "isMessageWaiting"
+
+
+class CallStateContainer:
+    INCOMING_NUMBER = "incomingNumber"
+    SUBSCRIPTION_ID = "subscriptionId"
+    CALL_STATE = "callState"
+
+class DisplayInfoContainer:
+    TIME = "time"
+    NETWORK = "network"
+    OVERRIDE = "override"
+    SUBSCRIPTION_ID = "subscriptionId"
+
+class OverrideNetworkContainer:
+    OVERRIDE_NETWORK_TYPE_NONE = "NONE"
+    OVERRIDE_NETWORK_TYPE_LTE_CA = "LTE_CA"
+    OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = "LTE_ADVANCED_PRO"
+    OVERRIDE_NETWORK_TYPE_NR_NSA = "NR_NSA"
+    OVERRIDE_NETWORK_TYPE_NR_MMWAVE = "NR_MMWAVE"
+
+class PreciseCallStateContainer:
+    TYPE = "type"
+    CAUSE = "cause"
+    SUBSCRIPTION_ID = "subscriptionId"
+    PRECISE_CALL_STATE = "preciseCallState"
+
+
+class DataConnectionRealTimeInfoContainer:
+    TYPE = "type"
+    TIME = "time"
+    SUBSCRIPTION_ID = "subscriptionId"
+    DATA_CONNECTION_POWER_STATE = "dataConnectionPowerState"
+
+
+class DataConnectionStateContainer:
+    TYPE = "type"
+    DATA_NETWORK_TYPE = "dataNetworkType"
+    STATE_CODE = "stateCode"
+    SUBSCRIPTION_ID = "subscriptionId"
+    DATA_CONNECTION_STATE = "dataConnectionState"
+
+
+class ServiceStateContainer:
+    VOICE_REG_STATE = "voiceRegState"
+    VOICE_NETWORK_TYPE = "voiceNetworkType"
+    DATA_REG_STATE = "dataRegState"
+    DATA_NETWORK_TYPE = "dataNetworkType"
+    OPERATOR_NAME = "operatorName"
+    OPERATOR_ID = "operatorId"
+    IS_MANUAL_NW_SELECTION = "isManualNwSelection"
+    ROAMING = "roaming"
+    IS_EMERGENCY_ONLY = "isEmergencyOnly"
+    NETWORK_ID = "networkId"
+    SYSTEM_ID = "systemId"
+    SUBSCRIPTION_ID = "subscriptionId"
+    SERVICE_STATE = "serviceState"
+
+
+class PacketKeepaliveContainer:
+    ID = "id"
+    PACKET_KEEPALIVE_EVENT = "packetKeepaliveEvent"
+
+
+class NetworkCallbackContainer:
+    ID = "id"
+    NETWORK_CALLBACK_EVENT = "networkCallbackEvent"
+    MAX_MS_TO_LIVE = "maxMsToLive"
+    RSSI = "rssi"
+
+
+class CarrierConfigs:
+    NAME_STRING = "carrier_name_string"
+    SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool"
+    VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"
+    VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"
+    VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"
+    VOLTE_OVERRIDE_WFC_BOOL = "carrier_volte_override_wfc_provisioning_bool"
+    VT_AVAILABLE_BOOL = "carrier_vt_available_bool"
+    ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL = "enhanced_4g_lte_on_by_default_bool"
+    HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool"
+    WFC_IMS_AVAILABLE_BOOL = "carrier_wfc_ims_available_bool"
+    WFC_SUPPORTS_WIFI_ONLY_BOOL = "carrier_wfc_supports_wifi_only_bool"
+    EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool"
+    EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool"
+    EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool"
+    DEFAULT_DATA_ROAMING_ENABLED_BOOL = "carrier_default_data_roaming_enabled_bool"
+    DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL = "carrier_default_wfc_ims_roaming_enabled_bool"
+    DEFAULT_WFC_IMS_ENABLED_BOOL = "carrier_default_wfc_ims_enabled_bool"
+    DEFAULT_WFC_IMS_MODE_INT = "carrier_default_wfc_ims_mode_int"
+    DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL = "carrier_default_wfc_ims_roaming_enabled_bool"
+    DEFAULT_WFC_IMS_ROAMING_MODE_INT = "carrier_default_wfc_ims_roaming_mode_int"
+
+
+"""
+End shared constant define for both Python and Java
+"""
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
new file mode 100644
index 0000000..c794fa6
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_lookup_tables.py
@@ -0,0 +1,737 @@
+#!/usr/bin/env python3
+#
+#   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.
+from acts.utils import NexusModelNames
+from acts_contrib.test_utils.tel import tel_defines
+
+
+def rat_family_from_rat(rat_type):
+    return _TelTables.technology_tbl[rat_type]['rat_family']
+
+
+def rat_generation_from_rat(rat_type):
+    return _TelTables.technology_tbl[rat_type]['generation']
+
+
+def network_preference_for_generation(generation, operator, phone_type=None):
+    if not phone_type:
+        return _TelTables.operator_network_tbl[operator][generation][
+            'network_preference']
+    else:
+        return _TelTables.operator_network_tbl_by_phone_type[phone_type][
+            generation]['network_preference']
+
+
+def rat_families_for_network_preference(network_preference):
+    return _TelTables.network_preference_tbl[network_preference][
+        'rat_family_list']
+
+
+def rat_family_for_generation(generation, operator, phone_type=None):
+    if not phone_type:
+        return _TelTables.operator_network_tbl[operator][generation][
+            'rat_family']
+    else:
+        return _TelTables.operator_network_tbl_by_phone_type[phone_type][
+            generation]['rat_family']
+
+
+def operator_name_from_plmn_id(plmn_id):
+    return _TelTables.operator_id_to_name[plmn_id]
+
+
+def operator_name_from_network_name(name):
+    return _TelTables.operator_name_tbl.get("name", name)
+
+
+def is_valid_rat(rat_type):
+    return True if rat_type in _TelTables.technology_tbl else False
+
+
+def is_valid_generation(gen):
+    return True if gen in _TelTables.technology_gen_tbl else False
+
+
+def is_rat_svd_capable(rat):
+    return _TelTables.technology_tbl[rat]["simultaneous_voice_data"]
+
+
+def connection_type_from_type_string(input_string):
+    if input_string in _ConnectionTables.connection_type_tbl:
+        return _ConnectionTables.connection_type_tbl[input_string]
+    return tel_defines.NETWORK_CONNECTION_TYPE_UNKNOWN
+
+
+def is_user_plane_data_type(connection_type):
+    if connection_type in _ConnectionTables.user_plane_data_type:
+        return _ConnectionTables.user_plane_data_type[connection_type]
+    return False
+
+
+# For TMO, to check if voice mail count is correct after leaving a new voice message.
+def check_tmo_voice_mail_count(voice_mail_count_before,
+                               voice_mail_count_after):
+    return (voice_mail_count_after == -1)
+
+
+# For ATT, to check if voice mail count is correct after leaving a new voice message.
+def check_att_voice_mail_count(voice_mail_count_before,
+                               voice_mail_count_after):
+    return (voice_mail_count_after == (voice_mail_count_before + 1))
+
+
+# For SPT, to check if voice mail count is correct after leaving a new voice message.
+def check_spt_voice_mail_count(voice_mail_count_before,
+                               voice_mail_count_after):
+    return (voice_mail_count_after == (voice_mail_count_before + 1))
+
+
+def get_voice_mail_check_number(operator):
+    return _TelTables.voice_mail_number_tbl.get(operator)
+
+
+def get_voice_mail_count_check_function(operator):
+    return _TelTables.voice_mail_count_check_function_tbl.get(
+        operator, check_tmo_voice_mail_count)
+
+
+def get_voice_mail_delete_digit(operator):
+    return _TelTables.voice_mail_delete_digit_tbl.get(operator, "7")
+
+
+def get_allowable_network_preference(operator, phone_type=None):
+    if not phone_type:
+        return _TelTables.allowable_network_preference_tbl[operator]
+    else:
+        return _TelTables.allowable_network_preference_tbl_by_phone_type[
+            phone_type]
+
+
+class _ConnectionTables():
+    connection_type_tbl = {
+        'WIFI': tel_defines.NETWORK_CONNECTION_TYPE_WIFI,
+        'WIFI_P2P': tel_defines.NETWORK_CONNECTION_TYPE_WIFI,
+        'MOBILE': tel_defines.NETWORK_CONNECTION_TYPE_CELL,
+        'MOBILE_DUN': tel_defines.NETWORK_CONNECTION_TYPE_CELL,
+        'MOBILE_HIPRI': tel_defines.NETWORK_CONNECTION_TYPE_HIPRI,
+        # TODO: b/26296489 add support for 'MOBILE_SUPL', 'MOBILE_HIPRI',
+        # 'MOBILE_FOTA', 'MOBILE_IMS', 'MOBILE_CBS', 'MOBILE_IA',
+        # 'MOBILE_EMERGENCY'
+        'MOBILE_MMS': tel_defines.NETWORK_CONNECTION_TYPE_MMS
+    }
+
+    user_plane_data_type = {
+        tel_defines.NETWORK_CONNECTION_TYPE_WIFI: True,
+        tel_defines.NETWORK_CONNECTION_TYPE_CELL: False,
+        tel_defines.NETWORK_CONNECTION_TYPE_MMS: False,
+        tel_defines.NETWORK_CONNECTION_TYPE_UNKNOWN: False
+    }
+
+
+class _TelTables():
+    # Operator id mapping to operator name
+    # Reference: Pages 43-50 in
+    # https://www.itu.int/dms_pub/itu-t/opb/sp/T-SP-E.212B-2013-PDF-E.pdf [2013]
+
+    operator_name_tbl = {
+        "T-Mobile": tel_defines.CARRIER_TMO,
+        "AT&T": tel_defines.CARRIER_ATT,
+        "Verizon": tel_defines.CARRIER_VZW,
+        "Verizon Wireless": tel_defines.CARRIER_VZW,
+        "Sprint": tel_defines.CARRIER_SPT,
+        "ROGERS": tel_defines.CARRIER_ROGERS,
+        "Videotron PRTNR1": tel_defines.CARRIER_VIDEOTRON,
+        "Bell": tel_defines.CARRIER_BELL,
+        "Koodo": tel_defines.CARRIER_KOODO,
+        "Ntt Docomo" : tel_defines.CARRIER_NTT_DOCOMO,
+        "KDDI" : tel_defines.CARRIER_KDDI,
+        "Rakuten": tel_defines.CARRIER_RAKUTEN,
+        "SBM": tel_defines.CARRIER_SBM
+    }
+    operator_id_to_name = {
+
+        #VZW (Verizon Wireless)
+        '310010': tel_defines.CARRIER_VZW,
+        '310012': tel_defines.CARRIER_VZW,
+        '310013': tel_defines.CARRIER_VZW,
+        '310590': tel_defines.CARRIER_VZW,
+        '310890': tel_defines.CARRIER_VZW,
+        '310910': tel_defines.CARRIER_VZW,
+        '310110': tel_defines.CARRIER_VZW,
+        '311270': tel_defines.CARRIER_VZW,
+        '311271': tel_defines.CARRIER_VZW,
+        '311272': tel_defines.CARRIER_VZW,
+        '311273': tel_defines.CARRIER_VZW,
+        '311274': tel_defines.CARRIER_VZW,
+        '311275': tel_defines.CARRIER_VZW,
+        '311276': tel_defines.CARRIER_VZW,
+        '311277': tel_defines.CARRIER_VZW,
+        '311278': tel_defines.CARRIER_VZW,
+        '311279': tel_defines.CARRIER_VZW,
+        '311280': tel_defines.CARRIER_VZW,
+        '311281': tel_defines.CARRIER_VZW,
+        '311282': tel_defines.CARRIER_VZW,
+        '311283': tel_defines.CARRIER_VZW,
+        '311284': tel_defines.CARRIER_VZW,
+        '311285': tel_defines.CARRIER_VZW,
+        '311286': tel_defines.CARRIER_VZW,
+        '311287': tel_defines.CARRIER_VZW,
+        '311288': tel_defines.CARRIER_VZW,
+        '311289': tel_defines.CARRIER_VZW,
+        '311390': tel_defines.CARRIER_VZW,
+        '311480': tel_defines.CARRIER_VZW,
+        '311481': tel_defines.CARRIER_VZW,
+        '311482': tel_defines.CARRIER_VZW,
+        '311483': tel_defines.CARRIER_VZW,
+        '311484': tel_defines.CARRIER_VZW,
+        '311485': tel_defines.CARRIER_VZW,
+        '311486': tel_defines.CARRIER_VZW,
+        '311487': tel_defines.CARRIER_VZW,
+        '311488': tel_defines.CARRIER_VZW,
+        '311489': tel_defines.CARRIER_VZW,
+
+        #TMO (T-Mobile USA)
+        '310160': tel_defines.CARRIER_TMO,
+        '310200': tel_defines.CARRIER_TMO,
+        '310210': tel_defines.CARRIER_TMO,
+        '310220': tel_defines.CARRIER_TMO,
+        '310230': tel_defines.CARRIER_TMO,
+        '310240': tel_defines.CARRIER_TMO,
+        '310250': tel_defines.CARRIER_TMO,
+        '310260': tel_defines.CARRIER_TMO,
+        '310270': tel_defines.CARRIER_TMO,
+        '310310': tel_defines.CARRIER_TMO,
+        '310490': tel_defines.CARRIER_TMO,
+        '310660': tel_defines.CARRIER_TMO,
+        '310800': tel_defines.CARRIER_TMO,
+
+        #ATT (AT&T and Cingular)
+        '310070': tel_defines.CARRIER_ATT,
+        '310560': tel_defines.CARRIER_ATT,
+        '310670': tel_defines.CARRIER_ATT,
+        '310680': tel_defines.CARRIER_ATT,
+        '310150': tel_defines.CARRIER_ATT,  #Cingular
+        '310170': tel_defines.CARRIER_ATT,  #Cingular
+        '310410': tel_defines.CARRIER_ATT,  #Cingular
+        '311180': tel_defines.CARRIER_ATT,
+        #Cingular Licensee Pacific Telesis Mobile Services, LLC
+
+        #Sprint (and Sprint-Nextel)
+        '310120': tel_defines.CARRIER_SPT,
+        '311490': tel_defines.CARRIER_SPT,
+        '311870': tel_defines.CARRIER_SPT,
+        '311880': tel_defines.CARRIER_SPT,
+        '312190': tel_defines.CARRIER_SPT,  #Sprint-Nextel Communications Inc
+        '316010': tel_defines.CARRIER_SPT,  #Sprint-Nextel Communications Inc
+        '23433': tel_defines.CARRIER_EEUK,  #Orange
+        '23434': tel_defines.CARRIER_EEUK,  #Orange
+        '23430': tel_defines.CARRIER_EEUK,  #T-Mobile UK
+        '23431': tel_defines.CARRIER_EEUK,  #Virgin Mobile (MVNO)
+        '23432': tel_defines.CARRIER_EEUK,  #Virgin Mobile (MVNO)
+        '23415': tel_defines.CARRIER_VFUK,
+
+        # Google Fi
+        '312580': tel_defines.CARRIER_FI,
+
+        #USCC
+        '311580': tel_defines.CARRIER_USCC,
+
+        #Vodafone (Germany)
+        '26202': tel_defines.CARRIER_GMBH,
+        '26204': tel_defines.CARRIER_GMBH,
+        '26209': tel_defines.CARRIER_GMBH,
+        '26242': tel_defines.CARRIER_GMBH,
+        '26243': tel_defines.CARRIER_GMBH,
+
+        #Vodafone (Italy)
+        '22206': tel_defines.CARRIER_ITA,
+        '22210': tel_defines.CARRIER_ITA,
+
+        #Vodafone (Spain)
+        '21401': tel_defines.CARRIER_ESP,
+        '20406': tel_defines.CARRIER_ESP,
+
+        #Orange (France)
+        '20801': tel_defines.CARRIER_ORG,
+        '20802': tel_defines.CARRIER_ORG,
+        '20891': tel_defines.CARRIER_ORG,
+
+        #Telenor (Norway)
+        '24201': tel_defines.CARRIER_TEL,
+        '24212': tel_defines.CARRIER_TEL,
+
+        #Canada Freedom
+        '302490': tel_defines.CARRIER_FRE,
+
+        #Telstra (Australia)
+        '52501': tel_defines.CARRIER_SING,
+        '50501': tel_defines.CARRIER_TSA
+    }
+
+    technology_gen_tbl = [
+        tel_defines.GEN_2G, tel_defines.GEN_3G, tel_defines.GEN_4G
+    ]
+
+    technology_tbl = {
+        tel_defines.RAT_1XRTT: {
+            'is_voice_rat': True,
+            'is_data_rat': False,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_CDMA2000
+        },
+        tel_defines.RAT_EDGE: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_2G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_GSM
+        },
+        tel_defines.RAT_GPRS: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_2G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_GSM
+        },
+        tel_defines.RAT_GSM: {
+            'is_voice_rat': True,
+            'is_data_rat': False,
+            'generation': tel_defines.GEN_2G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_GSM
+        },
+        tel_defines.RAT_UMTS: {
+            'is_voice_rat': True,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': True,
+            'rat_family': tel_defines.RAT_FAMILY_WCDMA
+        },
+        tel_defines.RAT_WCDMA: {
+            'is_voice_rat': True,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': True,
+            'rat_family': tel_defines.RAT_FAMILY_WCDMA
+        },
+        tel_defines.RAT_HSDPA: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_WCDMA
+        },
+        tel_defines.RAT_HSUPA: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_WCDMA
+        },
+        tel_defines.RAT_CDMA: {
+            'is_voice_rat': True,
+            'is_data_rat': False,
+            'generation': tel_defines.GEN_2G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_CDMA
+        },
+        tel_defines.RAT_EVDO: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_CDMA2000
+        },
+        tel_defines.RAT_EVDO_0: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_CDMA2000
+        },
+        tel_defines.RAT_EVDO_A: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_CDMA2000
+        },
+        tel_defines.RAT_EVDO_B: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_CDMA2000
+        },
+        tel_defines.RAT_IDEN: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_2G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_IDEN
+        },
+        tel_defines.RAT_LTE_CA: {
+            'is_voice_rat': True,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_4G,
+            'simultaneous_voice_data': True,
+            'rat_family': tel_defines.RAT_FAMILY_LTE
+        },
+        tel_defines.RAT_LTE: {
+            'is_voice_rat': True,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_4G,
+            'simultaneous_voice_data': True,
+            'rat_family': tel_defines.RAT_FAMILY_LTE
+        },
+        tel_defines.RAT_NR: {
+            'is_voice_rat': True,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_5G,
+            'simultaneous_voice_data': True,
+            'rat_family': tel_defines.RAT_FAMILY_NR
+        },
+        tel_defines.RAT_EHRPD: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_CDMA2000
+        },
+        tel_defines.RAT_HSPA: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': True,
+            'rat_family': tel_defines.RAT_FAMILY_WCDMA
+        },
+        tel_defines.RAT_HSPAP: {
+            'is_voice_rat': False,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': True,
+            'rat_family': tel_defines.RAT_FAMILY_WCDMA
+        },
+        tel_defines.RAT_IWLAN: {
+            'is_voice_rat': True,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_4G,
+            'simultaneous_voice_data': True,
+            'rat_family': tel_defines.RAT_FAMILY_WLAN
+        },
+        tel_defines.RAT_TD_SCDMA: {
+            'is_voice_rat': True,
+            'is_data_rat': True,
+            'generation': tel_defines.GEN_3G,
+            'simultaneous_voice_data': True,
+            'rat_family': tel_defines.RAT_FAMILY_TDSCDMA
+        },
+        tel_defines.RAT_UNKNOWN: {
+            'is_voice_rat': False,
+            'is_data_rat': False,
+            'generation': tel_defines.GEN_UNKNOWN,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_UNKNOWN
+        },
+        tel_defines.RAT_GLOBAL: {
+            'is_voice_rat': False,
+            'is_data_rat': False,
+            'generation': tel_defines.GEN_UNKNOWN,
+            'simultaneous_voice_data': False,
+            'rat_family': tel_defines.RAT_FAMILY_UNKNOWN
+        }
+    }
+
+    network_preference_tbl = {
+        tel_defines.NETWORK_MODE_LTE_GSM_WCDMA: {
+            'rat_family_list': [
+                tel_defines.RAT_FAMILY_LTE, tel_defines.RAT_FAMILY_WCDMA,
+                tel_defines.RAT_FAMILY_GSM
+            ]
+        },
+        tel_defines.NETWORK_MODE_GSM_UMTS: {
+            'rat_family_list':
+            [tel_defines.RAT_FAMILY_WCDMA, tel_defines.RAT_FAMILY_GSM]
+        },
+        tel_defines.NETWORK_MODE_GSM_ONLY: {
+            'rat_family_list': [tel_defines.RAT_FAMILY_GSM]
+        },
+        tel_defines.NETWORK_MODE_LTE_CDMA_EVDO: {
+            'rat_family_list': [
+                tel_defines.RAT_FAMILY_LTE, tel_defines.RAT_FAMILY_CDMA2000,
+                tel_defines.RAT_FAMILY_CDMA
+            ]
+        },
+        tel_defines.NETWORK_MODE_CDMA: {
+            'rat_family_list':
+            [tel_defines.RAT_FAMILY_CDMA2000, tel_defines.RAT_FAMILY_CDMA]
+        },
+        tel_defines.NETWORK_MODE_CDMA_NO_EVDO: {
+            'rat_family_list':
+            [tel_defines.RAT_FAMILY_CDMA2000, tel_defines.RAT_FAMILY_CDMA]
+        },
+        tel_defines.NETWORK_MODE_WCDMA_PREF: {
+            'rat_family_list':
+            [tel_defines.RAT_FAMILY_WCDMA, tel_defines.RAT_FAMILY_GSM]
+        },
+        tel_defines.NETWORK_MODE_WCDMA_ONLY: {
+            'rat_family_list': [tel_defines.RAT_FAMILY_WCDMA]
+        },
+        tel_defines.NETWORK_MODE_EVDO_NO_CDMA: {
+            'rat_family_list': [tel_defines.RAT_FAMILY_CDMA2000]
+        },
+        tel_defines.NETWORK_MODE_GLOBAL: {
+            'rat_family_list': [
+                tel_defines.RAT_FAMILY_LTE, tel_defines.RAT_FAMILY_TDSCDMA,
+                tel_defines.RAT_FAMILY_WCDMA, tel_defines.RAT_FAMILY_GSM,
+                tel_defines.RAT_FAMILY_CDMA2000, tel_defines.RAT_FAMILY_CDMA
+            ]
+        },
+        tel_defines.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA: {
+            'rat_family_list': [
+                tel_defines.RAT_FAMILY_LTE, tel_defines.RAT_FAMILY_WCDMA,
+                tel_defines.RAT_FAMILY_GSM, tel_defines.RAT_FAMILY_CDMA2000,
+                tel_defines.RAT_FAMILY_CDMA
+            ]
+        },
+        tel_defines.NETWORK_MODE_LTE_ONLY: {
+            'rat_family_list': [tel_defines.RAT_FAMILY_LTE]
+        },
+        tel_defines.NETWORK_MODE_LTE_WCDMA: {
+            'rat_family_list':
+            [tel_defines.RAT_FAMILY_LTE, tel_defines.RAT_FAMILY_WCDMA]
+        },
+        tel_defines.NETWORK_MODE_TDSCDMA_ONLY: {
+            'rat_family_list': [tel_defines.RAT_FAMILY_TDSCDMA]
+        },
+        tel_defines.NETWORK_MODE_TDSCDMA_WCDMA: {
+            'rat_family_list':
+            [tel_defines.RAT_FAMILY_TDSCDMA, tel_defines.RAT_FAMILY_WCDMA]
+        },
+        tel_defines.NETWORK_MODE_LTE_TDSCDMA: {
+            'rat_family_list':
+            [tel_defines.RAT_FAMILY_LTE, tel_defines.RAT_FAMILY_TDSCDMA]
+        },
+        tel_defines.NETWORK_MODE_TDSCDMA_GSM: {
+            'rat_family_list':
+            [tel_defines.RAT_FAMILY_TDSCDMA, tel_defines.RAT_FAMILY_GSM]
+        },
+        tel_defines.NETWORK_MODE_LTE_TDSCDMA_GSM: {
+            'rat_family_list': [
+                tel_defines.RAT_FAMILY_LTE, tel_defines.RAT_FAMILY_TDSCDMA,
+                tel_defines.RAT_FAMILY_GSM
+            ]
+        },
+        tel_defines.NETWORK_MODE_TDSCDMA_GSM_WCDMA: {
+            'rat_family_list': [
+                tel_defines.RAT_FAMILY_WCDMA, tel_defines.RAT_FAMILY_TDSCDMA,
+                tel_defines.RAT_FAMILY_GSM
+            ]
+        },
+        tel_defines.NETWORK_MODE_LTE_TDSCDMA_WCDMA: {
+            'rat_family_list': [
+                tel_defines.RAT_FAMILY_WCDMA, tel_defines.RAT_FAMILY_TDSCDMA,
+                tel_defines.RAT_FAMILY_LTE
+            ]
+        },
+        tel_defines.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA: {
+            'rat_family_list': [
+                tel_defines.RAT_FAMILY_WCDMA, tel_defines.RAT_FAMILY_TDSCDMA,
+                tel_defines.RAT_FAMILY_LTE, tel_defines.RAT_FAMILY_GSM
+            ]
+        },
+        tel_defines.NETWORK_MODE_TDSCDMA_CDMA_EVDO_WCDMA: {
+            'rat_family_list': [
+                tel_defines.RAT_FAMILY_WCDMA, tel_defines.RAT_FAMILY_TDSCDMA,
+                tel_defines.RAT_FAMILY_CDMA2000, tel_defines.RAT_FAMILY_CDMA
+            ]
+        },
+        tel_defines.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA: {
+            'rat_family_list': [
+                tel_defines.RAT_FAMILY_WCDMA, tel_defines.RAT_FAMILY_TDSCDMA,
+                tel_defines.RAT_FAMILY_LTE, tel_defines.RAT_FAMILY_GSM,
+                tel_defines.RAT_FAMILY_CDMA2000, tel_defines.RAT_FAMILY_CDMA
+            ]
+        }
+    }
+    default_umts_operator_network_tbl = {
+        tel_defines.GEN_5G: {
+            'rat_family': tel_defines.RAT_FAMILY_NR,
+            'network_preference': tel_defines.NETWORK_MODE_NR_LTE_GSM_WCDMA
+        },
+        tel_defines.GEN_4G: {
+            'rat_family': tel_defines.RAT_FAMILY_LTE,
+            'network_preference':
+            tel_defines.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA
+        },
+        tel_defines.GEN_3G: {
+            'rat_family': tel_defines.RAT_FAMILY_WCDMA,
+            'network_preference': tel_defines.NETWORK_MODE_WCDMA_ONLY
+        },
+        tel_defines.GEN_2G: {
+            'rat_family': tel_defines.RAT_FAMILY_GSM,
+            'network_preference': tel_defines.NETWORK_MODE_GSM_ONLY
+        }
+    }
+    default_cdma_operator_network_tbl = {
+        tel_defines.GEN_5G: {
+            'rat_family': tel_defines.RAT_FAMILY_NR,
+            'network_preference': tel_defines.NETWORK_MODE_NR_LTE_GSM_WCDMA
+        },
+        tel_defines.GEN_4G: {
+            'rat_family': tel_defines.RAT_FAMILY_LTE,
+            'network_preference':
+            tel_defines.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA
+        },
+        tel_defines.GEN_3G: {
+            'rat_family': tel_defines.RAT_FAMILY_CDMA2000,
+            'network_preference': tel_defines.NETWORK_MODE_CDMA
+        },
+        tel_defines.GEN_2G: {
+            'rat_family': tel_defines.RAT_FAMILY_CDMA2000,
+            'network_preference': tel_defines.NETWORK_MODE_CDMA_NO_EVDO
+        }
+    }
+    operator_network_tbl = {
+        tel_defines.CARRIER_TMO: default_umts_operator_network_tbl,
+        tel_defines.CARRIER_ATT: default_umts_operator_network_tbl,
+        tel_defines.CARRIER_VZW: default_cdma_operator_network_tbl,
+        tel_defines.CARRIER_SPT: default_cdma_operator_network_tbl,
+        tel_defines.CARRIER_EEUK: default_umts_operator_network_tbl,
+        tel_defines.CARRIER_VFUK: default_umts_operator_network_tbl,
+        tel_defines.CARRIER_GMBH: default_umts_operator_network_tbl,
+        tel_defines.CARRIER_ITA: default_umts_operator_network_tbl,
+        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
+    }
+    operator_network_tbl_by_phone_type = {
+        tel_defines.PHONE_TYPE_GSM: default_umts_operator_network_tbl,
+        tel_defines.PHONE_TYPE_CDMA: default_cdma_operator_network_tbl
+    }
+
+    umts_allowable_network_preference_tbl = \
+        [tel_defines.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA,
+         tel_defines.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA,
+         tel_defines.NETWORK_MODE_LTE_GSM_WCDMA,
+         tel_defines.NETWORK_MODE_WCDMA_PREF,
+         tel_defines.NETWORK_MODE_WCDMA_ONLY,
+         tel_defines.NETWORK_MODE_GSM_ONLY]
+
+    cdma_allowable_network_preference_tbl = \
+        [tel_defines.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA,
+         tel_defines.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA,
+         tel_defines.NETWORK_MODE_LTE_CDMA_EVDO,
+         tel_defines.NETWORK_MODE_CDMA,
+         tel_defines.NETWORK_MODE_CDMA_NO_EVDO,
+         tel_defines.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA]
+
+    allowable_network_preference_tbl = {
+        tel_defines.CARRIER_TMO: umts_allowable_network_preference_tbl,
+        tel_defines.CARRIER_ATT: umts_allowable_network_preference_tbl,
+        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
+    }
+    allowable_network_preference_tbl_by_phone_type = {
+        tel_defines.PHONE_TYPE_GSM: umts_allowable_network_preference_tbl,
+        tel_defines.PHONE_TYPE_CDMA: cdma_allowable_network_preference_tbl
+    }
+
+    voice_mail_number_tbl = {
+        tel_defines.CARRIER_TMO: "123",
+        tel_defines.CARRIER_VZW: "*86",
+        tel_defines.CARRIER_ATT: None,
+        tel_defines.CARRIER_SPT: None,
+        tel_defines.CARRIER_EEUK: "+447953222222",
+        tel_defines.CARRIER_NTT_DOCOMO: "1417",
+        tel_defines.CARRIER_KDDI: "1417",
+        tel_defines.CARRIER_RAKUTEN: "1417",
+        tel_defines.CARRIER_SBM: "1416"
+    }
+
+    voice_mail_count_check_function_tbl = {
+        tel_defines.CARRIER_TMO: check_tmo_voice_mail_count,
+        tel_defines.CARRIER_ATT: check_att_voice_mail_count,
+        tel_defines.CARRIER_SPT: check_spt_voice_mail_count
+    }
+
+    voice_mail_delete_digit_tbl = {
+        tel_defines.CARRIER_EEUK: "3",
+        tel_defines.CARRIER_NTT_DOCOMO: "3",
+        tel_defines.CARRIER_KDDI: "9"
+    }
+
+
+device_capabilities = {
+    NexusModelNames.ONE:
+    [tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_MSIM],
+    NexusModelNames.N5: [tel_defines.CAPABILITY_PHONE],
+    NexusModelNames.N5v2: [
+        tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_OMADM,
+        tel_defines.CAPABILITY_VOLTE, tel_defines.CAPABILITY_WFC
+    ],
+    NexusModelNames.N6: [
+        tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_OMADM,
+        tel_defines.CAPABILITY_VOLTE, tel_defines.CAPABILITY_WFC
+    ],
+    NexusModelNames.N6v2: [
+        tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_OMADM,
+        tel_defines.CAPABILITY_VOLTE, tel_defines.CAPABILITY_WFC
+    ],
+    NexusModelNames.N5v3: [
+        tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_OMADM,
+        tel_defines.CAPABILITY_VOLTE, tel_defines.CAPABILITY_WFC,
+        tel_defines.CAPABILITY_VT
+    ],
+    NexusModelNames.N6v3: [
+        tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_OMADM,
+        tel_defines.CAPABILITY_VOLTE, tel_defines.CAPABILITY_WFC,
+        tel_defines.CAPABILITY_VT
+    ],
+    "default": [
+        tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_OMADM,
+        tel_defines.CAPABILITY_VOLTE, tel_defines.CAPABILITY_WFC,
+        tel_defines.CAPABILITY_VT
+    ]
+}
+
+operator_capabilities = {
+    tel_defines.CARRIER_VZW: [
+        tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_OMADM,
+        tel_defines.CAPABILITY_VOLTE, tel_defines.CAPABILITY_WFC,
+        tel_defines.CAPABILITY_VT
+    ],
+    tel_defines.CARRIER_ATT:
+    [tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_VOLTE],
+    tel_defines.CARRIER_TMO: [
+        tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_VOLTE,
+        tel_defines.CAPABILITY_WFC, tel_defines.CAPABILITY_VT
+    ],
+    tel_defines.CARRIER_SPT: [tel_defines.CAPABILITY_PHONE],
+    tel_defines.CARRIER_ROGERS: [
+        tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_VOLTE,
+        tel_defines.CAPABILITY_WFC
+    ],
+    tel_defines.CARRIER_EEUK: [
+        tel_defines.CAPABILITY_PHONE, tel_defines.CAPABILITY_VOLTE,
+        tel_defines.CAPABILITY_WFC
+    ],
+    tel_defines.CARRIER_VFUK: [tel_defines.CAPABILITY_PHONE],
+    "default": [tel_defines.CAPABILITY_PHONE]
+}
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
new file mode 100644
index 0000000..69d9e11
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_subscription_utils.py
@@ -0,0 +1,517 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+# 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 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 time
+
+
+def initial_set_up_for_subid_infomation(log, ad):
+    """Initial subid setup for voice, message and data according to ad's
+    attribute.
+
+    Setup sub id properties for android device. Including the followings:
+        incoming_voice_sub_id
+        incoming_message_sub_id
+        outgoing_voice_sub_id
+        outgoing_message_sub_id
+        default_data_sub_id
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        None
+    """
+    # outgoing_voice_sub_id
+    # If default_voice_sim_slot_index is set in config file, then use sub_id
+    # of this SIM as default_outgoing_sub_id. If default_voice_sim_slot_index
+    # is not set, then use default voice sub_id as default_outgoing_sub_id.
+    # Outgoing voice call will be made on default_outgoing_sub_id by default.
+    if hasattr(ad, "default_voice_sim_slot_index"):
+        outgoing_voice_sub_id = get_subid_from_slot_index(
+            log, ad, ad.default_voice_sim_slot_index)
+        set_subid_for_outgoing_call(ad, outgoing_voice_sub_id)
+    else:
+        outgoing_voice_sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
+    setattr(ad, "outgoing_voice_sub_id", outgoing_voice_sub_id)
+
+    # outgoing_message_sub_id
+    # If default_message_sim_slot_index is set in config file, then use sub_id
+    # of this SIM as outgoing_message_sub_id. If default_message_sim_slot_index
+    # is not set, then use default Sms sub_id as outgoing_message_sub_id.
+    # Outgoing SMS will be sent on outgoing_message_sub_id by default.
+    if hasattr(ad, "default_message_sim_slot_index"):
+        outgoing_message_sub_id = get_subid_from_slot_index(
+            log, ad, ad.default_message_sim_slot_index)
+        set_subid_for_message(ad, outgoing_message_sub_id)
+    else:
+        outgoing_message_sub_id = ad.droid.subscriptionGetDefaultSmsSubId()
+    setattr(ad, "outgoing_message_sub_id", outgoing_message_sub_id)
+
+    # default_data_sub_id
+    # If default_data_sim_slot_index is set in config file, then use sub_id
+    # of this SIM as default_data_sub_id. If default_data_sim_slot_index
+    # is not set, then use default Data sub_id as default_data_sub_id.
+    # Data connection will be established on default_data_sub_id by default.
+    if hasattr(ad, "default_data_sim_slot_index"):
+        default_data_sub_id = get_subid_from_slot_index(
+            log, ad, ad.default_data_sim_slot_index)
+        set_subid_for_data(ad, default_data_sub_id, 0)
+    else:
+        default_data_sub_id = ad.droid.subscriptionGetDefaultDataSubId()
+    setattr(ad, "default_data_sub_id", default_data_sub_id)
+
+    # This is for Incoming Voice Sub ID
+    # If "incoming_voice_sim_slot_index" is set in config file, then
+    # incoming voice call will call to the phone number of the SIM in
+    # "incoming_voice_sim_slot_index".
+    # If "incoming_voice_sim_slot_index" is NOT set in config file,
+    # then incoming voice call will call to the phone number of default
+    # subId.
+    if hasattr(ad, "incoming_voice_sim_slot_index"):
+        incoming_voice_sub_id = get_subid_from_slot_index(
+            log, ad, ad.incoming_voice_sim_slot_index)
+    else:
+        incoming_voice_sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
+    setattr(ad, "incoming_voice_sub_id", incoming_voice_sub_id)
+
+    # This is for Incoming SMS Sub ID
+    # If "incoming_message_sim_slot_index" is set in config file, then
+    # incoming SMS be sent to the phone number of the SIM in
+    # "incoming_message_sim_slot_index".
+    # If "incoming_message_sim_slot_index" is NOT set in config file,
+    # then incoming SMS be sent to the phone number of default
+    # subId.
+    if hasattr(ad, "incoming_message_sim_slot_index"):
+        incoming_message_sub_id = get_subid_from_slot_index(
+            log, ad, ad.incoming_message_sim_slot_index)
+    else:
+        incoming_message_sub_id = ad.droid.subscriptionGetDefaultSmsSubId()
+    setattr(ad, "incoming_message_sub_id", incoming_message_sub_id)
+
+
+def get_default_data_sub_id(ad):
+    """ Get default data subscription id
+    """
+    if hasattr(ad, "default_data_sub_id"):
+        return ad.default_data_sub_id
+    else:
+        return ad.droid.subscriptionGetDefaultDataSubId()
+
+
+def get_outgoing_message_sub_id(ad):
+    """ Get outgoing message subscription id
+    """
+    if hasattr(ad, "outgoing_message_sub_id"):
+        return ad.outgoing_message_sub_id
+    else:
+        return ad.droid.subscriptionGetDefaultSmsSubId()
+
+
+def get_outgoing_voice_sub_id(ad):
+    """ Get outgoing voice subscription id
+    """
+    if hasattr(ad, "outgoing_voice_sub_id"):
+        return ad.outgoing_voice_sub_id
+    else:
+        return ad.droid.subscriptionGetDefaultVoiceSubId()
+
+
+def get_incoming_voice_sub_id(ad):
+    """ Get incoming voice subscription id
+    """
+    if hasattr(ad, "incoming_voice_sub_id"):
+        return ad.incoming_voice_sub_id
+    else:
+        return ad.droid.subscriptionGetDefaultVoiceSubId()
+
+
+def get_incoming_message_sub_id(ad):
+    """ Get incoming message subscription id
+    """
+    if hasattr(ad, "incoming_message_sub_id"):
+        return ad.incoming_message_sub_id
+    else:
+        return ad.droid.subscriptionGetDefaultSmsSubId()
+
+
+def get_subid_from_slot_index(log, ad, sim_slot_index):
+    """ Get the subscription ID for a SIM at a particular slot
+
+    Args:
+        ad: android_device object.
+
+    Returns:
+        result: Subscription ID
+    """
+    subInfo = ad.droid.subscriptionGetAllSubInfoList()
+    for info in subInfo:
+        if info['simSlotIndex'] == sim_slot_index:
+            return info['subscriptionId']
+    return INVALID_SUB_ID
+
+
+def get_operatorname_from_slot_index(ad, sim_slot_index):
+    """ Get the operator name for a SIM at a particular slot
+
+    Args:
+        ad: android_device object.
+
+    Returns:
+        result: Operator Name
+    """
+    subInfo = ad.droid.subscriptionGetAllSubInfoList()
+    for info in subInfo:
+        if info['simSlotIndex'] == sim_slot_index:
+            return info['displayName']
+    return None
+
+
+def get_carrierid_from_slot_index(ad, sim_slot_index):
+    """ Get the carrierId for a SIM at a particular slot
+
+    Args:
+        ad: android_device object.
+        sim_slot_index: slot 0 or slot 1
+
+    Returns:
+        result: CarrierId
+    """
+    subInfo = ad.droid.subscriptionGetAllSubInfoList()
+    for info in subInfo:
+        if info['simSlotIndex'] == sim_slot_index:
+            return info['carrierId']
+    return None
+
+def get_isopportunistic_from_slot_index(ad, sim_slot_index):
+    """ Get the isOppotunistic field for a particular slot
+
+    Args:
+        ad: android_device object.
+        sim_slot_index: slot 0 or slot 1
+
+    Returns:
+        result: True or False based on Value set
+    """
+    subInfo = ad.droid.subscriptionGetAllSubInfoList()
+    for info in subInfo:
+        if info['simSlotIndex'] == sim_slot_index:
+            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
+
+    Args:
+        ad: android device object.
+        sub_id: subscription id (integer)
+
+    Returns:
+        None
+    """
+    # TODO: Need to check onSubscriptionChanged event. b/27843365
+    if ad.droid.subscriptionGetDefaultDataSubId() != sub_id:
+        ad.droid.subscriptionSetDefaultDataSubId(sub_id)
+        time.sleep(time_to_sleep)
+        setattr(ad, "default_data_sub_id", sub_id)
+
+
+def set_subid_for_message(ad, sub_id):
+    """Set subId for outgoing message
+
+    Args:
+        ad: android device object.
+        sub_id: subscription id (integer)
+
+    Returns:
+        None
+    """
+    ad.droid.subscriptionSetDefaultSmsSubId(sub_id)
+    if hasattr(ad, "outgoing_message_sub_id"):
+        ad.outgoing_message_sub_id = sub_id
+
+def set_message_subid(ad, sub_id):
+    """Set subId for both outgoing and incoming messages
+
+    Args:
+        ad: android device object.
+        sub_id: subscription id (integer)
+
+    Returns:
+        None
+    """
+    ad.droid.subscriptionSetDefaultSmsSubId(sub_id)
+    if hasattr(ad, "outgoing_message_sub_id"):
+        ad.outgoing_message_sub_id = sub_id
+    if hasattr(ad, "incoming_message_sub_id"):
+        ad.incoming_message_sub_id = sub_id
+
+
+def set_subid_for_outgoing_call(ad, sub_id):
+    """Set subId for outgoing voice call
+
+    Args:
+        ad: android device object.
+        sub_id: subscription id (integer)
+
+    Returns:
+        None
+    """
+    ad.droid.telecomSetUserSelectedOutgoingPhoneAccountBySubId(sub_id)
+    if hasattr(ad, "outgoing_voice_sub_id"):
+        ad.outgoing_voice_sub_id = sub_id
+
+
+def set_incoming_voice_sub_id(ad, sub_id):
+    """Set default subId for 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
+
+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_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
+
+    Args:
+        ad: android device object.
+        slot_id: 0 or 1 (integer)
+
+    Returns:
+        None
+    """
+    sub_id = get_subid_from_slot_index(ad.log, ad, slot_id)
+    ad.log.info("Default Subid for all service is %s", sub_id)
+    set_subid_for_outgoing_call(ad, sub_id)
+    set_incoming_voice_sub_id(ad, sub_id)
+    set_subid_for_data(ad, sub_id)
+    set_subid_for_message(ad, sub_id)
+    ad.droid.telephonyToggleDataConnection(True)
+
+
+def perform_dds_switch(ad):
+    slot_dict = {0: {}, 1: {}}
+    for slot in (0,1):
+        slot_dict[slot]['sub_id'] = get_subid_from_slot_index(ad.log, ad, slot)
+        slot_dict[slot]['operator'] = get_operatorname_from_slot_index(ad, slot)
+    ad.log.debug("%s", slot_dict)
+
+    current_data = get_default_data_sub_id(ad)
+    if slot_dict[0]['sub_id'] == current_data:
+        ad.log.info("DDS Switch from %s to %s", slot_dict[0]['operator'],
+                                                slot_dict[1]['operator'])
+        new_data = slot_dict[1]['sub_id']
+        new_oper = slot_dict[1]['operator']
+    else:
+        ad.log.info("DDS Switch from %s to %s", slot_dict[1]['operator'],
+                                                slot_dict[0]['operator'])
+        new_data = slot_dict[0]['sub_id']
+        new_oper = slot_dict[0]['operator']
+    set_subid_for_data(ad, new_data)
+    ad.droid.telephonyToggleDataConnection(True)
+    if get_default_data_sub_id(ad) == new_data:
+        return new_oper
+    else:
+        ad.log.error("DDS Switch Failed")
+        return False
+
+
+def set_dds_on_slot_0(ad):
+    sub_id = get_subid_from_slot_index(ad.log, ad, 0)
+    operator = get_operatorname_from_slot_index(ad, 0)
+    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_dds_on_slot_1(ad):
+    sub_id = get_subid_from_slot_index(ad.log, ad, 1)
+    operator = get_operatorname_from_slot_index(ad, 1)
+    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_slways_allow_mms_data(ad, sub_id, state=True):
+    """Set always allow mms data on sub_id
+
+    Args:
+        ad: android device object.
+        sub_id: subscription id (integer)
+        state: True or False
+
+    Returns:
+        None
+    """
+    list_of_models = ["sdm", "msm", "kon", "lit"]
+    if any(model in ad.model for model in list_of_models):
+        ad.log.debug("SKIP telephonySetAlwaysAllowMmsData")
+    else:
+        ad.log.debug("telephonySetAlwaysAllowMmsData %s sub_id %s", state, sub_id)
+        ad.droid.telephonySetAlwaysAllowMmsData(sub_id, state)
+    return True
+
+
+def get_cbrs_and_default_sub_id(ad):
+    """Gets CBRS and Default SubId
+
+    Args:
+        ad: android device object.
+
+    Returns:
+        cbrs_subId
+        default_subId
+    """
+    cbrs_subid, default_subid = None, None
+    slot_dict = {0: {}, 1: {}}
+    for slot in (0, 1):
+        slot_dict[slot]['sub_id'] = get_subid_from_slot_index(
+            ad.log, ad, slot)
+        slot_dict[slot]['carrier_id'] = get_carrierid_from_slot_index(
+            ad, slot)
+        slot_dict[slot]['operator'] = get_operatorname_from_slot_index(
+            ad, slot)
+        if slot_dict[slot]['carrier_id'] == 2340:
+            cbrs_subid = slot_dict[slot]['sub_id']
+        else:
+            default_subid = slot_dict[slot]['sub_id']
+        ad.log.info("Slot %d - Sub %s - Carrier %d - %s", slot,
+                    slot_dict[slot]['sub_id'],
+                    slot_dict[slot]['carrier_id'],
+                    slot_dict[slot]['operator'])
+        if not cbrs_subid:
+            ad.log.error("CBRS sub_id is not ACTIVE")
+    return cbrs_subid, default_subid
+
+def get_subid_on_same_network_of_host_ad(ads, host_sub_id=None, type="voice"):
+    ad_host = ads[0]
+    ad_p1 = ads[1]
+
+    try:
+        ad_p2 = ads[2]
+    except:
+        ad_p2 = None
+
+    if not host_sub_id:
+        if type == "sms":
+            host_sub_id = get_outgoing_message_sub_id(ad_host)
+        else:
+            host_sub_id = get_incoming_voice_sub_id(ad_host)
+    host_mcc = ad_host.telephony["subscription"][host_sub_id]["mcc"]
+    host_mnc = ad_host.telephony["subscription"][host_sub_id]["mnc"]
+    p1_sub_id = INVALID_SUB_ID
+    p2_sub_id = INVALID_SUB_ID
+    p1_mcc = None
+    p1_mnc = None
+    p2_mcc = None
+    p2_mnc = None
+
+    for ad in [ad_p1, ad_p2]:
+        if ad:
+            for sub_id in ad.telephony["subscription"]:
+                mcc = ad.telephony["subscription"][sub_id]["mcc"]
+                mnc = ad.telephony["subscription"][sub_id]["mnc"]
+
+                if ad == ad_p1:
+                    if p1_sub_id == INVALID_SUB_ID:
+                        p1_sub_id = sub_id
+                    if not p1_mcc:
+                        p1_mcc = mcc
+                    if not p1_mnc:
+                        p1_mnc = mnc
+                elif ad == ad_p2:
+                    if p2_sub_id == INVALID_SUB_ID:
+                        p2_sub_id = sub_id
+                    if not p2_mcc:
+                        p2_mcc = mcc
+                    if not p2_mnc:
+                        p2_mnc = mnc
+
+                if mcc == host_mcc and mnc == host_mnc:
+                    if ad == ad_p1:
+                        p1_sub_id = sub_id
+                        p1_mcc = mcc
+                        p1_mnc = mnc
+
+                    elif ad == ad_p2:
+                        p2_sub_id = sub_id
+                        p2_mcc = mcc
+                        p2_mnc = mnc
+
+    return host_sub_id, p1_sub_id, p2_sub_id
\ 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
new file mode 100644
index 0000000..846a2f9
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_test_utils.py
@@ -0,0 +1,11011 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+from future import standard_library
+standard_library.install_aliases()
+
+import concurrent.futures
+import json
+import logging
+import re
+import os
+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.android_device import list_adb_devices
+from acts.controllers.android_device import list_fastboot_devices
+from acts.controllers.android_device import DEFAULT_QXDM_LOG_PATH
+from acts.controllers.android_device import DEFAULT_SDM_LOG_PATH
+from acts.controllers.android_device import SL4A_APK_NAME
+from acts.libs.proc import job
+from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
+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 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
+from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
+from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_VOLTE
+from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_VOLTE_PROVISIONING
+from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_VOLTE_OVERRIDE_WFC_PROVISIONING
+from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_HIDE_ENHANCED_4G_LTE_BOOL
+from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_VT
+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 DATA_STATE_CONNECTED
+from acts_contrib.test_utils.tel.tel_defines import DATA_STATE_DISCONNECTED
+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_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 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
+from acts_contrib.test_utils.tel.tel_defines import SERVICE_STATE_MAPPING
+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.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_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_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.wifi import wifi_test_utils
+from acts_contrib.test_utils.wifi import wifi_constants
+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
+
+
+class TelTestUtilsError(Exception):
+    pass
+
+
+class TelResultWrapper(object):
+    """Test results wrapper for Telephony test utils.
+
+    In order to enable metrics reporting without refactoring
+    all of the test utils this class is used to keep the
+    current return boolean scheme in tact.
+    """
+
+    def __init__(self, result_value):
+        self._result_value = result_value
+
+    @property
+    def result_value(self):
+        return self._result_value
+
+    @result_value.setter
+    def result_value(self, result_value):
+        self._result_value = result_value
+
+    def __bool__(self):
+        return self._result_value == CallResult('SUCCESS')
+
+
+def abort_all_tests(log, msg):
+    log.error("Aborting all ongoing tests due to: %s.", msg)
+    abort_all(msg)
+
+
+def get_phone_number_by_adb(ad):
+    return phone_number_formatter(
+        ad.adb.shell("service call iphonesubinfo 13"))
+
+
+def get_iccid_by_adb(ad):
+    return ad.adb.shell("service call iphonesubinfo 11")
+
+
+def get_operator_by_adb(ad):
+    operator = ad.adb.getprop("gsm.sim.operator.alpha")
+    if "," in operator:
+        operator = operator.strip()[0]
+    return operator
+
+
+def get_plmn_by_adb(ad):
+    plmn_id = ad.adb.getprop("gsm.sim.operator.numeric")
+    if "," in plmn_id:
+        plmn_id = plmn_id.strip()[0]
+    return plmn_id
+
+
+def get_sub_id_by_adb(ad):
+    return ad.adb.shell("service call iphonesubinfo 5")
+
+
+def setup_droid_properties_by_adb(log, ad, sim_filename=None):
+
+    sim_data = None
+    if sim_filename:
+        try:
+            sim_data = load_config(sim_filename)
+        except Exception:
+            log.warning("Failed to load %s!", sim_filename)
+
+    sub_id = get_sub_id_by_adb(ad)
+    iccid = get_iccid_by_adb(ad)
+    ad.log.info("iccid = %s", iccid)
+    if sim_data.get(iccid) and sim_data[iccid].get("phone_num"):
+        phone_number = phone_number_formatter(sim_data[iccid]["phone_num"])
+    else:
+        phone_number = get_phone_number_by_adb(ad)
+        if not phone_number and hasattr(ad, phone_number):
+            phone_number = ad.phone_number
+    if not phone_number:
+        ad.log.error("Failed to find valid phone number for %s", iccid)
+        abort_all_tests(ad.log, "Failed to find valid phone number for %s")
+    sub_record = {
+        'phone_num': phone_number,
+        'iccid': get_iccid_by_adb(ad),
+        'sim_operator_name': get_operator_by_adb(ad),
+        'operator': operator_name_from_plmn_id(get_plmn_by_adb(ad))
+    }
+    device_props = {'subscription': {sub_id: sub_record}}
+    ad.log.info("subId %s SIM record: %s", sub_id, sub_record)
+    setattr(ad, 'telephony', device_props)
+
+
+def setup_droid_properties(log, ad, sim_filename=None):
+
+    if ad.skip_sl4a:
+        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:
+            sim_data = load_config(sim_filename)
+        except Exception:
+            log.warning("Failed to load %s!", sim_filename)
+    if not ad.telephony["subscription"]:
+        abort_all_tests(ad.log, "No valid subscription")
+    ad.log.debug("Subscription DB %s", ad.telephony["subscription"])
+    result = True
+    active_sub_id = get_outgoing_voice_sub_id(ad)
+    for sub_id, sub_info in ad.telephony["subscription"].items():
+        ad.log.debug("Loop for Subid %s", sub_id)
+        sub_info["operator"] = get_operator_name(log, ad, sub_id)
+        iccid = sub_info["iccid"]
+        if not iccid:
+            ad.log.warning("Unable to find ICC-ID for subscriber %s", sub_id)
+            continue
+        if sub_info.get("phone_num"):
+            if iccid in sim_data and sim_data[iccid].get("phone_num"):
+                if not check_phone_number_match(sim_data[iccid]["phone_num"],
+                                                sub_info["phone_num"]):
+                    ad.log.warning(
+                        "phone_num %s in sim card data file for iccid %s"
+                        "  do not match phone_num %s from subscription",
+                        sim_data[iccid]["phone_num"], iccid,
+                        sub_info["phone_num"])
+                sub_info["phone_num"] = sim_data[iccid]["phone_num"]
+        else:
+            if iccid in sim_data and sim_data[iccid].get("phone_num"):
+                sub_info["phone_num"] = sim_data[iccid]["phone_num"]
+            elif sub_id == active_sub_id:
+                phone_number = get_phone_number_by_secret_code(
+                    ad, sub_info["sim_operator_name"])
+                if phone_number:
+                    sub_info["phone_num"] = phone_number
+                elif getattr(ad, "phone_num", None):
+                    sub_info["phone_num"] = ad.phone_number
+        if (not sub_info.get("phone_num")) and sub_id == active_sub_id:
+            ad.log.info("sub_id %s sub_info = %s", sub_id, sub_info)
+            ad.log.error(
+                "Unable to retrieve phone number for sub %s with iccid"
+                " %s from device or testbed config or sim card file %s",
+                sub_id, iccid, sim_filename)
+            result = False
+        if not hasattr(
+                ad, 'roaming'
+        ) and sub_info["sim_plmn"] != sub_info["network_plmn"] and sub_info["sim_operator_name"].strip(
+        ) not in sub_info["network_operator_name"].strip():
+            ad.log.info("roaming is not enabled, enable it")
+            setattr(ad, 'roaming', True)
+        ad.log.info("SubId %s info: %s", sub_id, sorted(sub_info.items()))
+    get_phone_capability(ad)
+    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)
+        # Setup VoWiFi MDN for Verizon. b/33187374
+    if not result:
+        abort_all_tests(ad.log, "Failed to find valid phone number")
+
+    ad.log.debug("telephony = %s", ad.telephony)
+
+
+def refresh_droid_config(log, ad):
+    """ Update Android Device telephony records for each sub_id.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        None
+    """
+    if not getattr(ad, 'telephony', {}):
+        setattr(ad, 'telephony', {"subscription": {}})
+    droid = ad.droid
+    sub_info_list = droid.subscriptionGetAllSubInfoList()
+    ad.log.info("SubInfoList is %s", sub_info_list)
+    active_sub_id = get_outgoing_voice_sub_id(ad)
+    for sub_info in sub_info_list:
+        sub_id = sub_info["subscriptionId"]
+        sim_slot = sub_info["simSlotIndex"]
+        if sub_info.get("carrierId"):
+            carrier_id = sub_info["carrierId"]
+        else:
+            carrier_id = -1
+        if sub_info.get("isOpportunistic"):
+            isopportunistic = sub_info["isOpportunistic"]
+        else:
+            isopportunistic = -1
+
+        if sim_slot != INVALID_SIM_SLOT_INDEX:
+            if sub_id not in ad.telephony["subscription"]:
+                ad.telephony["subscription"][sub_id] = {}
+            sub_record = ad.telephony["subscription"][sub_id]
+            if sub_info.get("iccId"):
+                sub_record["iccid"] = sub_info["iccId"]
+            else:
+                sub_record[
+                    "iccid"] = droid.telephonyGetSimSerialNumberForSubscription(
+                        sub_id)
+            sub_record["sim_slot"] = sim_slot
+            if sub_info.get("mcc"):
+                sub_record["mcc"] = sub_info["mcc"]
+            if sub_info.get("mnc"):
+                sub_record["mnc"] = sub_info["mnc"]
+            if sub_info.get("displayName"):
+                sub_record["display_name"] = sub_info["displayName"]
+            try:
+                sub_record[
+                    "phone_type"] = droid.telephonyGetPhoneTypeForSubscription(
+                        sub_id)
+            except:
+                if not sub_record.get("phone_type"):
+                    sub_record["phone_type"] = droid.telephonyGetPhoneType()
+            sub_record[
+                "sim_plmn"] = droid.telephonyGetSimOperatorForSubscription(
+                    sub_id)
+            sub_record[
+                "sim_operator_name"] = droid.telephonyGetSimOperatorNameForSubscription(
+                    sub_id)
+            sub_record[
+                "network_plmn"] = droid.telephonyGetNetworkOperatorForSubscription(
+                    sub_id)
+            sub_record[
+                "network_operator_name"] = droid.telephonyGetNetworkOperatorNameForSubscription(
+                    sub_id)
+            sub_record[
+                "sim_country"] = droid.telephonyGetSimCountryIsoForSubscription(
+                    sub_id)
+            if active_sub_id == sub_id:
+                try:
+                    sub_record[
+                        "carrier_id"] = ad.droid.telephonyGetSimCarrierId()
+                    sub_record[
+                        "carrier_id_name"] = ad.droid.telephonyGetSimCarrierIdName(
+                        )
+                except:
+                    ad.log.info("Carrier ID is not supported")
+            if carrier_id == 2340:
+                ad.log.info("SubId %s info: %s", sub_id, sorted(
+                    sub_record.items()))
+                continue
+            if carrier_id == 1989 and isopportunistic == "true":
+                ad.log.info("SubId %s info: %s", sub_id, sorted(
+                    sub_record.items()))
+                continue
+            if not sub_info.get("number"):
+                sub_info[
+                    "number"] = droid.telephonyGetLine1NumberForSubscription(
+                        sub_id)
+            if sub_info.get("number"):
+                if sub_record.get("phone_num"):
+                    # Use the phone number provided in sim info file by default
+                    # as the sub_info["number"] may not be formatted in a
+                    # dialable number
+                    if not check_phone_number_match(sub_info["number"],
+                                                    sub_record["phone_num"]):
+                        ad.log.info(
+                            "Subscriber phone number changed from %s to %s",
+                            sub_record["phone_num"], sub_info["number"])
+                        sub_record["phone_num"] = sub_info["number"]
+                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()))
+
+
+def get_phone_number_by_secret_code(ad, operator):
+    if "T-Mobile" in operator:
+        ad.droid.telecomDialNumber("#686#")
+        ad.send_keycode("ENTER")
+        for _ in range(12):
+            output = ad.search_logcat("mobile number")
+            if output:
+                result = re.findall(r"mobile number is (\S+)",
+                                    output[-1]["log_message"])
+                ad.send_keycode("BACK")
+                return result[0]
+            else:
+                time.sleep(5)
+    return ""
+
+
+def get_user_config_profile(ad):
+    return {
+        "Airplane Mode":
+        ad.droid.connectivityCheckAirplaneMode(),
+        "IMS Registered":
+        ad.droid.telephonyIsImsRegistered(),
+        "Preferred Network Type":
+        ad.droid.telephonyGetPreferredNetworkTypes(),
+        "VoLTE Platform Enabled":
+        ad.droid.imsIsEnhanced4gLteModeSettingEnabledByPlatform(),
+        "VoLTE Enabled":
+        ad.droid.imsIsEnhanced4gLteModeSettingEnabledByUser(),
+        "VoLTE Available":
+        ad.droid.telephonyIsVolteAvailable(),
+        "VT Available":
+        ad.droid.telephonyIsVideoCallingAvailable(),
+        "VT Enabled":
+        ad.droid.imsIsVtEnabledByUser(),
+        "VT Platform Enabled":
+        ad.droid.imsIsVtEnabledByPlatform(),
+        "WiFi State":
+        ad.droid.wifiCheckState(),
+        "WFC Available":
+        ad.droid.telephonyIsWifiCallingAvailable(),
+        "WFC Enabled":
+        ad.droid.imsIsWfcEnabledByUser(),
+        "WFC Platform Enabled":
+        ad.droid.imsIsWfcEnabledByPlatform(),
+        "WFC Mode":
+        ad.droid.imsGetWfcMode()
+    }
+
+
+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
+
+    Args:
+        ad: android_device object.
+
+    Returns:
+        result: The number of loaded (physical) SIM cards
+    """
+    # using a dictionary as a cheap way to prevent double counting
+    # in the situation where multiple subscriptions are on the same SIM.
+    # yes, this is a corner corner case.
+    valid_sims = {}
+    subInfo = ad.droid.subscriptionGetAllSubInfoList()
+    for info in subInfo:
+        ssidx = info['simSlotIndex']
+        if ssidx == INVALID_SIM_SLOT_INDEX:
+            continue
+        valid_sims[ssidx] = True
+    return len(valid_sims.keys())
+
+
+def toggle_airplane_mode_by_adb(log, ad, new_state=None):
+    """ Toggle the state of airplane mode.
+
+    Args:
+        log: log handler.
+        ad: android_device object.
+        new_state: Airplane mode state to set to.
+            If None, opposite of the current state.
+        strict_checking: Whether to turn on strict checking that checks all features.
+
+    Returns:
+        result: True if operation succeed. False if error happens.
+    """
+    cur_state = bool(int(ad.adb.shell("settings get global airplane_mode_on")))
+    if new_state == cur_state:
+        ad.log.info("Airplane mode already in %s", new_state)
+        return True
+    elif new_state is None:
+        new_state = not cur_state
+    ad.log.info("Change airplane mode from %s to %s", cur_state, new_state)
+    try:
+        ad.adb.shell("settings put global airplane_mode_on %s" % int(new_state))
+        ad.adb.shell("am broadcast -a android.intent.action.AIRPLANE_MODE")
+    except Exception as e:
+        ad.log.error(e)
+        return False
+    changed_state = bool(int(ad.adb.shell("settings get global airplane_mode_on")))
+    return changed_state == new_state
+
+
+def toggle_airplane_mode(log, ad, new_state=None, strict_checking=True):
+    """ Toggle the state of airplane mode.
+
+    Args:
+        log: log handler.
+        ad: android_device object.
+        new_state: Airplane mode state to set to.
+            If None, opposite of the current state.
+        strict_checking: Whether to turn on strict checking that checks all features.
+
+    Returns:
+        result: True if operation succeed. False if error happens.
+    """
+    if ad.skip_sl4a:
+        return toggle_airplane_mode_by_adb(log, ad, new_state)
+    else:
+        return toggle_airplane_mode_msim(
+            log, ad, new_state, strict_checking=strict_checking)
+
+
+def get_telephony_signal_strength(ad):
+    #{'evdoEcio': -1, 'asuLevel': 28, 'lteSignalStrength': 14, 'gsmLevel': 0,
+    # 'cdmaAsuLevel': 99, 'evdoDbm': -120, 'gsmDbm': -1, 'cdmaEcio': -160,
+    # 'level': 2, 'lteLevel': 2, 'cdmaDbm': -120, 'dbm': -112, 'cdmaLevel': 0,
+    # 'lteAsuLevel': 28, 'gsmAsuLevel': 99, 'gsmBitErrorRate': 0,
+    # 'lteDbm': -112, 'gsmSignalStrength': 99}
+    try:
+        signal_strength = ad.droid.telephonyGetSignalStrength()
+        if not signal_strength:
+            signal_strength = {}
+    except Exception as e:
+        ad.log.error(e)
+        signal_strength = {}
+    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"):
+            out = ad.adb.shell(
+                "dumpsys telephony.registry | grep -i signalstrength")
+            if out:
+                lte_rsrp = out.split()[9]
+                if lte_rsrp:
+                    ad.log.info("lte_rsrp: %s ", lte_rsrp)
+                    return lte_rsrp
+        else:
+            out = ad.adb.shell(
+            "dumpsys telephony.registry |grep -i primary=CellSignalStrengthLte")
+            if out:
+                lte_cell_info = out.split('mLte=')[1]
+                lte_rsrp = re.match(r'.*rsrp=(\S+).*', lte_cell_info).group(1)
+                if lte_rsrp:
+                    ad.log.info("lte_rsrp: %s ", lte_rsrp)
+                    return lte_rsrp
+    except Exception as e:
+        ad.log.error(e)
+    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,
+                 ignore_status=True)
+    ad.adb.shell("iptables -I INPUT 2 -p tcp --sport %s -j ACCEPT" % sl4a_port,
+                 ignore_status=True)
+    ad.adb.shell("iptables -I INPUT 3 -j DROP", ignore_status=True)
+    ad.adb.shell("ip6tables -I INPUT -j DROP", ignore_status=True)
+    return True
+
+
+def resume_internet_with_sl4a_port(ad, sl4a_port):
+    ad.log.info("Bring internet back using iptables rules")
+    ad.adb.shell("iptables -D INPUT -p tcp --dport %s -j ACCEPT" % sl4a_port,
+                 ignore_status=True)
+    ad.adb.shell("iptables -D INPUT -p tcp --sport %s -j ACCEPT" % sl4a_port,
+                 ignore_status=True)
+    ad.adb.shell("iptables -D INPUT -j DROP", ignore_status=True)
+    ad.adb.shell("ip6tables -D INPUT -j DROP", ignore_status=True)
+    return True
+
+
+def test_data_browsing_success_using_sl4a(log, ad):
+    result = True
+    web_page_list = ['https://www.google.com', 'https://www.yahoo.com',
+                     'https://www.amazon.com', 'https://www.nike.com',
+                     'https://www.facebook.com']
+    for website in web_page_list:
+        if not verify_http_connection(log, ad, website, retry=0):
+            ad.log.error("Failed to browse %s successfully!", website)
+            result = False
+    return result
+
+
+def test_data_browsing_failure_using_sl4a(log, ad):
+    result = True
+    web_page_list = ['https://www.youtube.com', 'https://www.cnn.com',
+                     'https://www.att.com', 'https://www.nbc.com',
+                     'https://www.verizonwireless.com']
+    for website in web_page_list:
+        if not verify_http_connection(log, ad, website, retry=0,
+                                      expected_state=False):
+            ad.log.error("Browsing to %s worked!", website)
+            result = False
+    return result
+
+
+def is_expected_event(event_to_check, events_list):
+    """ check whether event is present in the event list
+
+    Args:
+        event_to_check: event to be checked.
+        events_list: list of events
+    Returns:
+        result: True if event present in the list. False if not.
+    """
+    for event in events_list:
+        if event in event_to_check['name']:
+            return True
+    return False
+
+
+def is_sim_ready(log, ad, sim_slot_id=None):
+    """ check whether SIM is ready.
+
+    Args:
+        ad: android_device object.
+        sim_slot_id: check the SIM status for sim_slot_id
+            This is optional. If this is None, check default SIM.
+
+    Returns:
+        result: True if all SIMs are ready. False if not.
+    """
+    if sim_slot_id is None:
+        status = ad.droid.telephonyGetSimState()
+    else:
+        status = ad.droid.telephonyGetSimStateForSlotId(sim_slot_id)
+    if status != SIM_STATE_READY:
+        ad.log.info("Sim state is %s, not ready", status)
+        return False
+    return True
+
+
+def is_sim_ready_by_adb(log, ad):
+    state = ad.adb.getprop("gsm.sim.state")
+    ad.log.info("gsm.sim.state = %s", state)
+    return state == SIM_STATE_READY or state == SIM_STATE_LOADED
+
+
+def wait_for_sim_ready_by_adb(log, ad, wait_time=90):
+    return _wait_for_droid_in_state(log, ad, wait_time, is_sim_ready_by_adb)
+
+
+def is_sims_ready_by_adb(log, ad):
+    states = list(ad.adb.getprop("gsm.sim.state").split(","))
+    ad.log.info("gsm.sim.state = %s", states)
+    for state in states:
+        if state != SIM_STATE_READY and state != SIM_STATE_LOADED:
+            return False
+    return True
+
+
+def wait_for_sims_ready_by_adb(log, ad, wait_time=90):
+    return _wait_for_droid_in_state(log, ad, wait_time, is_sims_ready_by_adb)
+
+
+def get_service_state_by_adb(log, ad):
+    output = ad.adb.shell("dumpsys telephony.registry | grep mServiceState")
+    if "mVoiceRegState" in output:
+        result = re.search(r"mVoiceRegState=(\S+)\((\S+)\)", output)
+        if result:
+            ad.log.info("mVoiceRegState is %s %s", result.group(1),
+                        result.group(2))
+            return result.group(2)
+        else:
+            if getattr(ad, "sdm_log", False):
+                #look for all occurrence in string
+                result2 = re.findall(r"mVoiceRegState=(\S+)\((\S+)\)", output)
+                for voice_state in result2:
+                    if voice_state[0] == 0:
+                        ad.log.info("mVoiceRegState is 0 %s", voice_state[1])
+                        return voice_state[1]
+                return result2[1][1]
+    else:
+        result = re.search(r"mServiceState=(\S+)", output)
+        if result:
+            ad.log.info("mServiceState=%s %s", result.group(1),
+                        SERVICE_STATE_MAPPING[result.group(1)])
+            return SERVICE_STATE_MAPPING[result.group(1)]
+
+
+def _is_expecting_event(event_recv_list):
+    """ check for more event is expected in event list
+
+    Args:
+        event_recv_list: list of events
+    Returns:
+        result: True if more events are expected. False if not.
+    """
+    for state in event_recv_list:
+        if state is False:
+            return True
+    return False
+
+
+def _set_event_list(event_recv_list, sub_id_list, sub_id, value):
+    """ set received event in expected event list
+
+    Args:
+        event_recv_list: list of received events
+        sub_id_list: subscription ID list
+        sub_id: subscription id of current event
+        value: True or False
+    Returns:
+        None.
+    """
+    for i in range(len(sub_id_list)):
+        if sub_id_list[i] == sub_id:
+            event_recv_list[i] = value
+
+
+def _wait_for_bluetooth_in_state(log, ad, state, max_wait):
+    # FIXME: These event names should be defined in a common location
+    _BLUETOOTH_STATE_ON_EVENT = 'BluetoothStateChangedOn'
+    _BLUETOOTH_STATE_OFF_EVENT = 'BluetoothStateChangedOff'
+    ad.ed.clear_events(_BLUETOOTH_STATE_ON_EVENT)
+    ad.ed.clear_events(_BLUETOOTH_STATE_OFF_EVENT)
+
+    ad.droid.bluetoothStartListeningForAdapterStateChange()
+    try:
+        bt_state = ad.droid.bluetoothCheckState()
+        if bt_state == state:
+            return True
+        if max_wait <= 0:
+            ad.log.error("Time out: bluetooth state still %s, expecting %s",
+                         bt_state, state)
+            return False
+
+        event = {
+            False: _BLUETOOTH_STATE_OFF_EVENT,
+            True: _BLUETOOTH_STATE_ON_EVENT
+        }[state]
+        event = ad.ed.pop_event(event, max_wait)
+        ad.log.info("Got event %s", event['name'])
+        return True
+    except Empty:
+        ad.log.error("Time out: bluetooth state still in %s, expecting %s",
+                     bt_state, state)
+        return False
+    finally:
+        ad.droid.bluetoothStopListeningForAdapterStateChange()
+
+
+# TODO: replace this with an event-based function
+def _wait_for_wifi_in_state(log, ad, state, max_wait):
+    return _wait_for_droid_in_state(log, ad, max_wait,
+        lambda log, ad, state: \
+                (True if ad.droid.wifiCheckState() == state else False),
+                state)
+
+
+def toggle_airplane_mode_msim(log, ad, new_state=None, strict_checking=True):
+    """ Toggle the state of airplane mode.
+
+    Args:
+        log: log handler.
+        ad: android_device object.
+        new_state: Airplane mode state to set to.
+            If None, opposite of the current state.
+        strict_checking: Whether to turn on strict checking that checks all features.
+
+    Returns:
+        result: True if operation succeed. False if error happens.
+    """
+
+    cur_state = ad.droid.connectivityCheckAirplaneMode()
+    if cur_state == new_state:
+        ad.log.info("Airplane mode already in %s", new_state)
+        return True
+    elif new_state is None:
+        new_state = not cur_state
+        ad.log.info("Toggle APM mode, from current tate %s to %s", cur_state,
+                    new_state)
+    sub_id_list = []
+    active_sub_info = ad.droid.subscriptionGetAllSubInfoList()
+    if active_sub_info:
+        for info in active_sub_info:
+            sub_id_list.append(info['subscriptionId'])
+
+    ad.ed.clear_all_events()
+    time.sleep(0.1)
+    service_state_list = []
+    if new_state:
+        service_state_list.append(SERVICE_STATE_POWER_OFF)
+        ad.log.info("Turn on airplane mode")
+
+    else:
+        # If either one of these 3 events show up, it should be OK.
+        # Normal SIM, phone in service
+        service_state_list.append(SERVICE_STATE_IN_SERVICE)
+        # NO SIM, or Dead SIM, or no Roaming coverage.
+        service_state_list.append(SERVICE_STATE_OUT_OF_SERVICE)
+        service_state_list.append(SERVICE_STATE_EMERGENCY_ONLY)
+        ad.log.info("Turn off airplane mode")
+
+    for sub_id in sub_id_list:
+        ad.droid.telephonyStartTrackingServiceStateChangeForSubscription(
+            sub_id)
+
+    timeout_time = time.time() + MAX_WAIT_TIME_AIRPLANEMODE_EVENT
+    ad.droid.connectivityToggleAirplaneMode(new_state)
+
+    try:
+        try:
+            event = ad.ed.wait_for_event(
+                EventServiceStateChanged,
+                is_event_match_for_list,
+                timeout=MAX_WAIT_TIME_AIRPLANEMODE_EVENT,
+                field=ServiceStateContainer.SERVICE_STATE,
+                value_list=service_state_list)
+            ad.log.info("Got event %s", event)
+        except Empty:
+            ad.log.warning("Did not get expected service state change to %s",
+                           service_state_list)
+        finally:
+            for sub_id in sub_id_list:
+                ad.droid.telephonyStopTrackingServiceStateChangeForSubscription(
+                    sub_id)
+    except Exception as e:
+        ad.log.error(e)
+
+    # APM on (new_state=True) will turn off bluetooth but may not turn it on
+    try:
+        if new_state and not _wait_for_bluetooth_in_state(
+                log, ad, False, timeout_time - time.time()):
+            ad.log.error(
+                "Failed waiting for bluetooth during airplane mode toggle")
+            if strict_checking: return False
+    except Exception as e:
+        ad.log.error("Failed to check bluetooth state due to %s", e)
+        if strict_checking:
+            raise
+
+    # APM on (new_state=True) will turn off wifi but may not turn it on
+    if new_state and not _wait_for_wifi_in_state(log, ad, False,
+                                                 timeout_time - time.time()):
+        ad.log.error("Failed waiting for wifi during airplane mode toggle on")
+        if strict_checking: return False
+
+    if ad.droid.connectivityCheckAirplaneMode() != new_state:
+        ad.log.error("Set airplane mode to %s failed", new_state)
+        return False
+    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():
+        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,
+        timeout=120):
+    """Wait for an data change event on specified subscription.
+
+    Args:
+        ad: android device object.
+        event_tracking_started: True if event tracking already state outside
+        timeout: time to wait for event
+
+    Returns:
+        True: if data change event is received.
+        False: if data change event is not received.
+    """
+    if not event_tracking_started:
+        ad.ed.clear_events(EventActiveDataSubIdChanged)
+        ad.droid.telephonyStartTrackingActiveDataChange()
+    try:
+        ad.ed.wait_for_event(
+            EventActiveDataSubIdChanged,
+            is_event_match,
+            timeout=timeout)
+        ad.log.info("Got event activedatasubidchanged")
+    except Empty:
+        ad.log.info("No event for data subid change")
+        return False
+    finally:
+        if not event_tracking_started:
+            ad.droid.telephonyStopTrackingActiveDataChange()
+    return True
+
+
+def is_current_data_on_cbrs(ad, cbrs_subid):
+    """Verifies if current data sub is on CBRS
+
+    Args:
+        ad: android device object.
+        cbrs_subid: sub_id against which we need to check
+
+    Returns:
+        True: if data is on cbrs
+        False: if data is not on cbrs
+    """
+    if cbrs_subid is None:
+        return False
+    current_data = ad.droid.subscriptionGetActiveDataSubscriptionId()
+    ad.log.info("Current Data subid %s cbrs_subid %s", current_data, cbrs_subid)
+    if current_data == cbrs_subid:
+        return True
+    else:
+        return False
+
+
+def get_current_override_network_type(ad, timeout=30):
+    """Returns current override network type
+
+    Args:
+        ad: android device object.
+        timeout: max time to wait for event
+
+    Returns:
+        value: current override type
+        -1: if no event received
+    """
+    override_value_list = [OverrideNetworkContainer.OVERRIDE_NETWORK_TYPE_NR_NSA,
+                           OverrideNetworkContainer.OVERRIDE_NETWORK_TYPE_NONE,
+                           OverrideNetworkContainer.OVERRIDE_NETWORK_TYPE_NR_MMWAVE,
+                           OverrideNetworkContainer.OVERRIDE_NETWORK_TYPE_LTE_CA,
+                           OverrideNetworkContainer.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO]
+    ad.ed.clear_events(EventDisplayInfoChanged)
+    ad.droid.telephonyStartTrackingDisplayInfoChange()
+    try:
+        event = ad.ed.wait_for_event(
+                    EventDisplayInfoChanged,
+                    is_event_match_for_list,
+                    timeout=timeout,
+                    field=DisplayInfoContainer.OVERRIDE,
+                    value_list=override_value_list)
+        override_type = event['data']['override']
+        ad.log.info("Current Override Type is %s", override_type)
+        return override_type
+    except Empty:
+        ad.log.info("No event for display info change")
+        return -1
+    finally:
+        ad.droid.telephonyStopTrackingDisplayInfoChange()
+    return -1
+
+
+def is_current_network_5g_nsa(ad, timeout=30):
+    """Verifies 5G NSA override network type
+
+    Args:
+        ad: android device object.
+        timeout: max time to wait for event
+
+    Returns:
+        True: if data is on 5g NSA
+        False: if data is not on 5g NSA
+    """
+    ad.ed.clear_events(EventDisplayInfoChanged)
+    ad.droid.telephonyStartTrackingDisplayInfoChange()
+    try:
+        event = ad.ed.wait_for_event(
+                EventDisplayInfoChanged,
+                is_event_match,
+                timeout=timeout,
+                field=DisplayInfoContainer.OVERRIDE,
+                value=OverrideNetworkContainer.OVERRIDE_NETWORK_TYPE_NR_NSA)
+        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
+
+
+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.
+    Currently only handle phone number with the following formats:
+        (US phone number format)
+        +1abcxxxyyyy
+        1abcxxxyyyy
+        abcxxxyyyy
+        abc xxx yyyy
+        abc.xxx.yyyy
+        abc-xxx-yyyy
+        (EEUK phone number format)
+        +44abcxxxyyyy
+        0abcxxxyyyy
+
+    Args:
+        number: input phone number
+
+    Returns:
+        Phone number without country code or prefix
+    """
+    if number is None:
+        return None, None
+    for country_code in COUNTRY_CODE_LIST:
+        if number.startswith(country_code):
+            return number[len(country_code):], country_code
+    if number[0] == "1" or number[0] == "0":
+        return number[1:], None
+    return number, None
+
+
+def check_phone_number_match(number1, number2):
+    """Check whether two input phone numbers match or not.
+
+    Compare the two input phone numbers.
+    If they match, return True; otherwise, return False.
+    Currently only handle phone number with the following formats:
+        (US phone number format)
+        +1abcxxxyyyy
+        1abcxxxyyyy
+        abcxxxyyyy
+        abc xxx yyyy
+        abc.xxx.yyyy
+        abc-xxx-yyyy
+        (EEUK phone number format)
+        +44abcxxxyyyy
+        0abcxxxyyyy
+
+        There are some scenarios we can not verify, one example is:
+            number1 = +15555555555, number2 = 5555555555
+            (number2 have no country code)
+
+    Args:
+        number1: 1st phone number to be compared.
+        number2: 2nd phone number to be compared.
+
+    Returns:
+        True if two phone numbers match. Otherwise False.
+    """
+    number1 = phone_number_formatter(number1)
+    number2 = phone_number_formatter(number2)
+    # Handle extra country code attachment when matching phone number
+    if number1[-7:] in number2 or number2[-7:] in number1:
+        return True
+    else:
+        logging.info("phone number1 %s and number2 %s does not match" %
+                     (number1, number2))
+        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):
+    """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 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,
+        get_incoming_voice_sub_id(ad))
+    output = ad.adb.shell("dumpsys telephony.registry | grep mCallState")
+    if "mCallState" in output:
+        call_state_list = re.findall("mCallState=(\d)", output)
+        if call_state_list:
+            return call_state_list[slot_index_of_default_voice_subid]
+
+
+def check_call_state_connected_by_adb(ad):
+    return "2" in get_call_state_by_adb(ad)
+
+
+def check_call_state_idle_by_adb(ad):
+    return "0" in get_call_state_by_adb(ad)
+
+
+def check_call_state_ring_by_adb(ad):
+    return "1" in get_call_state_by_adb(ad)
+
+
+def get_incoming_call_number_by_adb(ad):
+    output = ad.adb.shell(
+        "dumpsys telephony.registry | grep mCallIncomingNumber")
+    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")
+    calls = re.findall("Call TC@\d+: {(.*?)}", output, re.DOTALL)
+    calls_info = []
+    for call in calls:
+        call_info = {}
+        for attr in ("startTime", "endTime", "direction", "isInterrupted",
+                     "callTechnologies", "callTerminationsReason",
+                     "connectionService", "isVideoCall", "callProperties"):
+            match = re.search(r"%s: (.*)" % attr, call)
+            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)
+        call_info["inCallServices"] = re.findall(r"name: (.*)", call)
+        calls_info.append(call_info)
+    ad.log.debug("calls_info = %s", calls_info)
+    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 = []
+    output_phone_id_1 = []
+    current_output = []
+    for line in output:
+        if "Phone Id = 0" in line:
+            current_output = output_phone_id_0
+        elif "Phone Id = 1" in line:
+            current_output = output_phone_id_1
+        current_output.append(line.strip())
+
+    configs = {}
+    if ad.adb.getprop("ro.build.version.release")[0] in ("9", "P"):
+        phone_count = 1
+        if "," in ad.adb.getprop("gsm.network.type"):
+            phone_count = 2
+    else:
+        phone_count = ad.droid.telephonyGetPhoneCount()
+
+    slot_0_subid = get_subid_from_slot_index(ad.log, ad, 0)
+    if slot_0_subid != INVALID_SUB_ID:
+        configs[slot_0_subid] = {}
+
+    if phone_count == 2:
+        slot_1_subid = get_subid_from_slot_index(ad.log, ad, 1)
+        if slot_1_subid != INVALID_SUB_ID:
+            configs[slot_1_subid] = {}
+
+    attrs = [attr for attr in dir(CarrierConfigs) if not attr.startswith("__")]
+    for attr in attrs:
+        attr_string = getattr(CarrierConfigs, attr)
+        values = re.findall(
+            r"%s = (\S+)" % attr_string, "\n".join(output_phone_id_0))
+
+        if slot_0_subid != INVALID_SUB_ID:
+            if values:
+                value = values[-1]
+                if value == "true":
+                    configs[slot_0_subid][attr_string] = True
+                elif value == "false":
+                    configs[slot_0_subid][attr_string] = False
+                elif attr_string == CarrierConfigs.DEFAULT_WFC_IMS_MODE_INT:
+                    if value == "0":
+                        configs[slot_0_subid][attr_string] = WFC_MODE_WIFI_ONLY
+                    elif value == "1":
+                        configs[slot_0_subid][attr_string] = \
+                            WFC_MODE_CELLULAR_PREFERRED
+                    elif value == "2":
+                        configs[slot_0_subid][attr_string] = \
+                            WFC_MODE_WIFI_PREFERRED
+                else:
+                    try:
+                        configs[slot_0_subid][attr_string] = int(value)
+                    except Exception:
+                        configs[slot_0_subid][attr_string] = value
+            else:
+                configs[slot_0_subid][attr_string] = None
+
+        if phone_count == 2:
+            if slot_1_subid != INVALID_SUB_ID:
+                values = re.findall(
+                    r"%s = (\S+)" % attr_string, "\n".join(output_phone_id_1))
+                if values:
+                    value = values[-1]
+                    if value == "true":
+                        configs[slot_1_subid][attr_string] = True
+                    elif value == "false":
+                        configs[slot_1_subid][attr_string] = False
+                    elif attr_string == CarrierConfigs.DEFAULT_WFC_IMS_MODE_INT:
+                        if value == "0":
+                            configs[slot_1_subid][attr_string] = \
+                                WFC_MODE_WIFI_ONLY
+                        elif value == "1":
+                            configs[slot_1_subid][attr_string] = \
+                                WFC_MODE_CELLULAR_PREFERRED
+                        elif value == "2":
+                            configs[slot_1_subid][attr_string] = \
+                                WFC_MODE_WIFI_PREFERRED
+                    else:
+                        try:
+                            configs[slot_1_subid][attr_string] = int(value)
+                        except Exception:
+                            configs[slot_1_subid][attr_string] = value
+                else:
+                    configs[slot_1_subid][attr_string] = None
+    return configs
+
+
+def get_phone_capability(ad):
+    carrier_configs = dumpsys_carrier_config(ad)
+    for sub_id in carrier_configs:
+        capabilities = []
+        if carrier_configs[sub_id][CarrierConfigs.VOLTE_AVAILABLE_BOOL]:
+            capabilities.append(CAPABILITY_VOLTE)
+        if carrier_configs[sub_id][CarrierConfigs.WFC_IMS_AVAILABLE_BOOL]:
+            capabilities.append(CAPABILITY_WFC)
+        if carrier_configs[sub_id][CarrierConfigs.EDITABLE_WFC_MODE_BOOL]:
+            capabilities.append(CAPABILITY_WFC_MODE_CHANGE)
+        if carrier_configs[sub_id][CarrierConfigs.SUPPORT_CONFERENCE_CALL_BOOL]:
+            capabilities.append(CAPABILITY_CONFERENCE)
+        if carrier_configs[sub_id][CarrierConfigs.VT_AVAILABLE_BOOL]:
+            capabilities.append(CAPABILITY_VT)
+        if carrier_configs[sub_id][CarrierConfigs.VOLTE_PROVISIONED_BOOL]:
+            capabilities.append(CAPABILITY_VOLTE_PROVISIONING)
+        if carrier_configs[sub_id][CarrierConfigs.VOLTE_OVERRIDE_WFC_BOOL]:
+            capabilities.append(CAPABILITY_VOLTE_OVERRIDE_WFC_PROVISIONING)
+        if carrier_configs[sub_id][CarrierConfigs.HIDE_ENHANCED_4G_LTE_BOOL]:
+            capabilities.append(CAPABILITY_HIDE_ENHANCED_4G_LTE_BOOL)
+
+        ad.log.info("Capabilities of sub ID %s: %s", sub_id, capabilities)
+        if not getattr(ad, 'telephony', {}):
+            ad.telephony["subscription"] = {}
+            ad.telephony["subscription"][sub_id] = {}
+            setattr(
+                ad.telephony["subscription"][sub_id],
+                'capabilities', capabilities)
+
+        else:
+            ad.telephony["subscription"][sub_id]["capabilities"] = capabilities
+        if CAPABILITY_WFC not in capabilities:
+            wfc_modes = []
+        else:
+            if carrier_configs[sub_id].get(
+                CarrierConfigs.EDITABLE_WFC_MODE_BOOL, False):
+                wfc_modes = [
+                    WFC_MODE_CELLULAR_PREFERRED,
+                    WFC_MODE_WIFI_PREFERRED]
+            else:
+                wfc_modes = [
+                    carrier_configs[sub_id].get(
+                        CarrierConfigs.DEFAULT_WFC_IMS_MODE_INT,
+                        WFC_MODE_CELLULAR_PREFERRED)
+                ]
+        if carrier_configs[sub_id].get(
+            CarrierConfigs.WFC_SUPPORTS_WIFI_ONLY_BOOL,
+            False) and WFC_MODE_WIFI_ONLY not in wfc_modes:
+            wfc_modes.append(WFC_MODE_WIFI_ONLY)
+        ad.telephony["subscription"][sub_id]["wfc_modes"] = wfc_modes
+        if wfc_modes:
+            ad.log.info("Supported WFC modes for sub ID %s: %s", sub_id,
+                wfc_modes)
+
+
+def get_capability_for_subscription(ad, capability, subid):
+    if capability in ad.telephony["subscription"][subid].get(
+        "capabilities", []):
+        ad.log.info('Capability "%s" is available for sub ID %s.',
+            capability, subid)
+        return True
+    else:
+        ad.log.info('Capability "%s" is NOT available for sub ID %s.',
+            capability, subid)
+        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):
+    """ 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)
+
+
+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):
+    """ 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
+        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 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.
+
+    Args:
+        input_string: (string) input phone number.
+            The input could be 10/11/12 digital, with or without " "/"-"/"."
+        formatter: (int) expected format, this could be 7/10/11/12
+            if formatter is 7: output string would be 7 digital number.
+            if formatter is 10: output string would be 10 digital (standard) number.
+            if formatter is 11: output string would be "1" + 10 digital number.
+            if formatter is 12: output string would be "+1" + 10 digital number.
+
+    Returns:
+        If no error happen, return phone number in expected format.
+        Else, return None.
+    """
+    if not input_string:
+        return ""
+    # 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"):
+        input_string = "0" + input_string[3:]
+        return input_string
+    # Remove "1"  or "+1"from front
+    if (len(input_string) == PHONE_NUMBER_STRING_FORMAT_11_DIGIT
+            and input_string[0] == "1"):
+        input_string = input_string[1:]
+    elif (len(input_string) == PHONE_NUMBER_STRING_FORMAT_12_DIGIT
+          and input_string[0:2] == "+1"):
+        input_string = input_string[2:]
+    elif (len(input_string) == PHONE_NUMBER_STRING_FORMAT_7_DIGIT
+          and formatter == PHONE_NUMBER_STRING_FORMAT_7_DIGIT):
+        return input_string
+    elif len(input_string) != PHONE_NUMBER_STRING_FORMAT_10_DIGIT:
+        return None
+    # change input_string according to format
+    if formatter == PHONE_NUMBER_STRING_FORMAT_12_DIGIT:
+        input_string = "+1" + input_string
+    elif formatter == PHONE_NUMBER_STRING_FORMAT_11_DIGIT:
+        input_string = "1" + input_string
+    elif formatter == PHONE_NUMBER_STRING_FORMAT_10_DIGIT:
+        input_string = input_string
+    elif formatter == PHONE_NUMBER_STRING_FORMAT_7_DIGIT:
+        input_string = input_string[3:]
+    else:
+        return None
+    return input_string
+
+
+def get_internet_connection_type(log, ad):
+    """Get current active connection type name.
+
+    Args:
+        log: Log object.
+        ad: Android Device Object.
+    Returns:
+        current active connection type name.
+    """
+    if not ad.droid.connectivityNetworkIsConnected():
+        return 'none'
+    return connection_type_from_type_string(
+        ad.droid.connectivityNetworkGetActiveConnectionTypeName())
+
+
+def verify_http_connection(log,
+                           ad,
+                           url="https://www.google.com",
+                           retry=5,
+                           retry_interval=15,
+                           expected_state=True):
+    """Make ping request and return status.
+
+    Args:
+        log: log object
+        ad: Android Device Object.
+        url: Optional. The ping request will be made to this URL.
+            Default Value is "http://www.google.com/".
+
+    """
+    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()
+    for i in range(0, retry + 1):
+        try:
+            http_response = ad.data_droid.httpPing(url)
+        except Exception as e:
+            ad.log.info("httpPing with %s", e)
+            http_response = None
+        if (expected_state and http_response) or (not expected_state
+                                                  and not http_response):
+            ad.log.info("Http ping response for %s meet expected %s", url,
+                        expected_state)
+            return True
+        if i < retry:
+            time.sleep(retry_interval)
+    ad.log.error("Http ping to %s is %s after %s second, expecting %s", url,
+                 http_response, i * retry_interval, expected_state)
+    return False
+
+
+def _generate_file_directory_and_file_name(url, out_path):
+    file_name = url.split("/")[-1]
+    if not out_path:
+        file_directory = "/sdcard/Download/"
+    elif not out_path.endswith("/"):
+        file_directory, file_name = os.path.split(out_path)
+    else:
+        file_directory = out_path
+    return file_directory, file_name
+
+
+def _check_file_existance(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.
+    """
+    out = None
+    try:
+        out = ad.adb.shell('stat -c "%%s" %s' % file_path)
+    except AdbError:
+        pass
+    # Handle some old version adb returns error message "No such" into std_out
+    if out and "No such" not in out:
+        if expected_file_size:
+            file_size = int(out)
+            if file_size >= expected_file_size:
+                ad.log.info("File %s of size %s exists", file_path, file_size)
+                return True
+            else:
+                ad.log.info("File %s is of size %s, does not meet expected %s",
+                            file_path, file_size, expected_file_size)
+                return False
+        else:
+            ad.log.info("File %s exists", file_path)
+            return True
+    else:
+        ad.log.info("File %s does not exist.", file_path)
+        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="https://www.youtube.com/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,
+                                       expected_state=True,
+                                       timeout=60):
+    """Verify internet connection by ping test.
+
+    Args:
+        log: log object
+        ad: Android Device Object.
+
+    """
+    begin_time = get_current_epoch_time()
+    ip_addr = "54.230.144.105"
+    for dest in ("www.google.com", "www.amazon.com", ip_addr):
+        for i in range(retries):
+            ad.log.info("Ping %s - attempt %d", dest, i + 1)
+            result = adb_shell_ping(
+                ad, count=5, timeout=timeout, loss_tolerance=40, dest_ip=dest)
+            if result == expected_state:
+                ad.log.info(
+                    "Internet connection by pinging to %s is %s as expected",
+                    dest, expected_state)
+                if dest == ip_addr:
+                    ad.log.warning("Suspect dns failure")
+                    ad.log.info("DNS config: %s",
+                                ad.adb.shell("getprop | grep dns").replace(
+                                    "\n", " "))
+                    return False
+                return True
+            else:
+                ad.log.warning(
+                    "Internet connection test by pinging %s is %s, expecting %s",
+                    dest, result, expected_state)
+                if get_current_epoch_time() - begin_time < timeout * 1000:
+                    time.sleep(5)
+    ad.log.error("Ping test doesn't meet expected %s", expected_state)
+    return False
+
+
+def verify_internet_connection(log, ad, retries=3, expected_state=True):
+    """Verify internet connection by ping test and http connection.
+
+    Args:
+        log: log object
+        ad: Android Device Object.
+
+    """
+    if ad.droid.connectivityNetworkIsConnected() != expected_state:
+        ad.log.info("NetworkIsConnected = %s, expecting %s",
+                    not expected_state, expected_state)
+    if verify_internet_connection_by_ping(
+            log, ad, retries=retries, expected_state=expected_state):
+        return True
+    for url in ("https://www.google.com", "https://www.amazon.com"):
+        if verify_http_connection(
+                log, ad, url=url, retry=retries,
+                expected_state=expected_state):
+            return True
+    ad.log.info("DNS config: %s", " ".join(
+        ad.adb.shell("getprop | grep dns").split()))
+    ad.log.info("Interface info:\n%s", ad.adb.shell("ifconfig"))
+    ad.log.info("NetworkAgentInfo: %s",
+                ad.adb.shell("dumpsys connectivity | grep NetworkAgentInfo"))
+    return False
+
+
+def iperf_test_with_options(log,
+                            ad,
+                            iperf_server,
+                            iperf_option,
+                            timeout=180,
+                            rate_dict=None,
+                            blocking=True,
+                            log_file_path=None):
+    """Iperf adb run helper.
+
+    Args:
+        log: log object
+        ad: Android Device Object.
+        iperf_server: The iperf host url".
+        iperf_option: The options to pass to iperf client
+        timeout: timeout for file download to complete.
+        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
+    Returns:
+        True if IPerf runs without throwing an exception
+    """
+    try:
+        if log_file_path:
+            ad.adb.shell("rm %s" % log_file_path, ignore_status=True)
+        ad.log.info("Running adb iperf test with server %s", iperf_server)
+        ad.log.info("IPerf options are %s", iperf_option)
+        if not blocking:
+            ad.run_iperf_client_nb(
+                iperf_server,
+                iperf_option,
+                timeout=timeout + 60,
+                log_file_path=log_file_path)
+            return True
+        result, data = ad.run_iperf_client(
+            iperf_server, iperf_option, timeout=timeout + 60)
+        ad.log.info("IPerf test result with server %s is %s", iperf_server,
+                    result)
+        if result:
+            iperf_str = ''.join(data)
+            iperf_result = ipf.IPerfResult(iperf_str)
+            if "-u" in iperf_option:
+                udp_rate = iperf_result.avg_rate
+                if udp_rate is None:
+                    ad.log.warning(
+                        "UDP rate is none, IPerf server returned error: %s",
+                        iperf_result.error)
+                ad.log.info("IPerf3 udp speed is %sbps", udp_rate)
+            else:
+                tx_rate = iperf_result.avg_send_rate
+                rx_rate = iperf_result.avg_receive_rate
+                if (tx_rate or rx_rate) is None:
+                    ad.log.warning(
+                        "A TCP rate is none, IPerf server returned error: %s",
+                        iperf_result.error)
+                ad.log.info(
+                    "IPerf3 upload speed is %sbps, download speed is %sbps",
+                    tx_rate, rx_rate)
+            if rate_dict is not None:
+                rate_dict["Uplink"] = tx_rate
+                rate_dict["Downlink"] = rx_rate
+        return result
+    except AdbError as e:
+        ad.log.warning("Fail to run iperf test with exception %s", e)
+        raise
+
+
+def iperf_udp_test_by_adb(log,
+                          ad,
+                          iperf_server,
+                          port_num=None,
+                          reverse=False,
+                          timeout=180,
+                          limit_rate=None,
+                          omit=10,
+                          ipv6=False,
+                          rate_dict=None,
+                          blocking=True,
+                          log_file_path=None):
+    """Iperf test by adb using UDP.
+
+    Args:
+        log: log object
+        ad: Android Device Object.
+        iperf_Server: The iperf host url".
+        port_num: TCP/UDP server port
+        reverse: whether to test download instead of upload
+        timeout: timeout for file download to complete.
+        limit_rate: iperf bandwidth option. None by default
+        omit: the omit option provided in iperf command.
+        ipv6: whether to run the test as ipv6
+        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
+    """
+    iperf_option = "-u -i 1 -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 iperf_test_by_adb(log,
+                      ad,
+                      iperf_server,
+                      port_num=None,
+                      reverse=False,
+                      timeout=180,
+                      limit_rate=None,
+                      omit=10,
+                      ipv6=False,
+                      rate_dict=None,
+                      blocking=True,
+                      log_file_path=None):
+    """Iperf test by adb using TCP.
+
+    Args:
+        log: log object
+        ad: Android Device Object.
+        iperf_server: The iperf host url".
+        port_num: TCP/UDP server port
+        reverse: whether to test download instead of upload
+        timeout: timeout for file download to complete.
+        limit_rate: iperf bandwidth option. None by default
+        omit: the omit option provided in iperf command.
+        ipv6: whether to run the test as ipv6
+        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
+    """
+    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
+        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)
+        for cmd in ("ls -lh /data/local/tmp/tcpdump/",
+                    "ls -lh /sdcard/Download/",
+                    "ls -lh /data/vendor/radio/diag_logs/logs/",
+                    "df -h",
+                    "du -d 4 -h /data"):
+            out = ad.adb.shell(cmd)
+            ad.log.debug("%s", out)
+        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)
+            for cmd in ("ls -lh /data/local/tmp/tcpdump/",
+                        "ls -lh /sdcard/Download/",
+                        "ls -lh /data/vendor/radio/diag_logs/logs/",
+                        "df -h",
+                        "du -d 4 -h /data"):
+                out = ad.adb.shell(cmd)
+                ad.log.debug("%s", out)
+            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):
+    cmd = "echo restart > /sys/kernel/debug/msm_subsys/modem"
+    ad.log.info("Triggering Modem Crash from kernel using adb command %s", cmd)
+    ad.adb.shell(cmd)
+    time.sleep(timeout)
+    return True
+
+
+def trigger_modem_crash_by_modem(ad, timeout=120):
+    begin_time = get_device_epoch_time(ad)
+    ad.adb.shell(
+        "setprop persist.vendor.sys.modem.diag.mdlog false",
+        ignore_status=True)
+    # Legacy pixels use persist.sys.modem.diag.mdlog.
+    ad.adb.shell(
+        "setprop persist.sys.modem.diag.mdlog false", ignore_status=True)
+    disable_qxdm_logger(ad)
+    cmd = ('am instrument -w -e request "4b 25 03 00" '
+           '"com.google.mdstest/com.google.mdstest.instrument.'
+           'ModemCommandInstrumentation"')
+    ad.log.info("Crash modem by %s", cmd)
+    ad.adb.shell(cmd, ignore_status=True)
+    time.sleep(timeout)  # sleep time for sl4a stability
+    reasons = ad.search_logcat("modem subsystem failure reason", begin_time)
+    if reasons:
+        ad.log.info("Modem crash is triggered successfully")
+        ad.log.info(reasons[-1]["log_message"])
+        return True
+    else:
+        ad.log.warning("There is no modem subsystem failure reason logcat")
+        return False
+
+
+def phone_switch_to_msim_mode(ad, retries=3, timeout=60):
+    result = False
+    if not ad.is_apk_installed("com.google.mdstest"):
+        raise signals.TestAbortClass("mdstest is not installed")
+    mode = ad.droid.telephonyGetPhoneCount()
+    if mode == 2:
+        ad.log.info("Device already in MSIM mode")
+        return True
+    for i in range(retries):
+        ad.adb.shell(
+        "setprop persist.vendor.sys.modem.diag.mdlog false", ignore_status=True)
+        ad.adb.shell(
+        "setprop persist.sys.modem.diag.mdlog false", ignore_status=True)
+        disable_qxdm_logger(ad)
+        cmd = ('am instrument -w -e request "WriteEFS" -e item '
+               '"/google/pixel_multisim_config" -e data  "02 00 00 00" '
+               '"com.google.mdstest/com.google.mdstest.instrument.'
+               'ModemConfigInstrumentation"')
+        ad.log.info("Switch to MSIM mode by using %s", cmd)
+        ad.adb.shell(cmd, ignore_status=True)
+        time.sleep(timeout)
+        ad.adb.shell("setprop persist.radio.multisim.config dsds")
+        reboot_device(ad)
+        # Verify if device is really in msim mode
+        mode = ad.droid.telephonyGetPhoneCount()
+        if mode == 2:
+            ad.log.info("Device correctly switched to MSIM mode")
+            result = True
+            if "Sprint" in ad.adb.getprop("gsm.sim.operator.alpha"):
+                cmd = ('am instrument -w -e request "WriteEFS" -e item '
+                       '"/google/pixel_dsds_imei_mapping_slot_record" -e data "03"'
+                       ' "com.google.mdstest/com.google.mdstest.instrument.'
+                       'ModemConfigInstrumentation"')
+                ad.log.info("Switch Sprint to IMEI1 slot using %s", cmd)
+                ad.adb.shell(cmd, ignore_status=True)
+                time.sleep(timeout)
+                reboot_device(ad)
+            break
+        else:
+            ad.log.warning("Attempt %d - failed to switch to MSIM", (i + 1))
+    return result
+
+
+def phone_switch_to_ssim_mode(ad, retries=3, timeout=30):
+    result = False
+    if not ad.is_apk_installed("com.google.mdstest"):
+        raise signals.TestAbortClass("mdstest is not installed")
+    mode = ad.droid.telephonyGetPhoneCount()
+    if mode == 1:
+        ad.log.info("Device already in SSIM mode")
+        return True
+    for i in range(retries):
+        ad.adb.shell(
+        "setprop persist.vendor.sys.modem.diag.mdlog false", ignore_status=True)
+        ad.adb.shell(
+        "setprop persist.sys.modem.diag.mdlog false", ignore_status=True)
+        disable_qxdm_logger(ad)
+        cmds = ('am instrument -w -e request "WriteEFS" -e item '
+                '"/google/pixel_multisim_config" -e data  "01 00 00 00" '
+                '"com.google.mdstest/com.google.mdstest.instrument.'
+                'ModemConfigInstrumentation"',
+                'am instrument -w -e request "WriteEFS" -e item "/nv/item_files'
+                '/modem/uim/uimdrv/uim_extended_slot_mapping_config" -e data '
+                '"00 01 02 01" "com.google.mdstest/com.google.mdstest.'
+                'instrument.ModemConfigInstrumentation"')
+        for cmd in cmds:
+            ad.log.info("Switch to SSIM mode by using %s", cmd)
+            ad.adb.shell(cmd, ignore_status=True)
+            time.sleep(timeout)
+        ad.adb.shell("setprop persist.radio.multisim.config ssss")
+        reboot_device(ad)
+        # Verify if device is really in ssim mode
+        mode = ad.droid.telephonyGetPhoneCount()
+        if mode == 1:
+            ad.log.info("Device correctly switched to SSIM mode")
+            result = True
+            break
+        else:
+            ad.log.warning("Attempt %d - failed to switch to SSIM", (i + 1))
+    return result
+
+
+def lock_lte_band_by_mds(ad, band):
+    disable_qxdm_logger(ad)
+    ad.log.info("Write band %s locking to efs file", band)
+    if band == "4":
+        item_string = (
+            "4B 13 26 00 08 00 00 00 40 00 08 00 0B 00 08 00 00 00 00 00 00 00 "
+            "2F 6E 76 2F 69 74 65 6D 5F 66 69 6C 65 73 2F 6D 6F 64 65 6D 2F 6D "
+            "6D 6F 64 65 2F 6C 74 65 5F 62 61 6E 64 70 72 65 66 00")
+    elif band == "13":
+        item_string = (
+            "4B 13 26 00 08 00 00 00 40 00 08 00 0A 00 00 10 00 00 00 00 00 00 "
+            "2F 6E 76 2F 69 74 65 6D 5F 66 69 6C 65 73 2F 6D 6F 64 65 6D 2F 6D "
+            "6D 6F 64 65 2F 6C 74 65 5F 62 61 6E 64 70 72 65 66 00")
+    else:
+        ad.log.error("Band %s is not supported", band)
+        return False
+    cmd = ('am instrument -w -e request "%s" com.google.mdstest/com.google.'
+           'mdstest.instrument.ModemCommandInstrumentation')
+    for _ in range(3):
+        if "SUCCESS" in ad.adb.shell(cmd % item_string, ignore_status=True):
+            break
+    else:
+        ad.log.error("Fail to write band by %s" % (cmd % item_string))
+        return False
+
+    # EFS Sync
+    item_string = "4B 13 30 00 2A 00 2F 00"
+
+    for _ in range(3):
+        if "SUCCESS" in ad.adb.shell(cmd % item_string, ignore_status=True):
+            break
+    else:
+        ad.log.error("Fail to sync efs by %s" % (cmd % item_string))
+        return False
+    time.sleep(5)
+    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}
+    return state_mapping[ad.adb.shell("settings get global data_roaming")]
+
+
+def set_cell_data_roaming_state_by_adb(ad, state):
+    """Set Cell Data Roaming state."""
+    state_mapping = {True: "1", False: "0"}
+    ad.log.info("Set data roaming to %s", state)
+    ad.adb.shell("settings put global data_roaming %s" % state_mapping[state])
+
+
+def toggle_cell_data_roaming(ad, state):
+    """Enable cell data roaming for default data subscription.
+
+    Wait for the data roaming status to be DATA_STATE_CONNECTED
+        or DATA_STATE_DISCONNECTED.
+
+    Args:
+        log: Log object.
+        ad: Android Device Object.
+        state: True or False for enable or disable cell data roaming.
+
+    Returns:
+        True if success.
+        False if failed.
+    """
+    state_int = {True: DATA_ROAMING_ENABLE, False: DATA_ROAMING_DISABLE}[state]
+    action_str = {True: "Enable", False: "Disable"}[state]
+    if ad.droid.connectivityCheckDataRoamingMode() == state:
+        ad.log.info("Data roaming is already in state %s", state)
+        return True
+    if not ad.droid.connectivitySetDataRoaming(state_int):
+        ad.error.info("Fail to config data roaming into state %s", state)
+        return False
+    if ad.droid.connectivityCheckDataRoamingMode() == state:
+        ad.log.info("Data roaming is configured into state %s", state)
+        return True
+    else:
+        ad.log.error("Data roaming is not configured into state %s", state)
+        return False
+
+
+def verify_incall_state(log, ads, expected_status):
+    """Verify phones in incall state or not.
+
+    Verify if all phones in the array <ads> are in <expected_status>.
+
+    Args:
+        log: Log object.
+        ads: Array of Android Device Object. All droid in this array will be tested.
+        expected_status: If True, verify all Phones in incall state.
+            If False, verify all Phones not in incall state.
+
+    """
+    result = True
+    for ad in ads:
+        if ad.droid.telecomIsInCall() is not expected_status:
+            ad.log.error("InCall status:%s, expected:%s",
+                         ad.droid.telecomIsInCall(), expected_status)
+            result = False
+    return result
+
+
+def verify_active_call_number(log, ad, expected_number):
+    """Verify the number of current active call.
+
+    Verify if the number of current active call in <ad> is
+        equal to <expected_number>.
+
+    Args:
+        ad: Android Device Object.
+        expected_number: Expected active call number.
+    """
+    calls = ad.droid.telecomCallGetCallIds()
+    if calls is None:
+        actual_number = 0
+    else:
+        actual_number = len(calls)
+    if actual_number != expected_number:
+        ad.log.error("Active Call number is %s, expecting", actual_number,
+                     expected_number)
+        return False
+    return True
+
+
+def num_active_calls(log, ad):
+    """Get the count of current active calls.
+
+    Args:
+        log: Log object.
+        ad: Android Device Object.
+
+    Returns:
+        Count of current active calls.
+    """
+    calls = ad.droid.telecomCallGetCallIds()
+    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 Enhanced 4G LTE Mode 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 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:
+        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", 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:
+                    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.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
+
+    Args:
+        ad: Android device object.
+        sub_id: Subscription Id
+        feature_flag: voice, video, ut, sms
+        tech: wlan, wwan
+
+    """
+    try:
+        if sub_id is None:
+            sub_id = ad.droid.subscriptionGetDefaultVoiceSubId()
+        result = ad.droid.imsMmTelIsSupported(sub_id, feature_flag, tech)
+        ad.log.info("SubId %s - imsMmTelIsSupported 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 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:
+        if state_check_func(log, ad, *args, **kwargs):
+            return True
+
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+        max_time -= WAIT_TIME_BETWEEN_STATE_CHECK
+
+    return False
+
+
+def _wait_for_droid_in_state_for_subscription(
+        log, ad, sub_id, max_time, state_check_func, *args, **kwargs):
+    while max_time >= 0:
+        if state_check_func(log, ad, sub_id, *args, **kwargs):
+            return True
+
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+        max_time -= WAIT_TIME_BETWEEN_STATE_CHECK
+
+    return False
+
+
+def _wait_for_droids_in_state(log, ads, max_time, state_check_func, *args,
+                              **kwargs):
+    while max_time > 0:
+        success = True
+        for ad in ads:
+            if not state_check_func(log, ad, *args, **kwargs):
+                success = False
+                break
+        if success:
+            return True
+
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+        max_time -= WAIT_TIME_BETWEEN_STATE_CHECK
+
+    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)
+
+
+def _is_attached_for_subscription(log, ad, sub_id, voice_or_data):
+    rat = get_network_rat_for_subscription(log, ad, sub_id, voice_or_data)
+    ad.log.info("Sub_id %s network RAT is %s for %s", sub_id, rat,
+                voice_or_data)
+    return rat != RAT_UNKNOWN
+
+
+def is_voice_attached(log, ad):
+    return _is_attached_for_subscription(
+        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.
+
+    Args:
+        log: log object.
+        ad:  android device.
+        max_time: maximal wait time.
+
+    Returns:
+        Return True if device attach data within max_time.
+        Return False if timeout.
+    """
+    return _wait_for_droid_in_state(log, ad, max_time, _is_attached,
+                                    NETWORK_SERVICE_DATA)
+
+
+def wait_for_data_attach_for_subscription(log, ad, sub_id, max_time):
+    """Wait for android device to attach on data 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 data within max_time.
+        Return False if timeout.
+    """
+    return _wait_for_droid_in_state_for_subscription(
+        log, ad, sub_id, max_time, _is_attached_for_subscription,
+        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
+
+    Args:
+        log: log object.
+        ad: Android device object.
+
+    Returns:
+        Phone number.
+    """
+    return get_phone_number_for_subscription(log, ad,
+                                             get_outgoing_voice_sub_id(ad))
+
+
+def get_phone_number_for_subscription(log, ad, subid):
+    """Get phone number for subscription
+
+    Args:
+        log: log object.
+        ad: Android device object.
+        subid: subscription id.
+
+    Returns:
+        Phone number.
+    """
+    number = None
+    try:
+        number = ad.telephony['subscription'][subid]['phone_num']
+    except KeyError:
+        number = ad.droid.telephonyGetLine1NumberForSubscription(subid)
+    return number
+
+
+def set_phone_number(log, ad, phone_num):
+    """Set phone number for default subscription
+
+    Args:
+        log: log object.
+        ad: Android device object.
+        phone_num: phone number string.
+
+    Returns:
+        True if success.
+    """
+    return set_phone_number_for_subscription(log, ad,
+                                             get_outgoing_voice_sub_id(ad),
+                                             phone_num)
+
+
+def set_phone_number_for_subscription(log, ad, subid, phone_num):
+    """Set phone number for subscription
+
+    Args:
+        log: log object.
+        ad: Android device object.
+        subid: subscription id.
+        phone_num: phone number string.
+
+    Returns:
+        True if success.
+    """
+    try:
+        ad.telephony['subscription'][subid]['phone_num'] = phone_num
+    except Exception:
+        return False
+    return True
+
+
+def get_operator_name(log, ad, subId=None):
+    """Get operator name (e.g. vzw, tmo) of droid.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription ID
+            Optional, default is None
+
+    Returns:
+        Operator name.
+    """
+    try:
+        if subId is not None:
+            result = operator_name_from_plmn_id(
+                ad.droid.telephonyGetNetworkOperatorForSubscription(subId))
+        else:
+            result = operator_name_from_plmn_id(
+                ad.droid.telephonyGetNetworkOperator())
+    except KeyError:
+        try:
+            if subId is not None:
+                result = ad.droid.telephonyGetNetworkOperatorNameForSubscription(
+                    subId)
+            else:
+                result = ad.droid.telephonyGetNetworkOperatorName()
+            result = operator_name_from_network_name(result)
+        except Exception:
+            result = CARRIER_UNKNOWN
+    ad.log.info("Operator Name is %s", result)
+    return result
+
+
+def get_model_name(ad):
+    """Get android device model name
+
+    Args:
+        ad: Android device object
+
+    Returns:
+        model name string
+    """
+    # TODO: Create translate table.
+    model = ad.model
+    if (model.startswith(AOSP_PREFIX)):
+        model = model[len(AOSP_PREFIX):]
+    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.
+    """
+    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 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,
+        voice_or_data)
+
+
+def is_droid_in_rat_family_for_subscription(log,
+                                            ad,
+                                            sub_id,
+                                            rat_family,
+                                            voice_or_data=None):
+    return is_droid_in_rat_family_list_for_subscription(
+        log, ad, sub_id, [rat_family], voice_or_data)
+
+
+def is_droid_in_rat_familiy_list(log, ad, rat_family_list, voice_or_data=None):
+    return is_droid_in_rat_family_list_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), rat_family_list,
+        voice_or_data)
+
+
+def is_droid_in_rat_family_list_for_subscription(log,
+                                                 ad,
+                                                 sub_id,
+                                                 rat_family_list,
+                                                 voice_or_data=None):
+    service_list = [NETWORK_SERVICE_DATA, NETWORK_SERVICE_VOICE]
+    if voice_or_data:
+        service_list = [voice_or_data]
+
+    for service in service_list:
+        nw_rat = get_network_rat_for_subscription(log, ad, sub_id, service)
+        if nw_rat == RAT_UNKNOWN or not is_valid_rat(nw_rat):
+            continue
+        if rat_family_from_rat(nw_rat) in rat_family_list:
+            return True
+    return False
+
+
+def is_droid_in_network_generation(log, ad, nw_gen, voice_or_data):
+    """Checks if a droid in expected network generation ("2g", "3g" or "4g").
+
+    Args:
+        log: log object.
+        ad: android device.
+        nw_gen: expected generation "4g", "3g", "2g".
+        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.
+
+    Returns:
+        True if droid in expected network generation. Otherwise False.
+    """
+    return is_droid_in_network_generation_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), nw_gen, voice_or_data)
+
+
+def is_droid_in_network_generation_for_subscription(log, ad, sub_id, nw_gen,
+                                                    voice_or_data):
+    """Checks if a droid in expected network generation ("2g", "3g" or "4g").
+
+    Args:
+        log: log object.
+        ad: android device.
+        nw_gen: expected generation "4g", "3g", "2g".
+        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.
+
+    Returns:
+        True if droid in expected network generation. Otherwise False.
+    """
+    service_list = [NETWORK_SERVICE_DATA, NETWORK_SERVICE_VOICE]
+
+    if voice_or_data:
+        service_list = [voice_or_data]
+
+    for service in service_list:
+        nw_rat = get_network_rat_for_subscription(log, ad, sub_id, service)
+        ad.log.info("%s network rat is %s", service, nw_rat)
+        if nw_rat == RAT_UNKNOWN or not is_valid_rat(nw_rat):
+            continue
+
+        if rat_generation_from_rat(nw_rat) == nw_gen:
+            ad.log.info("%s network rat %s is expected %s", service, nw_rat,
+                        nw_gen)
+            return True
+        else:
+            ad.log.info("%s network rat %s is %s, does not meet expected %s",
+                        service, nw_rat, rat_generation_from_rat(nw_rat),
+                        nw_gen)
+            return False
+
+    return False
+
+
+def get_network_rat(log, ad, voice_or_data):
+    """Get current network type (Voice network type, or data network type)
+       for default subscription id
+
+    Args:
+        ad: Android Device Object
+        voice_or_data: Input parameter indicating to get voice network type or
+            data network type.
+
+    Returns:
+        Current voice/data network type.
+    """
+    return get_network_rat_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), voice_or_data)
+
+
+def get_network_rat_for_subscription(log, ad, sub_id, voice_or_data):
+    """Get current network type (Voice network type, or data network type)
+       for specified subscription id
+
+    Args:
+        ad: Android Device Object
+        sub_id: subscription ID
+        voice_or_data: Input parameter indicating to get voice network type or
+            data network type.
+
+    Returns:
+        Current voice/data network type.
+    """
+    if voice_or_data == NETWORK_SERVICE_VOICE:
+        ret_val = ad.droid.telephonyGetCurrentVoiceNetworkTypeForSubscription(
+            sub_id)
+    elif voice_or_data == NETWORK_SERVICE_DATA:
+        ret_val = ad.droid.telephonyGetCurrentDataNetworkTypeForSubscription(
+            sub_id)
+    else:
+        ret_val = ad.droid.telephonyGetNetworkTypeForSubscription(sub_id)
+
+    if ret_val is None:
+        log.error("get_network_rat(): Unexpected null return value")
+        return RAT_UNKNOWN
+    else:
+        return ret_val
+
+
+def get_network_gen(log, ad, voice_or_data):
+    """Get current network generation string (Voice network type, or data network type)
+
+    Args:
+        ad: Android Device Object
+        voice_or_data: Input parameter indicating to get voice network generation
+            or data network generation.
+
+    Returns:
+        Current voice/data network generation.
+    """
+    return get_network_gen_for_subscription(
+        log, ad, ad.droid.subscriptionGetDefaultSubId(), voice_or_data)
+
+
+def get_network_gen_for_subscription(log, ad, sub_id, voice_or_data):
+    """Get current network generation string (Voice network type, or data network type)
+
+    Args:
+        ad: Android Device Object
+        voice_or_data: Input parameter indicating to get voice network generation
+            or data network generation.
+
+    Returns:
+        Current voice/data network generation.
+    """
+    try:
+        return rat_generation_from_rat(
+            get_network_rat_for_subscription(log, ad, sub_id, voice_or_data))
+    except KeyError as e:
+        ad.log.error("KeyError %s", e)
+        return GEN_UNKNOWN
+
+
+def check_voice_mail_count(log, ad, voice_mail_count_before,
+                           voice_mail_count_after):
+    """function to check if voice mail count is correct after leaving a new voice message.
+    """
+    return get_voice_mail_count_check_function(get_operator_name(log, ad))(
+        voice_mail_count_before, voice_mail_count_after)
+
+
+def get_voice_mail_number(log, ad):
+    """function to get the voice mail number
+    """
+    voice_mail_number = get_voice_mail_check_number(get_operator_name(log, ad))
+    if voice_mail_number is None:
+        return get_phone_number(log, ad)
+    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.subscriptionGetDefaultVoiceSubId()
+        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.
+
+    Args:
+        log: log object
+        ad: android device object
+
+    Returns:
+        None
+    """
+    for sub_id, sub_info in ad.telephony["subscription"].items():
+        current_preference = \
+            ad.droid.telephonyGetPreferredNetworkTypesForSubscription(sub_id)
+        ad.log.debug("sub_id network preference is %s", current_preference)
+        try:
+            if current_preference not in get_allowable_network_preference(
+                    sub_info["operator"], sub_info["phone_type"]):
+                network_preference = network_preference_for_generation(
+                    GEN_4G, sub_info["operator"], sub_info["phone_type"])
+                ad.droid.telephonySetPreferredNetworkTypesForSubscription(
+                    network_preference, sub_id)
+        except KeyError:
+            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.
+
+    Args:
+        log: Log object.
+        ad: Android device object.
+        screen_on_time: screen on time.
+            This is optional, default value is MAX_SCREEN_ON_TIME.
+    Returns:
+        True if set successfully.
+    """
+    ad.droid.setScreenTimeout(screen_on_time)
+    return screen_on_time == ad.droid.getScreenTimeout()
+
+
+def set_phone_silent_mode(log, ad, silent_mode=True):
+    """Set phone silent mode.
+
+    Args:
+        log: Log object.
+        ad: Android device object.
+        silent_mode: set phone silent or not.
+            This is optional, default value is True (silent mode on).
+    Returns:
+        True if set successfully.
+    """
+    ad.droid.toggleRingerSilentMode(silent_mode)
+    ad.droid.setMediaVolume(0)
+    ad.droid.setVoiceCallVolume(0)
+    ad.droid.setAlarmVolume(0)
+    ad.adb.ensure_root()
+    ad.adb.shell("setprop ro.audio.silent 1", ignore_status=True)
+    ad.adb.shell("cmd notification set_dnd on", ignore_status=True)
+    return silent_mode == ad.droid.checkRingerSilentMode()
+
+
+def set_preferred_network_mode_pref(log,
+                                    ad,
+                                    sub_id,
+                                    network_preference,
+                                    timeout=WAIT_TIME_ANDROID_STATE_SETTLING):
+    """Set Preferred Network Mode for Sub_id
+    Args:
+        log: Log object.
+        ad: Android device object.
+        sub_id: Subscription ID.
+        network_preference: Network Mode Type
+    """
+    begin_time = get_device_epoch_time(ad)
+    if ad.droid.telephonyGetPreferredNetworkTypesForSubscription(
+            sub_id) == network_preference:
+        ad.log.info("Current ModePref for Sub %s is in %s", sub_id,
+                    network_preference)
+        return True
+    ad.log.info("Setting ModePref to %s for Sub %s", network_preference,
+                sub_id)
+    while timeout >= 0:
+        if ad.droid.telephonySetPreferredNetworkTypesForSubscription(
+                network_preference, sub_id):
+            return True
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+        timeout = timeout - WAIT_TIME_BETWEEN_STATE_CHECK
+    error_msg = "Failed to set sub_id %s PreferredNetworkType to %s" % (
+        sub_id, network_preference)
+    search_results = ad.search_logcat(
+        "REQUEST_SET_PREFERRED_NETWORK_TYPE error", begin_time=begin_time)
+    if search_results:
+        log_message = search_results[-1]["log_message"]
+        if "DEVICE_IN_USE" in log_message:
+            error_msg = "%s due to DEVICE_IN_USE" % error_msg
+        else:
+            error_msg = "%s due to %s" % (error_msg, log_message)
+    ad.log.error(error_msg)
+    return False
+
+
+def set_preferred_mode_for_5g(ad, sub_id=None, mode=None):
+    """Set Preferred Network Mode for 5G NSA
+    Args:
+        ad: Android device object.
+        sub_id: Subscription ID.
+        mode: 5G Network Mode Type
+    """
+    if sub_id is None:
+        sub_id = ad.droid.subscriptionGetDefaultSubId()
+    if mode is None:
+        mode = NETWORK_MODE_NR_LTE_GSM_WCDMA
+    return set_preferred_network_mode_pref(ad.log, ad, sub_id, mode)
+
+
+
+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.
+
+    Args:
+        log: Log object.
+        ad: Android device object.
+        value: True or False
+        sub_id :Subscription ID.
+
+    Returns:
+        True or False
+    """
+    if sub_id == INVALID_SUB_ID:
+        log.error("Invalid Subscription ID")
+        return False
+    ad.droid.telephonyAdjustPreciseCallStateListenLevelForSubscription(
+        "Foreground", value, sub_id)
+    ad.droid.telephonyAdjustPreciseCallStateListenLevelForSubscription(
+        "Ringing", value, sub_id)
+    ad.droid.telephonyAdjustPreciseCallStateListenLevelForSubscription(
+        "Background", value, sub_id)
+    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.
+
+    Args:
+        event: event to test. This event need to have <field>.
+        field: field to match.
+        value: value to match.
+
+    Returns:
+        True if <field> in "event" match <value>.
+        False otherwise.
+    """
+    return is_event_match_for_list(event, field, [value])
+
+
+def is_event_match_for_list(event, field, value_list):
+    """Return if <field> in "event" match any one of the value
+        in "value_list" or not.
+
+    Args:
+        event: event to test. This event need to have <field>.
+        field: field to match.
+        value_list: a list of value to match.
+
+    Returns:
+        True if <field> in "event" match one of the value in "value_list".
+        False otherwise.
+    """
+    try:
+        value_in_event = event['data'][field]
+    except KeyError:
+        return False
+    for value in value_list:
+        if value_in_event == value:
+            return True
+    return False
+
+
+def is_network_call_back_event_match(event, network_callback_id,
+                                     network_callback_event):
+    try:
+        return (
+            (network_callback_id == event['data'][NetworkCallbackContainer.ID])
+            and (network_callback_event == event['data']
+                 [NetworkCallbackContainer.NETWORK_CALLBACK_EVENT]))
+    except KeyError:
+        return False
+
+
+def is_build_id(log, ad, build_id):
+    """Return if ad's build id is the same as input parameter build_id.
+
+    Args:
+        log: log object.
+        ad: android device object.
+        build_id: android build id.
+
+    Returns:
+        True if ad's build id is the same as input parameter build_id.
+        False otherwise.
+    """
+    actual_bid = ad.droid.getBuildID()
+
+    ad.log.info("BUILD DISPLAY: %s", ad.droid.getBuildDisplay())
+    #In case we want to log more stuff/more granularity...
+    #log.info("{} BUILD ID:{} ".format(ad.serial, ad.droid.getBuildID()))
+    #log.info("{} BUILD FINGERPRINT: {} "
+    # .format(ad.serial), ad.droid.getBuildFingerprint())
+    #log.info("{} BUILD TYPE: {} "
+    # .format(ad.serial), ad.droid.getBuildType())
+    #log.info("{} BUILD NUMBER: {} "
+    # .format(ad.serial), ad.droid.getBuildNumber())
+    if actual_bid.upper() != build_id.upper():
+        ad.log.error("%s: Incorrect Build ID", ad.model)
+        return False
+    return True
+
+
+def is_uri_equivalent(uri1, uri2):
+    """Check whether two input uris match or not.
+
+    Compare Uris.
+        If Uris are tel URI, it will only take the digit part
+        and compare as phone number.
+        Else, it will just do string compare.
+
+    Args:
+        uri1: 1st uri to be compared.
+        uri2: 2nd uri to be compared.
+
+    Returns:
+        True if two uris match. Otherwise False.
+    """
+
+    #If either is None/empty we return false
+    if not uri1 or not uri2:
+        return False
+
+    try:
+        if uri1.startswith('tel:') and uri2.startswith('tel:'):
+            uri1_number = get_number_from_tel_uri(uri1)
+            uri2_number = get_number_from_tel_uri(uri2)
+            return check_phone_number_match(uri1_number, uri2_number)
+        else:
+            return uri1 == uri2
+    except AttributeError as e:
+        return False
+
+
+def get_call_uri(ad, call_id):
+    """Get call's uri field.
+
+    Get Uri for call_id in ad.
+
+    Args:
+        ad: android device object.
+        call_id: the call id to get Uri from.
+
+    Returns:
+        call's Uri if call is active and have uri field. None otherwise.
+    """
+    try:
+        call_detail = ad.droid.telecomCallGetDetails(call_id)
+        return call_detail["Handle"]["Uri"]
+    except:
+        return None
+
+
+def get_number_from_tel_uri(uri):
+    """Get Uri number from tel uri
+
+    Args:
+        uri: input uri
+
+    Returns:
+        If input uri is tel uri, return the number part.
+        else return None.
+    """
+    if uri.startswith('tel:'):
+        uri_number = ''.join(
+            i for i in urllib.parse.unquote(uri) if i.isdigit())
+        return uri_number
+    else:
+        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/"):
+            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]
+        if mask in ad.adb.shell("ls /vendor/etc/mdlog/"):
+            setattr(ad, "qxdm_log_path", DEFAULT_QXDM_LOG_PATH)
+            return "%s/%s" % ("/vendor/etc/mdlog/", 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))
+        for prop in ("persist.sys.modem.diag.mdlog",
+                     "persist.vendor.sys.modem.diag.mdlog"):
+            if ad.adb.getprop(prop):
+                # Enable qxdm always on if supported
+                for conf_path in ("/data/vendor/radio/diag_logs",
+                                  "/vendor/etc/mdlog"):
+                    if "diag.conf" in ad.adb.shell(
+                            "ls %s" % conf_path, ignore_status=True):
+                        conf_path = "%s/diag.conf" % conf_path
+                        ad.adb.shell('echo "%s" > %s' %
+                                     (ad.qxdm_logger_command, conf_path),
+                                     ignore_status=True)
+                        break
+                ad.adb.shell("setprop %s true" % prop, ignore_status=True)
+                break
+        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))
+    # 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))
+    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
+
+    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
+    """
+    status = True
+    if carriersettingsapk is None:
+        ad.log.warning("CarrierSettingsApk is not provided, aborting")
+        return False
+    ad.log.info("Push carriersettings apk to the Android device.")
+    android_apk_path = "/product/priv-app/CarrierSettings/CarrierSettings.apk"
+    ad.adb.push("%s %s" % (carriersettingsapk, android_apk_path))
+    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(30)
+            if ad.serial in list_fastboot_devices():
+                ad.log.info("Reboot in fastboot")
+                ad.fastboot.reboot()
+            ad.wait_for_boot_completion()
+            ad.root_adb()
+            if ad.is_sl4a_installed():
+                break
+            time.sleep(10)
+            break
+        except Exception as e:
+            ad.log.warning(e)
+            if i == attempts:
+                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()
+    return status
+
+
+def bring_up_sl4a(ad, attemps=3):
+    for i in range(attemps):
+        try:
+            droid, ed = ad.get_droid()
+            ed.start()
+            ad.log.info("Brought up new sl4a session")
+            break
+        except Exception as e:
+            if i < attemps - 1:
+                ad.log.info(e)
+                time.sleep(10)
+            else:
+                ad.log.error(e)
+                raise
+
+
+def reboot_device(ad, recover_sim_state=True):
+    sim_state = is_sim_ready(ad.log, ad)
+    ad.reboot()
+    if ad.qxdm_log:
+        start_qxdm_logger(ad)
+    ad.unlock_screen()
+    if recover_sim_state:
+        if not unlock_sim(ad):
+            ad.log.error("Unable to unlock SIM")
+            return False
+        if sim_state and not _wait_for_droid_in_state(
+                log, ad, MAX_WAIT_TIME_FOR_STATE_CHANGE, is_sim_ready):
+            ad.log.error("Sim state didn't reach pre-reboot ready state")
+            return False
+    return True
+
+
+def unlocking_device(ad, device_password=None):
+    """First unlock device attempt, required after reboot"""
+    ad.unlock_screen(device_password)
+    time.sleep(2)
+    ad.adb.wait_for_device(timeout=180)
+    if not ad.is_waiting_for_unlock_pin():
+        return True
+    else:
+        ad.unlock_screen(device_password)
+        time.sleep(2)
+        ad.adb.wait_for_device(timeout=180)
+        if ad.wait_for_window_ready():
+            return True
+    ad.log.error("Unable to unlock to user window")
+    return False
+
+
+def refresh_sl4a_session(ad):
+    try:
+        ad.droid.logI("Checking SL4A connection")
+        ad.log.debug("Existing sl4a session is active")
+        return True
+    except Exception as e:
+        ad.log.warning("Existing sl4a session is NOT active: %s", e)
+    try:
+        ad.terminate_all_sessions()
+    except Exception as e:
+        ad.log.info("terminate_all_sessions with error %s", e)
+    ad.ensure_screen_on()
+    ad.log.info("Open new sl4a connection")
+    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()
+    except Exception as e:
+        ad.log.error(e)
+        state = ad.adb.getprop("gsm.sim.state")
+    return state
+
+
+def is_sim_locked(ad):
+    return get_sim_state(ad) == SIM_STATE_PIN_REQUIRED
+
+
+def is_sim_lock_enabled(ad):
+    # TODO: add sl4a fascade to check if sim is locked
+    return getattr(ad, "is_sim_locked", False)
+
+
+def unlock_sim(ad):
+    #The puk and pin can be provided in testbed config file.
+    #"AndroidDevice": [{"serial": "84B5T15A29018214",
+    #                   "adb_logcat_param": "-b all",
+    #                   "puk": "12345678",
+    #                   "puk_pin": "1234"}]
+    if not is_sim_locked(ad):
+        return True
+    else:
+        ad.is_sim_locked = True
+    puk_pin = getattr(ad, "puk_pin", "1111")
+    try:
+        if not hasattr(ad, 'puk'):
+            ad.log.info("Enter SIM pin code")
+            ad.droid.telephonySupplyPin(puk_pin)
+        else:
+            ad.log.info("Enter PUK code and pin")
+            ad.droid.telephonySupplyPuk(ad.puk, puk_pin)
+    except:
+        # if sl4a is not available, use adb command
+        ad.unlock_screen(puk_pin)
+        if is_sim_locked(ad):
+            ad.unlock_screen(puk_pin)
+    time.sleep(30)
+    return not is_sim_locked(ad)
+
+
+def send_dialer_secret_code(ad, secret_code):
+    """Send dialer secret code.
+
+    ad: android device controller
+    secret_code: the secret code to be sent to dialer. the string between
+                 code prefix *#*# and code postfix #*#*. *#*#<xxx>#*#*
+    """
+    action = 'android.provider.Telephony.SECRET_CODE'
+    uri = 'android_secret_code://%s' % secret_code
+    intent = ad.droid.makeIntent(
+        action,
+        uri,
+        None,  # type
+        None,  # extras
+        None,  # categories,
+        None,  # packagename,
+        None,  # classname,
+        0x01000000)  # flags
+    ad.log.info('Issuing dialer secret dialer code: %s', secret_code)
+    ad.droid.sendBroadcastIntent(intent)
+
+
+def enable_radio_log_on(ad):
+    if ad.adb.getprop("persist.vendor.radio.adb_log_on") != "1":
+        ad.log.info("Enable radio adb_log_on and reboot")
+        adb_disable_verity(ad)
+        ad.adb.shell("setprop persist.vendor.radio.adb_log_on 1")
+        reboot_device(ad)
+
+
+def adb_disable_verity(ad):
+    if ad.adb.getprop("ro.boot.veritymode") == "enforcing":
+        ad.adb.disable_verity()
+        reboot_device(ad)
+        ad.adb.remount()
+
+
+def recover_build_id(ad):
+    build_fingerprint = ad.adb.getprop(
+        "ro.vendor.build.fingerprint") or ad.adb.getprop(
+            "ro.build.fingerprint")
+    if not build_fingerprint:
+        return
+    build_id = build_fingerprint.split("/")[3]
+    if ad.adb.getprop("ro.build.id") != build_id:
+        build_id_override(ad, build_id)
+
+def enable_privacy_usage_diagnostics(ad):
+    try:
+        ad.ensure_screen_on()
+        ad.send_keycode('HOME')
+    # open the UI page on which we need to enable the setting
+        cmd = ('am start -n com.google.android.gms/com.google.android.gms.'
+               'usagereporting.settings.UsageReportingActivity')
+        ad.adb.shell(cmd)
+    # perform the toggle
+        ad.send_keycode('TAB')
+        ad.send_keycode('ENTER')
+    except Exception:
+        ad.log.info("Unable to toggle Usage and Diagnostics")
+
+def build_id_override(ad, new_build_id=None, postfix=None):
+    build_fingerprint = ad.adb.getprop(
+        "ro.build.fingerprint") or ad.adb.getprop(
+            "ro.vendor.build.fingerprint")
+    if build_fingerprint:
+        build_id = build_fingerprint.split("/")[3]
+    else:
+        build_id = None
+    existing_build_id = ad.adb.getprop("ro.build.id")
+    if postfix is not None and postfix in build_id:
+        ad.log.info("Build id already contains %s", postfix)
+        return
+    if not new_build_id:
+        if postfix and build_id:
+            new_build_id = "%s.%s" % (build_id, postfix)
+    if not new_build_id or existing_build_id == new_build_id:
+        return
+    ad.log.info("Override build id %s with %s", existing_build_id,
+                new_build_id)
+    enable_privacy_usage_diagnostics(ad)
+    adb_disable_verity(ad)
+    ad.adb.remount()
+    if "backup.prop" not in ad.adb.shell("ls /sdcard/"):
+        ad.adb.shell("cp /system/build.prop /sdcard/backup.prop")
+    ad.adb.shell("cat /system/build.prop | grep -v ro.build.id > /sdcard/test.prop")
+    ad.adb.shell("echo ro.build.id=%s >> /sdcard/test.prop" % new_build_id)
+    ad.adb.shell("cp /sdcard/test.prop /system/build.prop")
+    reboot_device(ad)
+    ad.log.info("ro.build.id = %s", ad.adb.getprop("ro.build.id"))
+
+
+def enable_connectivity_metrics(ad):
+    cmds = [
+        "pm enable com.android.connectivity.metrics",
+        "am startservice -a com.google.android.gms.usagereporting.OPTIN_UR",
+        "am broadcast -a com.google.gservices.intent.action.GSERVICES_OVERRIDE"
+        " -e usagestats:connectivity_metrics:enable_data_collection 1",
+        "am broadcast -a com.google.gservices.intent.action.GSERVICES_OVERRIDE"
+        " -e usagestats:connectivity_metrics:telephony_snapshot_period_millis 180000"
+        # By default it turn on all modules
+        #"am broadcast -a com.google.gservices.intent.action.GSERVICES_OVERRIDE"
+        #" -e usagestats:connectivity_metrics:data_collection_bitmap 62"
+    ]
+    for cmd in cmds:
+        ad.adb.shell(cmd, ignore_status=True)
+
+
+def force_connectivity_metrics_upload(ad):
+    cmd = "cmd jobscheduler run --force com.android.connectivity.metrics %s"
+    for job_id in [2, 3, 5, 4, 1, 6]:
+        ad.adb.shell(cmd % job_id, ignore_status=True)
+
+
+def system_file_push(ad, src_file_path, dst_file_path):
+    """Push system file on a device.
+
+    Push system file need to change some system setting and remount.
+    """
+    cmd = "%s %s" % (src_file_path, dst_file_path)
+    out = ad.adb.push(cmd, timeout=300, ignore_status=True)
+    skip_sl4a = True if "sl4a.apk" in src_file_path else False
+    if "Read-only file system" in out:
+        ad.log.info("Change read-only file system")
+        adb_disable_verity(ad)
+        out = ad.adb.push(cmd, timeout=300, ignore_status=True)
+        if "Read-only file system" in out:
+            ad.reboot(skip_sl4a)
+            out = ad.adb.push(cmd, timeout=300, ignore_status=True)
+            if "error" in out:
+                ad.log.error("%s failed with %s", cmd, out)
+                return False
+            else:
+                ad.log.info("push %s succeed")
+                if skip_sl4a: ad.reboot(skip_sl4a)
+                return True
+        else:
+            return True
+    elif "error" in out:
+        return False
+    else:
+        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
+       It needs apn name as arg, and it will match with plmn id
+    """
+    try:
+        plmn_id = get_plmn_by_adb(ad)
+        out = ad.adb.shell("content query --uri content://telephony/carriers "
+                           "--where \"apn='%s' and numeric='%s'\"" %
+                           (pref_apn_name, plmn_id))
+        if "No result found" in out:
+            ad.log.warning("Cannot find APN %s on device", pref_apn_name)
+            return False
+        else:
+            apn_id = re.search(r'_id=(\d+)', out).group(1)
+            ad.log.info("APN ID is %s", apn_id)
+            ad.adb.shell("content insert --uri content:"
+                         "//telephony/carriers/preferapn --bind apn_id:i:%s" %
+                         (apn_id))
+            out = ad.adb.shell("content query --uri "
+                               "content://telephony/carriers/preferapn")
+            if "No result found" in out:
+                ad.log.error("Failed to set prefer APN %s", pref_apn_name)
+                return False
+            elif apn_id == re.search(r'_id=(\d+)', out).group(1):
+                ad.log.info("Preferred APN set to %s", pref_apn_name)
+                return True
+    except Exception as e:
+        ad.log.error("Exception while setting pref apn %s", e)
+        return True
+
+
+def check_apm_mode_on_by_serial(ad, serial_id):
+    try:
+        apm_check_cmd = "|".join(("adb -s %s shell dumpsys wifi" % serial_id,
+                                  "grep -i airplanemodeon", "cut -f2 -d ' '"))
+        output = exe_cmd(apm_check_cmd)
+        if output.decode("utf-8").split("\n")[0] == "true":
+            return True
+        else:
+            return False
+    except Exception as e:
+        ad.log.warning("Exception during check apm mode on %s", e)
+        return True
+
+
+def set_apm_mode_on_by_serial(ad, serial_id):
+    try:
+        cmd1 = "adb -s %s shell settings put global airplane_mode_on 1" % serial_id
+        cmd2 = "adb -s %s shell am broadcast -a android.intent.action.AIRPLANE_MODE" % serial_id
+        exe_cmd(cmd1)
+        exe_cmd(cmd2)
+    except Exception as e:
+        ad.log.warning("Exception during set apm mode on %s", e)
+        return True
+
+
+def print_radio_info(ad, extra_msg=""):
+    for prop in ("gsm.version.baseband", "persist.radio.ver_info",
+                 "persist.radio.cnv.ver_info"):
+        output = ad.adb.getprop(prop)
+        ad.log.info("%s%s = %s", extra_msg, prop, output)
+
+
+def wait_for_state(state_check_func,
+                   state,
+                   max_wait_time=MAX_WAIT_TIME_FOR_STATE_CHANGE,
+                   checking_interval=WAIT_TIME_BETWEEN_STATE_CHECK,
+                   *args,
+                   **kwargs):
+    while max_wait_time >= 0:
+        if state_check_func(*args, **kwargs) == state:
+            return True
+        time.sleep(checking_interval)
+        max_wait_time -= checking_interval
+    return False
+
+
+def power_off_sim(ad, sim_slot_id=None,
+                  timeout=MAX_WAIT_TIME_FOR_STATE_CHANGE):
+    try:
+        if sim_slot_id is None:
+            ad.droid.telephonySetSimPowerState(CARD_POWER_DOWN)
+            verify_func = ad.droid.telephonyGetSimState
+            verify_args = []
+        else:
+            ad.droid.telephonySetSimStateForSlotId(sim_slot_id,
+                                                   CARD_POWER_DOWN)
+            verify_func = ad.droid.telephonyGetSimStateForSlotId
+            verify_args = [sim_slot_id]
+    except Exception as e:
+        ad.log.error(e)
+        return False
+    while timeout > 0:
+        sim_state = verify_func(*verify_args)
+        if sim_state in (SIM_STATE_UNKNOWN, SIM_STATE_ABSENT):
+            ad.log.info("SIM slot is powered off, SIM state is %s", sim_state)
+            return True
+        timeout = timeout - WAIT_TIME_BETWEEN_STATE_CHECK
+        time.sleep(WAIT_TIME_BETWEEN_STATE_CHECK)
+    ad.log.warning("Fail to power off SIM slot, sim_state=%s",
+                   verify_func(*verify_args))
+    return False
+
+
+def power_on_sim(ad, sim_slot_id=None):
+    try:
+        if sim_slot_id is None:
+            ad.droid.telephonySetSimPowerState(CARD_POWER_UP)
+            verify_func = ad.droid.telephonyGetSimState
+            verify_args = []
+        else:
+            ad.droid.telephonySetSimStateForSlotId(sim_slot_id, CARD_POWER_UP)
+            verify_func = ad.droid.telephonyGetSimStateForSlotId
+            verify_args = [sim_slot_id]
+    except Exception as e:
+        ad.log.error(e)
+        return False
+    if wait_for_state(verify_func, SIM_STATE_READY,
+                      MAX_WAIT_TIME_FOR_STATE_CHANGE,
+                      WAIT_TIME_BETWEEN_STATE_CHECK, *verify_args):
+        ad.log.info("SIM slot is powered on, SIM state is READY")
+        return True
+    elif verify_func(*verify_args) == SIM_STATE_PIN_REQUIRED:
+        ad.log.info("SIM is pin locked")
+        return True
+    else:
+        ad.log.error("Fail to power on SIM slot")
+        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")))
+
+
+def synchronize_device_time(ad):
+    ad.adb.shell("put global auto_time 0", ignore_status=True)
+    try:
+        ad.adb.droid.setTime(get_current_epoch_time())
+    except Exception:
+        try:
+            ad.adb.shell("date `date +%m%d%H%M%G.%S`")
+        except Exception:
+            pass
+    try:
+        ad.adb.shell(
+            "am broadcast -a android.intent.action.TIME_SET",
+            ignore_status=True)
+    except Exception:
+        pass
+
+
+def revert_default_telephony_setting(ad):
+    toggle_airplane_mode_by_adb(ad.log, ad, True)
+    default_data_roaming = int(
+        ad.adb.getprop("ro.com.android.dataroaming") == 'true')
+    default_network_preference = int(
+        ad.adb.getprop("ro.telephony.default_network"))
+    ad.log.info("Default data roaming %s, network preference %s",
+                default_data_roaming, default_network_preference)
+    new_data_roaming = abs(default_data_roaming - 1)
+    new_network_preference = abs(default_network_preference - 1)
+    ad.log.info(
+        "Set data roaming = %s, mobile data = 0, network preference = %s",
+        new_data_roaming, new_network_preference)
+    ad.adb.shell("settings put global mobile_data 0")
+    ad.adb.shell("settings put global data_roaming %s" % new_data_roaming)
+    ad.adb.shell("settings put global preferred_network_mode %s" %
+                 new_network_preference)
+
+
+def verify_default_telephony_setting(ad):
+    ad.log.info("carrier_config: %s", dumpsys_carrier_config(ad))
+    default_data_roaming = int(
+        ad.adb.getprop("ro.com.android.dataroaming") == 'true')
+    default_network_preference = int(
+        ad.adb.getprop("ro.telephony.default_network"))
+    ad.log.info("Default data roaming %s, network preference %s",
+                default_data_roaming, default_network_preference)
+    data_roaming = int(ad.adb.shell("settings get global data_roaming"))
+    mobile_data = int(ad.adb.shell("settings get global mobile_data"))
+    network_preference = int(
+        ad.adb.shell("settings get global preferred_network_mode"))
+    airplane_mode = int(ad.adb.shell("settings get global airplane_mode_on"))
+    result = True
+    ad.log.info("data_roaming = %s, mobile_data = %s, "
+                "network_perference = %s, airplane_mode = %s", data_roaming,
+                mobile_data, network_preference, airplane_mode)
+    if airplane_mode:
+        ad.log.error("Airplane mode is on")
+        result = False
+    if data_roaming != default_data_roaming:
+        ad.log.error("Data roaming is %s, expecting %s", data_roaming,
+                     default_data_roaming)
+        result = False
+    if not mobile_data:
+        ad.log.error("Mobile data is off")
+        result = False
+    if network_preference != default_network_preference:
+        ad.log.error("preferred_network_mode is %s, expecting %s",
+                     network_preference, default_network_preference)
+        result = False
+    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")
+    log_screen_shot(ad, test_name)
+    ad.adb.shell("am start -n com.google.android.apps.messaging/com.google."
+                 "android.apps.messaging.ui.conversation.ConversationActivity"
+                 " -e conversation_id 1")
+    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")
+    if out and ":" in out:
+        version = out.split(':')[1].lstrip()
+    else:
+        version = "0"
+    ad.log.debug("Carrier Config Version is %s", version)
+    return version
+
+
+def get_carrier_config_version(ad):
+    out = ad.adb.shell("dumpsys carrier_config | grep version_string")
+    if out and "-" in out:
+        version = out.split('-')[1]
+    else:
+        version = "0"
+    ad.log.debug("Carrier Config Version is %s", version)
+    return version
+
+def get_er_db_id_version(ad):
+    out = ad.adb.shell("dumpsys activity service TelephonyDebugService | \
+                        grep -i \"Database Version\"")
+    if out and ":" in out:
+        version = out.split(':', 2)[2].lstrip()
+    else:
+        version = "0"
+    ad.log.debug("Emergency database Version is %s", version)
+    return version
+
+def get_database_content(ad):
+    out = ad.adb.shell("dumpsys activity service TelephonyDebugService | \
+                        egrep -i \EmergencyNumber:Number-54321")
+    if out:
+        return True
+    result = ad.adb.shell(r"dumpsys activity service TelephonyDebugService | \
+                egrep -i \updateOtaEmergencyNumberListDatabaseAndNotify")
+    ad.log.error("Emergency Number is incorrect. %s ", result)
+    return False
+
+def add_whitelisted_account(ad, user_account,user_password, retries=3):
+    if not ad.is_apk_installed("com.google.android.tradefed.account"):
+        ad.log.error("GoogleAccountUtil is not installed")
+        return False
+    for _ in range(retries):
+        ad.ensure_screen_on()
+        output = ad.adb.shell(
+            'am instrument -w -e account "%s@gmail.com" -e password '
+            '"%s" -e sync true -e wait-for-checkin false '
+            'com.google.android.tradefed.account/.AddAccount' %
+            (user_account, user_password))
+        if "result=SUCCESS" in output:
+            ad.log.info("Google account is added successfully")
+            return True
+    ad.log.error("Failed to add google account - %s", output)
+    return False
+
+
+def install_googleaccountutil_apk(ad, account_util):
+    ad.log.info("Install account_util %s", account_util)
+    ad.ensure_screen_on()
+    ad.adb.install("-r %s" % account_util, timeout=300, ignore_status=True)
+    time.sleep(3)
+    if not ad.is_apk_installed("com.google.android.tradefed.account"):
+        ad.log.info("com.google.android.tradefed.account is not installed")
+        return False
+    return True
+
+
+def install_googlefi_apk(ad, fi_util):
+    ad.log.info("Install fi_util %s", fi_util)
+    ad.ensure_screen_on()
+    ad.adb.install("-r -g --user 0 %s" % fi_util,
+                   timeout=300, ignore_status=True)
+    time.sleep(3)
+    if not check_fi_apk_installed(ad):
+        return False
+    return True
+
+
+def check_fi_apk_installed(ad):
+    if not ad.is_apk_installed("com.google.android.apps.tycho"):
+        ad.log.warning("com.google.android.apps.tycho is not installed")
+        return False
+    return True
+
+
+def add_google_account(ad, retries=3):
+    if not ad.is_apk_installed("com.google.android.tradefed.account"):
+        ad.log.error("GoogleAccountUtil is not installed")
+        return False
+    for _ in range(retries):
+        ad.ensure_screen_on()
+        output = ad.adb.shell(
+            'am instrument -w -e account "%s@gmail.com" -e password '
+            '"%s" -e sync true -e wait-for-checkin false '
+            'com.google.android.tradefed.account/.AddAccount' %
+            (ad.user_account, ad.user_password))
+        if "result=SUCCESS" in output:
+            ad.log.info("Google account is added successfully")
+            return True
+    ad.log.error("Failed to add google account - %s", output)
+    return False
+
+
+def remove_google_account(ad, retries=3):
+    if not ad.is_apk_installed("com.google.android.tradefed.account"):
+        ad.log.error("GoogleAccountUtil is not installed")
+        return False
+    for _ in range(retries):
+        ad.ensure_screen_on()
+        output = ad.adb.shell(
+            'am instrument -w '
+            'com.google.android.tradefed.account/.RemoveAccounts')
+        if "result=SUCCESS" in output:
+            ad.log.info("google account is removed successfully")
+            return True
+    ad.log.error("Fail to remove google account due to %s", output)
+    return False
+
+
+def my_current_screen_content(ad, content):
+    ad.adb.shell("uiautomator dump --window=WINDOW")
+    out = ad.adb.shell("cat /sdcard/window_dump.xml | grep -E '%s'" % content)
+    if not out:
+        ad.log.warning("NOT FOUND - %s", content)
+        return False
+    return True
+
+
+def activate_esim_using_suw(ad):
+    _START_SUW = ('am start -a android.intent.action.MAIN -n '
+                  'com.google.android.setupwizard/.SetupWizardTestActivity')
+    _STOP_SUW = ('am start -a com.android.setupwizard.EXIT')
+
+    toggle_airplane_mode(ad.log, ad, new_state=False, strict_checking=False)
+    ad.adb.shell("settings put system screen_off_timeout 1800000")
+    ad.ensure_screen_on()
+    ad.send_keycode("MENU")
+    ad.send_keycode("HOME")
+    for _ in range(3):
+        ad.log.info("Attempt %d - activating eSIM", (_ + 1))
+        ad.adb.shell(_START_SUW)
+        time.sleep(10)
+        log_screen_shot(ad, "start_suw")
+        for _ in range(4):
+            ad.send_keycode("TAB")
+            time.sleep(0.5)
+        ad.send_keycode("ENTER")
+        time.sleep(15)
+        log_screen_shot(ad, "activate_esim")
+        get_screen_shot_log(ad)
+        ad.adb.shell(_STOP_SUW)
+        time.sleep(5)
+        current_sim = get_sim_state(ad)
+        ad.log.info("Current SIM status is %s", current_sim)
+        if current_sim not in (SIM_STATE_ABSENT, SIM_STATE_UNKNOWN):
+            break
+    return True
+
+def activate_google_fi_account(ad, retries=10):
+    _FI_APK = "com.google.android.apps.tycho"
+    _FI_ACTIVATE_CMD = ('am start -c android.intent.category.DEFAULT -n '
+                        'com.google.android.apps.tycho/.AccountDetailsActivity --ez '
+                        'in_setup_wizard false --ez force_show_account_chooser '
+                        'false')
+    toggle_airplane_mode(ad.log, ad, new_state=False, strict_checking=False)
+    ad.adb.shell("settings put system screen_off_timeout 1800000")
+    page_match_dict = {
+       "SelectAccount" : "Choose an account to use",
+       "Setup" : "Activate Google Fi to use your device for calls",
+       "Switch" : "Switch to the Google Fi mobile network",
+       "WiFi" : "Fi to download your SIM",
+       "Connect" : "Connect to the Google Fi mobile network",
+       "Move" : "Move number",
+       "Data" : "first turn on mobile data",
+       "Activate" : "This takes a minute or two, sometimes longer",
+       "Welcome" : "Welcome to Google Fi",
+       "Account" : "Your current cycle ends in"
+    }
+    page_list = ["Account", "Setup", "WiFi", "Switch", "Connect",
+                 "Activate", "Move", "Welcome", "Data"]
+    for _ in range(retries):
+        ad.force_stop_apk(_FI_APK)
+        ad.ensure_screen_on()
+        ad.send_keycode("MENU")
+        ad.send_keycode("HOME")
+        ad.adb.shell(_FI_ACTIVATE_CMD)
+        time.sleep(15)
+        for page in page_list:
+            if my_current_screen_content(ad, page_match_dict[page]):
+                ad.log.info("Ready for Step %s", page)
+                log_screen_shot(ad, "fi_activation_step_%s" % page)
+                if page in ("Setup", "Switch", "Connect", "WiFi"):
+                    ad.send_keycode("TAB")
+                    ad.send_keycode("TAB")
+                    ad.send_keycode("ENTER")
+                    time.sleep(30)
+                elif page == "Move" or page == "SelectAccount":
+                    ad.send_keycode("TAB")
+                    ad.send_keycode("ENTER")
+                    time.sleep(5)
+                elif page == "Welcome":
+                    ad.send_keycode("TAB")
+                    ad.send_keycode("TAB")
+                    ad.send_keycode("TAB")
+                    ad.send_keycode("ENTER")
+                    ad.log.info("Activation SUCCESS using Fi App")
+                    time.sleep(5)
+                    ad.send_keycode("TAB")
+                    ad.send_keycode("TAB")
+                    ad.send_keycode("ENTER")
+                    return True
+                elif page == "Activate":
+                    time.sleep(60)
+                    if my_current_screen_content(ad, page_match_dict[page]):
+                        time.sleep(60)
+                elif page == "Account":
+                    return True
+                elif page == "Data":
+                    ad.log.error("Mobile Data is turned OFF by default")
+                    ad.send_keycode("TAB")
+                    ad.send_keycode("TAB")
+                    ad.send_keycode("ENTER")
+            else:
+                ad.log.info("NOT FOUND - Page %s", page)
+                log_screen_shot(ad, "fi_activation_step_%s_failure" % page)
+                get_screen_shot_log(ad)
+    return False
+
+
+def check_google_fi_activated(ad, retries=20):
+    if check_fi_apk_installed(ad):
+        _FI_APK = "com.google.android.apps.tycho"
+        _FI_LAUNCH_CMD = ("am start -n %s/%s.AccountDetailsActivity" \
+                          % (_FI_APK, _FI_APK))
+        toggle_airplane_mode(ad.log, ad, new_state=False, strict_checking=False)
+        ad.adb.shell("settings put system screen_off_timeout 1800000")
+        ad.force_stop_apk(_FI_APK)
+        ad.ensure_screen_on()
+        ad.send_keycode("HOME")
+        ad.adb.shell(_FI_LAUNCH_CMD)
+        time.sleep(10)
+        if not my_current_screen_content(ad, "Your current cycle ends in"):
+            ad.log.warning("Fi is not activated")
+            return False
+        ad.send_keycode("HOME")
+        return True
+    else:
+        ad.log.info("Fi Apk is not yet installed")
+        return False
+
+
+def cleanup_configupdater(ad):
+    cmds = ('rm -rf /data/data/com.google.android.configupdater/shared_prefs',
+            'rm /data/misc/carrierid/carrier_list.pb',
+            'setprop persist.telephony.test.carrierid.ota true',
+            'rm /data/user_de/0/com.android.providers.telephony/shared_prefs'
+            '/CarrierIdProvider.xml')
+    for cmd in cmds:
+        ad.log.info("Cleanup ConfigUpdater - %s", cmd)
+        ad.adb.shell(cmd, ignore_status=True)
+
+
+def pull_carrier_id_files(ad, carrier_id_path):
+    os.makedirs(carrier_id_path, exist_ok=True)
+    ad.log.info("Pull CarrierId Files")
+    cmds = ('/data/data/com.google.android.configupdater/shared_prefs/',
+            '/data/misc/carrierid/',
+            '/data/user_de/0/com.android.providers.telephony/shared_prefs/',
+            '/data/data/com.android.providers.downloads/databases/downloads.db')
+    for cmd in cmds:
+        cmd = cmd + " %s" % carrier_id_path
+        ad.adb.pull(cmd, timeout=30, ignore_status=True)
+
+
+def bring_up_connectivity_monitor(ad):
+    monitor_apk = None
+    for apk in ("com.google.telephonymonitor",
+                "com.google.android.connectivitymonitor"):
+        if ad.is_apk_installed(apk):
+            ad.log.info("apk %s is installed", apk)
+            monitor_apk = apk
+            break
+    if not monitor_apk:
+        ad.log.info("ConnectivityMonitor|TelephonyMonitor is not installed")
+        return False
+    toggle_connectivity_monitor_setting(ad, True)
+
+    if not ad.is_apk_running(monitor_apk):
+        ad.log.info("%s is not running", monitor_apk)
+        # Reboot
+        ad.log.info("reboot to bring up %s", monitor_apk)
+        reboot_device(ad)
+        for i in range(30):
+            if ad.is_apk_running(monitor_apk):
+                ad.log.info("%s is running after reboot", monitor_apk)
+                return True
+            else:
+                ad.log.info(
+                    "%s is not running after reboot. Wait and check again",
+                    monitor_apk)
+                time.sleep(30)
+        ad.log.error("%s is not running after reboot", monitor_apk)
+        return False
+    else:
+        ad.log.info("%s is running", monitor_apk)
+        return True
+
+
+def get_host_ip_address(ad):
+    cmd = "|".join(("ifconfig", "grep eno1 -A1", "grep inet", "awk '{$1=$1};1'", "cut -d ' ' -f 2"))
+    destination_ip = exe_cmd(cmd)
+    destination_ip = (destination_ip.decode("utf-8")).split("\n")[0]
+    ad.log.info("Host IP is %s", destination_ip)
+    return destination_ip
+
+
+def load_scone_cat_simulate_data(ad, simulate_data, sub_id=None):
+    """ Load radio simulate data
+    ad: android device controller
+    simulate_data: JSON object of simulate data
+    sub_id: RIL sub id, should be 0 or 1
+    """
+    ad.log.info("load_scone_cat_simulate_data")
+
+    #Check RIL sub id
+    if sub_id is None or sub_id > 1:
+        ad.log.error("The value of RIL sub_id should be 0 or 1")
+        return False
+
+    action = "com.google.android.apps.scone.cat.action.SetSimulateData"
+
+    #add sub id
+    simulate_data["SubId"] = sub_id
+    try:
+        #dump json
+        extra = json.dumps(simulate_data)
+        ad.log.info("send simulate_data=[%s]" % extra)
+        #send data
+        ad.adb.shell("am broadcast -a " + action + " --es simulate_data '" + extra + "'")
+    except Exception as e:
+        ad.log.error("Exception error to send CAT: %s", e)
+        return False
+
+    return True
+
+
+def load_scone_cat_data_from_file(ad, simulate_file_path, sub_id=None):
+    """ Load radio simulate data
+    ad: android device controller
+    simulate_file_path: JSON file of simulate data
+    sub_id: RIL sub id, should be 0 or 1
+    """
+    ad.log.info("load_radio_simulate_data_from_file from %s" % simulate_file_path)
+    radio_simulate_data = {}
+
+    #Check RIL sub id
+    if sub_id is None or sub_id > 1:
+        ad.log.error("The value of RIL sub_id should be 0 or 1")
+        raise ValueError
+
+    with open(simulate_file_path, 'r') as f:
+        try:
+            radio_simulate_data = json.load(f)
+        except Exception as e:
+            self.log.error("Exception error to load %s: %s", f, e)
+            return False
+
+    for item in radio_simulate_data:
+        result = load_scone_cat_simulate_data(ad, item, sub_id)
+        if result == False:
+            ad.log.error("Load CAT command fail")
+            return False
+        time.sleep(0.1)
+
+    return True
+
+
+def toggle_connectivity_monitor_setting(ad, state=True):
+    monitor_setting = ad.adb.getprop("persist.radio.enable_tel_mon")
+    ad.log.info("radio.enable_tel_mon setting is %s", monitor_setting)
+    current_state = True if monitor_setting == "user_enabled" else False
+    if current_state == state:
+        return True
+    elif state is None:
+        state = not current_state
+    expected_monitor_setting = "user_enabled" if state else "disabled"
+    cmd = "setprop persist.radio.enable_tel_mon %s" % expected_monitor_setting
+    ad.log.info("Toggle connectivity monitor by %s", cmd)
+    ad.adb.shell(
+        "am start -n com.android.settings/.DevelopmentSettings",
+        ignore_status=True)
+    ad.adb.shell(cmd)
+    monitor_setting = ad.adb.getprop("persist.radio.enable_tel_mon")
+    ad.log.info("radio.enable_tel_mon setting is %s", monitor_setting)
+    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.
+    """
+    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
+
+    operator_name = get_operator_name(log, ad)
+
+    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"
+        },
+        '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:
+        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")
+
+        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)
+
+    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"
+        },
+        '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"]
+    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)
+
+        _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")
+                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")
+
+        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.
+
+    The method requires the MDS app to be installed in the DUT.
+
+    Args:
+        log: logger object
+        ad: an android device
+
+    Return:
+        A tuple where the first element is an array array with the RSRP value
+        in Rx chain, and the second element is the transmitted power in dBm.
+        Values for invalid Rx / Tx chains are set to None.
+    """
+    cmd = ('am instrument -w -e request "80 00 e8 03 00 08 00 00 00" -e '
+           'response wait "com.google.mdstest/com.google.mdstest.instrument.'
+           'ModemCommandInstrumentation"')
+    output = ad.adb.shell(cmd)
+
+    if 'result=SUCCESS' not in output:
+        raise RuntimeError('Could not obtain Tx/Rx power levels from MDS. Is '
+                           'the MDS app installed?')
+
+    response = re.search(r"(?<=response=).+", output)
+
+    if not response:
+        raise RuntimeError('Invalid response from the MDS app:\n' + output)
+
+    # Obtain a list of bytes in hex format from the response string
+    response_hex = response.group(0).split(' ')
+
+    def get_bool(pos):
+        """ Obtain a boolean variable from the byte array. """
+        return response_hex[pos] == '01'
+
+    def get_int32(pos):
+        """ Obtain an int from the byte array. Bytes are printed in
+        little endian format."""
+        return struct.unpack(
+            '<i', bytearray.fromhex(''.join(response_hex[pos:pos + 4])))[0]
+
+    rx_power = []
+    RX_CHAINS = 4
+
+    for i in range(RX_CHAINS):
+        # Calculate starting position for the Rx chain data structure
+        start = 12 + i * 22
+
+        # The first byte in the data structure indicates if the rx chain is
+        # valid.
+        if get_bool(start):
+            rx_power.append(get_int32(start + 2) / 10)
+        else:
+            rx_power.append(None)
+
+    # Calculate the position for the tx chain data structure
+    tx_pos = 12 + RX_CHAINS * 22
+
+    tx_valid = get_bool(tx_pos)
+    if tx_valid:
+        tx_power = get_int32(tx_pos + 2) / -10
+    else:
+        tx_power = None
+
+    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.')
+        ad.adb.shell('settings put global auto_time 1')
+
+    elif (action == 'disable'):
+        ad.log.info('Disabling sync time from network.')
+        ad.adb.shell('settings put global auto_time 0')
+
+    time.sleep(WAIT_TIME_SYNC_DATE_TIME_FROM_NETWORK)
+
+def datetime_handle(ad, action, set_datetime_value='', get_year=False):
+    get_value = ''
+    if (action == 'get'):
+        if (get_year):
+            datetime_string = ad.adb.shell('date')
+            datetime_list = datetime_string.split()
+            try:
+                get_value = datetime_list[5]
+            except Exception as e:
+                self.log.error("Fail to get year from datetime: %s. " \
+                                "Exception error: %s", datetime_list
+                                , str(e))
+                raise signals.TestSkip("Fail to get year from datetime" \
+                                    ", the format is changed. Skip the test.")
+        else:
+            get_value = ad.adb.shell('date')
+
+    elif (action == 'set'):
+        ad.adb.shell('date %s' % set_datetime_value)
+        time.sleep(WAIT_TIME_SYNC_DATE_TIME_FROM_NETWORK)
+        ad.adb.shell('am broadcast -a android.intent.action.TIME_SET')
+
+    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,
+        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, 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()
+
+    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
+
+    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)
+    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=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, 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):
+    result = False
+    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 state_check_func():
+        result = True
+
+    if voice_sub_id_changed:
+        set_incoming_voice_sub_id(ad, current_sub_id)
+
+    return result
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
new file mode 100644
index 0000000..85eecd3
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_video_utils.py
@@ -0,0 +1,949 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+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
+from acts_contrib.test_utils.tel.tel_defines import VT_STATE_RX_ENABLED
+from acts_contrib.test_utils.tel.tel_defines import VT_STATE_RX_PAUSED
+from acts_contrib.test_utils.tel.tel_defines import VT_STATE_TX_ENABLED
+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_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
+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 is_call_hd
+
+
+def phone_setup_video(log, ad, wfc_mode=WFC_MODE_DISABLED):
+    """Setup phone default sub_id to make video call
+
+    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 ad (default sub_id) is setup correctly and idle for video call.
+    """
+    return phone_setup_video_for_subscription(log, ad,
+                                              get_outgoing_voice_sub_id(ad),
+                                              wfc_mode)
+
+
+def phone_setup_video_for_subscription(log,
+                                       ad,
+                                       sub_id,
+                                       wfc_mode=WFC_MODE_DISABLED):
+    """Setup phone sub_id to make video call
+
+    Args:
+        log: log object.
+        ad: android device object
+        sub_id: ad's sub id.
+        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 ad (sub_id) is setup correctly and idle for video call.
+    """
+
+    toggle_airplane_mode(log, ad, False)
+    if not set_wfc_mode(log, ad, wfc_mode):
+        log.error("{} WFC mode failed to be set to {}.".format(
+            ad.serial, wfc_mode))
+        return False
+    toggle_volte(log, ad, True)
+
+    if not ensure_network_generation(
+            log, ad, GEN_4G, voice_or_data=NETWORK_SERVICE_DATA):
+        log.error("{} voice not in LTE mode.".format(ad.serial))
+        return False
+
+    return phone_idle_video_for_subscription(log, ad, sub_id)
+
+
+def phone_idle_video(log, ad):
+    """Return if phone (default sub_id) is idle for video call.
+
+    Args:
+        log: log object.
+        ad: android device object
+
+    Returns:
+        True if ad is idle for video call.
+    """
+    return phone_idle_video_for_subscription(log, ad,
+                                             get_outgoing_voice_sub_id(ad))
+
+
+def phone_idle_video_for_subscription(log, ad, sub_id):
+    """Return if phone (sub_id) is idle for video call.
+
+    Args:
+        log: log object.
+        ad: android device object
+        sub_id: ad's sub id
+
+    Returns:
+        True if ad (sub_id) is idle for video call.
+    """
+
+    if not wait_for_network_generation(log, ad, GEN_4G):
+        log.error("{} voice not in LTE mode.".format(ad.serial))
+        return False
+
+    if not wait_for_video_enabled(log, ad, MAX_WAIT_TIME_VOLTE_ENABLED):
+        log.error(
+            "{} failed to <report video calling enabled> within {}s.".format(
+                ad.serial, MAX_WAIT_TIME_VOLTE_ENABLED))
+        return False
+    return True
+
+
+def is_phone_in_call_video(log, ad):
+    """Return if ad is in a video call (in expected video state).
+
+    Args:
+        log: log object.
+        ad: android device object
+        video_state: Expected Video call state.
+            This is optional, if it's None,
+            then TX_ENABLED/RX_ENABLED/BIDIRECTIONAL video call state will
+            return True.
+
+    Returns:
+        True if ad (for sub_id) is in a video call (in expected video state).
+    """
+    return is_phone_in_call_video_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_video_for_subscription(log, ad, sub_id, video_state=None):
+    """Return if ad (for sub_id) is in a video call (in expected video state).
+    Args:
+        log: log object.
+        ad: android device object
+        sub_id: device sub_id
+        video_state: Expected Video call state.
+            This is optional, if it's None,
+            then TX_ENABLED/RX_ENABLED/BIDIRECTIONAL video call state will
+            return True.
+
+    Returns:
+        True if ad is in a video call (in expected video state).
+    """
+
+    if video_state is None:
+        log.info("Verify if {}(subid {}) in video call.".format(
+            ad.serial, sub_id))
+    if not ad.droid.telecomIsInCall():
+        log.error("{} not in call.".format(ad.serial))
+        return False
+    call_list = ad.droid.telecomCallGetCallIds()
+    for call in call_list:
+        state = ad.droid.telecomCallVideoGetState(call)
+        if video_state is None:
+            if {
+                    VT_STATE_AUDIO_ONLY: False,
+                    VT_STATE_TX_ENABLED: True,
+                    VT_STATE_TX_PAUSED: True,
+                    VT_STATE_RX_ENABLED: True,
+                    VT_STATE_RX_PAUSED: True,
+                    VT_STATE_BIDIRECTIONAL: True,
+                    VT_STATE_BIDIRECTIONAL_PAUSED: True,
+                    VT_STATE_STATE_INVALID: False
+            }[state]:
+                return True
+        else:
+            if state == video_state:
+                return True
+        log.info("Non-Video-State: {}".format(state))
+    log.error("Phone not in video call. Call list: {}".format(call_list))
+    return False
+
+
+def is_phone_in_call_viwifi_for_subscription(log, ad, sub_id,
+                                             video_state=None):
+    """Return if ad (for sub_id) is in a viwifi call (in expected video state).
+    Args:
+        log: log object.
+        ad: android device object
+        sub_id: device sub_id
+        video_state: Expected Video call state.
+            This is optional, if it's None,
+            then TX_ENABLED/RX_ENABLED/BIDIRECTIONAL video call state will
+            return True.
+
+    Returns:
+        True if ad is in a video call (in expected video state).
+    """
+
+    if video_state is None:
+        log.info("Verify if {}(subid {}) in video call.".format(
+            ad.serial, sub_id))
+    if not ad.droid.telecomIsInCall():
+        log.error("{} not in call.".format(ad.serial))
+        return False
+    nw_type = get_network_rat(log, ad, NETWORK_SERVICE_DATA)
+    if nw_type != RAT_IWLAN:
+        ad.log.error("Data rat on: %s. Expected: iwlan", nw_type)
+        return False
+    if not is_wfc_enabled(log, ad):
+        ad.log.error("WiFi Calling feature bit is False.")
+        return False
+    call_list = ad.droid.telecomCallGetCallIds()
+    for call in call_list:
+        state = ad.droid.telecomCallVideoGetState(call)
+        if video_state is None:
+            if {
+                    VT_STATE_AUDIO_ONLY: False,
+                    VT_STATE_TX_ENABLED: True,
+                    VT_STATE_TX_PAUSED: True,
+                    VT_STATE_RX_ENABLED: True,
+                    VT_STATE_RX_PAUSED: True,
+                    VT_STATE_BIDIRECTIONAL: True,
+                    VT_STATE_BIDIRECTIONAL_PAUSED: True,
+                    VT_STATE_STATE_INVALID: False
+            }[state]:
+                return True
+        else:
+            if state == video_state:
+                return True
+        ad.log.info("Non-Video-State: %s", state)
+    ad.log.error("Phone not in video call. Call list: %s", call_list)
+    return False
+
+
+def is_phone_in_call_video_bidirectional(log, ad):
+    """Return if phone in bi-directional video call.
+
+    Args:
+        log: log object.
+        ad: android device object
+
+    Returns:
+        True if phone in bi-directional video call.
+    """
+    return is_phone_in_call_video_bidirectional_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_video_bidirectional_for_subscription(log, ad, sub_id):
+    """Return if phone in bi-directional video call for subscription id.
+
+    Args:
+        log: log object.
+        ad: android device object
+        sub_id: subscription id.
+
+    Returns:
+        True if phone in bi-directional video call.
+    """
+    log.info("Verify if {}(subid {}) in bi-directional video call.".format(
+        ad.serial, sub_id))
+    return is_phone_in_call_video_for_subscription(log, ad, sub_id,
+                                                   VT_STATE_BIDIRECTIONAL)
+
+
+def is_phone_in_call_viwifi_bidirectional(log, ad):
+    """Return if phone in bi-directional viwifi call.
+
+    Args:
+        log: log object.
+        ad: android device object
+
+    Returns:
+        True if phone in bi-directional viwifi call.
+    """
+    return is_phone_in_call_viwifi_bidirectional_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_viwifi_bidirectional_for_subscription(log, ad, sub_id):
+    """Return if phone in bi-directional viwifi call for subscription id.
+
+    Args:
+        log: log object.
+        ad: android device object
+        sub_id: subscription id.
+
+    Returns:
+        True if phone in bi-directional viwifi call.
+    """
+    ad.log.info("Verify if subid %s in bi-directional video call.", sub_id)
+    return is_phone_in_call_viwifi_for_subscription(log, ad, sub_id,
+                                                    VT_STATE_BIDIRECTIONAL)
+
+
+def is_phone_in_call_video_tx_enabled(log, ad):
+    """Return if phone in tx_enabled video call.
+
+    Args:
+        log: log object.
+        ad: android device object
+
+    Returns:
+        True if phone in tx_enabled video call.
+    """
+    return is_phone_in_call_video_tx_enabled_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_video_tx_enabled_for_subscription(log, ad, sub_id):
+    """Return if phone in tx_enabled video call for subscription id.
+
+    Args:
+        log: log object.
+        ad: android device object
+        sub_id: subscription id.
+
+    Returns:
+        True if phone in tx_enabled video call.
+    """
+    log.info("Verify if {}(subid {}) in tx_enabled video call.".format(
+        ad.serial, sub_id))
+    return is_phone_in_call_video_for_subscription(log, ad, sub_id,
+                                                   VT_STATE_TX_ENABLED)
+
+
+def is_phone_in_call_video_rx_enabled(log, ad):
+    """Return if phone in rx_enabled video call.
+
+    Args:
+        log: log object.
+        ad: android device object
+
+    Returns:
+        True if phone in rx_enabled video call.
+    """
+    return is_phone_in_call_video_rx_enabled_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_video_rx_enabled_for_subscription(log, ad, sub_id):
+    """Return if phone in rx_enabled video call for subscription id.
+
+    Args:
+        log: log object.
+        ad: android device object
+        sub_id: subscription id.
+
+    Returns:
+        True if phone in rx_enabled video call.
+    """
+    log.info("Verify if {}(subid {}) in rx_enabled video call.".format(
+        ad.serial, sub_id))
+    return is_phone_in_call_video_for_subscription(log, ad, sub_id,
+                                                   VT_STATE_RX_ENABLED)
+
+
+def is_phone_in_call_voice_hd(log, ad):
+    """Return if phone in hd voice call.
+
+    Args:
+        log: log object.
+        ad: android device object
+
+    Returns:
+        True if phone in hd voice call.
+    """
+    return is_phone_in_call_voice_hd_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_voice_hd_for_subscription(log, ad, sub_id):
+    """Return if phone in hd voice call for subscription id.
+
+    Args:
+        log: log object.
+        ad: android device object
+        sub_id: subscription id.
+
+    Returns:
+        True if phone in hd voice call.
+    """
+    log.info("Verify if {}(subid {}) in hd voice call.".format(
+        ad.serial, sub_id))
+    if not ad.droid.telecomIsInCall():
+        log.error("{} not in call.".format(ad.serial))
+        return False
+    for call in ad.droid.telecomCallGetCallIds():
+        state = ad.droid.telecomCallVideoGetState(call)
+        if (state == VT_STATE_AUDIO_ONLY and is_call_hd(log, ad, call)):
+            return True
+        log.info("Non-HDAudio-State: {}, property: {}".format(
+            state, ad.droid.telecomCallGetProperties(call)))
+    return False
+
+
+def initiate_video_call(log, ad_caller, callee_number):
+    """Make phone call from caller to callee.
+
+    Args:
+        log: logging handle
+        ad_caller: Caller android device object.
+        callee_number: Callee phone number.
+
+    Returns:
+        result: if phone call is placed successfully.
+    """
+    return initiate_call(log, ad_caller, callee_number, video=True)
+
+
+def wait_and_answer_video_call(log,
+                               ad,
+                               incoming_number=None,
+                               video_state=VT_STATE_BIDIRECTIONAL,
+                               incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND):
+    """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_video_call_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad), incoming_number, video_state,
+        incall_ui_display)
+
+
+def wait_and_answer_video_call_for_subscription(
+        log,
+        ad,
+        sub_id,
+        incoming_number=None,
+        video_state=VT_STATE_BIDIRECTIONAL,
+        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND):
+    """Wait for an incoming call on specified subscription and
+       accepts the call.
+
+    Args:
+        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
+    """
+    return wait_and_answer_call_for_subscription(
+        log,
+        ad,
+        sub_id,
+        incoming_number=None,
+        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND,
+        video_state=video_state)
+
+
+def video_call_setup_teardown(log,
+                              ad_caller,
+                              ad_callee,
+                              ad_hangup=None,
+                              video_state=VT_STATE_BIDIRECTIONAL,
+                              verify_caller_func=None,
+                              verify_callee_func=None,
+                              wait_time_in_call=WAIT_TIME_IN_CALL,
+                              incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND):
+    """ Call process, including make a phone call from caller,
+    accept from callee, and hang up. The call is on default 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.
+        video_state: video state for VT call.
+            Optional. Default value is VT_STATE_BIDIRECTIONAL
+        verify_caller_func: func_ptr to verify caller in correct mode
+            Optional. Default is None
+        verify_callee_func: func_ptr to verify callee in correct mode
+            Optional. Default is None
+        wait_time_in_call: wait time during call.
+            Optional. Default is WAIT_TIME_IN_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.
+
+    Returns:
+        True if call process without any error.
+        False if error happened.
+
+    """
+    return video_call_setup_teardown_for_subscription(
+        log, ad_caller, ad_callee, get_outgoing_voice_sub_id(ad_caller),
+        get_incoming_voice_sub_id(ad_callee), ad_hangup, video_state,
+        verify_caller_func, verify_callee_func, wait_time_in_call,
+        incall_ui_display)
+
+
+def video_call_setup_teardown_for_subscription(
+        log,
+        ad_caller,
+        ad_callee,
+        subid_caller,
+        subid_callee,
+        ad_hangup=None,
+        video_state=VT_STATE_BIDIRECTIONAL,
+        verify_caller_func=None,
+        verify_callee_func=None,
+        wait_time_in_call=WAIT_TIME_IN_CALL,
+        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND):
+    """ 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.
+        video_state: video state for VT call.
+            Optional. Default value is VT_STATE_BIDIRECTIONAL
+        verify_caller_func: func_ptr to verify caller in correct mode
+            Optional. Default is None
+        verify_callee_func: func_ptr to verify callee in correct mode
+            Optional. Default is None
+        wait_time_in_call: wait time during call.
+            Optional. Default is WAIT_TIME_IN_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.
+
+    Returns:
+        True if call process without any error.
+        False if error happened.
+
+    """
+    return call_setup_teardown_for_subscription(
+        log,
+        ad_caller,
+        ad_callee,
+        subid_caller,
+        subid_callee,
+        ad_hangup=ad_hangup,
+        verify_caller_func=verify_caller_func,
+        verify_callee_func=verify_callee_func,
+        wait_time_in_call=wait_time_in_call,
+        incall_ui_display=incall_ui_display,
+        video_state=video_state)
+
+
+def video_call_setup(log,
+                     ad_caller,
+                     ad_callee,
+                     video_state=VT_STATE_BIDIRECTIONAL,
+                     incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND):
+    """ Call process, including make a phone call from caller,
+    accept from callee, and hang up. The call is on default 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.
+        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 call process without any error.
+        False if error happened.
+
+    """
+    return video_call_setup_for_subscription(
+        log, ad_caller, ad_callee, get_outgoing_voice_sub_id(ad_caller),
+        get_incoming_voice_sub_id(ad_callee), video_state, incall_ui_display)
+
+
+def video_call_setup_for_subscription(
+        log,
+        ad_caller,
+        ad_callee,
+        subid_caller,
+        subid_callee,
+        video_state=VT_STATE_BIDIRECTIONAL,
+        incall_ui_display=INCALL_UI_DISPLAY_FOREGROUND):
+    """ 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.
+        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 call process without any error.
+        False if error happened.
+
+    """
+    return call_setup_teardown_for_subscription(
+        log,
+        ad_caller,
+        ad_callee,
+        subid_caller,
+        subid_callee,
+        ad_hangup=None,
+        incall_ui_display=incall_ui_display,
+        video_state=video_state)
+
+
+def video_call_modify_video(log,
+                            ad_requester,
+                            call_id_requester,
+                            ad_responder,
+                            call_id_responder,
+                            video_state_request,
+                            video_quality_request=VT_VIDEO_QUALITY_DEFAULT,
+                            video_state_response=None,
+                            video_quality_response=None,
+                            verify_func_between_request_and_response=None):
+    """Modifies an ongoing call to change the video_call state
+
+    Args:
+        log: logger object
+        ad_requester: android_device object of the requester
+        call_id_requester: the call_id of the call placing the modify request
+        ad_requester: android_device object of the responder
+        call_id_requester: the call_id of the call receiving the modify request
+        video_state_request: the requested video state
+        video_quality_request: the requested video quality, defaults to
+            QUALITY_DEFAULT
+        video_state_response: the responded video state or, or (default)
+            match the request if None
+        video_quality_response: the responded video quality, or (default)
+            match the request if None
+
+    Returns:
+        A call_id corresponding to the first call in the state, or None
+    """
+
+    if not video_state_response:
+        video_state_response = video_state_request
+    if not video_quality_response:
+        video_quality_response = video_quality_request
+
+    cur_video_state = ad_requester.droid.telecomCallVideoGetState(
+        call_id_requester)
+
+    log.info("State change request from {} to {} requested".format(
+        cur_video_state, video_state_request))
+
+    if cur_video_state == video_state_request:
+        return True
+
+    ad_responder.ed.clear_events(
+        EventTelecomVideoCallSessionModifyRequestReceived)
+
+    ad_responder.droid.telecomCallVideoStartListeningForEvent(
+        call_id_responder, EVENT_VIDEO_SESSION_MODIFY_REQUEST_RECEIVED)
+
+    ad_requester.droid.telecomCallVideoSendSessionModifyRequest(
+        call_id_requester, video_state_request, video_quality_request)
+
+    try:
+        request_event = ad_responder.ed.pop_event(
+            EventTelecomVideoCallSessionModifyRequestReceived,
+            MAX_WAIT_TIME_VIDEO_SESSION_EVENT)
+        log.info(request_event)
+    except Empty:
+        log.error("Failed to receive SessionModifyRequest!")
+        return False
+    finally:
+        ad_responder.droid.telecomCallVideoStopListeningForEvent(
+            call_id_responder, EVENT_VIDEO_SESSION_MODIFY_REQUEST_RECEIVED)
+
+    if (verify_func_between_request_and_response
+            and not verify_func_between_request_and_response()):
+        log.error("verify_func_between_request_and_response failed.")
+        return False
+
+    # TODO: b/26291165 Replace with reducing the volume as we want
+    # to test route switching
+    ad_requester.droid.telecomCallSetAudioRoute(AUDIO_ROUTE_EARPIECE)
+
+    ad_requester.droid.telecomCallVideoStartListeningForEvent(
+        call_id_requester, EVENT_VIDEO_SESSION_MODIFY_RESPONSE_RECEIVED)
+
+    ad_responder.droid.telecomCallVideoSendSessionModifyResponse(
+        call_id_responder, video_state_response, video_quality_response)
+
+    try:
+        response_event = ad_requester.ed.pop_event(
+            EventTelecomVideoCallSessionModifyResponseReceived,
+            MAX_WAIT_TIME_VIDEO_SESSION_EVENT)
+        log.info(response_event)
+    except Empty:
+        log.error("Failed to receive SessionModifyResponse!")
+        return False
+    finally:
+        ad_requester.droid.telecomCallVideoStopListeningForEvent(
+            call_id_requester, EVENT_VIDEO_SESSION_MODIFY_RESPONSE_RECEIVED)
+
+    # TODO: b/26291165 Replace with reducing the volume as we want
+    # to test route switching
+    ad_responder.droid.telecomCallSetAudioRoute(AUDIO_ROUTE_EARPIECE)
+
+    return True
+
+
+def is_call_id_in_video_state(log, ad, call_id, video_state):
+    """Return is the call_id is in expected video_state
+
+    Args:
+        log: logger object
+        ad: android_device object
+        call_id: call id
+        video_state: valid VIDEO_STATE
+
+    Returns:
+        True is call_id in expected video_state; False if not.
+    """
+    return video_state == ad.droid.telecomCallVideoGetState(call_id)
+
+
+def get_call_id_in_video_state(log, ad, video_state):
+    """Gets the first call reporting a given video_state
+        from among the active calls
+
+    Args:
+        log: logger object
+        ad: android_device object
+        video_state: valid VIDEO_STATE
+
+    Returns:
+        A call_id corresponding to the first call in the state, or None
+    """
+
+    if not ad.droid.telecomIsInCall():
+        log.error("{} not in call.".format(ad.serial))
+        return None
+    for call in ad.droid.telecomCallGetCallIds():
+        if is_call_id_in_video_state(log, ad, call, video_state):
+            return call
+    return None
+
+
+def video_call_downgrade(log,
+                         ad_requester,
+                         call_id_requester,
+                         ad_responder,
+                         call_id_responder,
+                         video_state_request=None,
+                         video_quality_request=VT_VIDEO_QUALITY_DEFAULT):
+    """Downgrade Video call to video_state_request.
+    Send telecomCallVideoSendSessionModifyRequest from ad_requester.
+    Get video call state from ad_requester and ad_responder.
+    Verify video calls states are correct and downgrade succeed.
+
+    Args:
+        log: logger object
+        ad_requester: android_device object of the requester
+        call_id_requester: the call_id of the call placing the modify request
+        ad_requester: android_device object of the responder
+        call_id_requester: the call_id of the call receiving the modify request
+        video_state_request: the requested downgrade video state
+            This parameter is optional. If this parameter is None:
+                if call_id_requester current is bi-directional, will downgrade to RX_ENABLED
+                if call_id_requester current is RX_ENABLED, will downgrade to AUDIO_ONLY
+        video_quality_request: the requested video quality, defaults to
+            QUALITY_DEFAULT
+    Returns:
+        True if downgrade succeed.
+    """
+    if (call_id_requester is None) or (call_id_responder is None):
+        log.error("call_id_requester: {}, call_id_responder: {}".format(
+            call_id_requester, call_id_responder))
+        return False
+    current_video_state_requester = ad_requester.droid.telecomCallVideoGetState(
+        call_id_requester)
+    if video_state_request is None:
+        if (current_video_state_requester == VT_STATE_BIDIRECTIONAL or
+                current_video_state_requester == VT_STATE_BIDIRECTIONAL_PAUSED
+            ):
+            video_state_request = VT_STATE_RX_ENABLED
+        elif (current_video_state_requester == VT_STATE_TX_ENABLED
+              or current_video_state_requester == VT_STATE_TX_PAUSED):
+            video_state_request = VT_STATE_AUDIO_ONLY
+        else:
+            log.error("Can Not Downgrade. ad: {}, current state {}".format(
+                ad_requester.serial, current_video_state_requester))
+            return False
+    expected_video_state_responder = {
+        VT_STATE_AUDIO_ONLY: VT_STATE_AUDIO_ONLY,
+        VT_STATE_RX_ENABLED: VT_STATE_TX_ENABLED
+    }[video_state_request]
+
+    ad_requester.droid.telecomCallVideoStartListeningForEvent(
+        call_id_requester, EVENT_VIDEO_SESSION_MODIFY_RESPONSE_RECEIVED)
+
+    ad_requester.droid.telecomCallVideoSendSessionModifyRequest(
+        call_id_requester, video_state_request, video_quality_request)
+
+    try:
+        response_event = ad_requester.ed.pop_event(
+            EventTelecomVideoCallSessionModifyResponseReceived,
+            MAX_WAIT_TIME_VIDEO_SESSION_EVENT)
+        log.info(response_event)
+    except Empty:
+        log.error("Failed to receive SessionModifyResponse!")
+        return False
+    finally:
+        ad_requester.droid.telecomCallVideoStopListeningForEvent(
+            call_id_requester, EVENT_VIDEO_SESSION_MODIFY_RESPONSE_RECEIVED)
+
+    time.sleep(WAIT_TIME_ANDROID_STATE_SETTLING)
+    # TODO: b/26291165 Replace with reducing the volume as we want
+    # to test route switching
+    ad_requester.droid.telecomCallSetAudioRoute(AUDIO_ROUTE_EARPIECE)
+    ad_responder.droid.telecomCallSetAudioRoute(AUDIO_ROUTE_EARPIECE)
+
+    time.sleep(WAIT_TIME_IN_CALL)
+    if video_state_request != ad_requester.droid.telecomCallVideoGetState(
+            call_id_requester):
+        log.error("requester not in correct state. expected:{}, current:{}"
+                  .format(video_state_request,
+                          ad_requester.droid.telecomCallVideoGetState(
+                              call_id_requester)))
+        return False
+    if (expected_video_state_responder !=
+            ad_responder.droid.telecomCallVideoGetState(call_id_responder)):
+        log.error(
+            "responder not in correct state. expected:{}, current:{}".format(
+                expected_video_state_responder,
+                ad_responder.droid.telecomCallVideoGetState(
+                    call_id_responder)))
+        return False
+
+    return True
+
+
+def verify_video_call_in_expected_state(log, ad, call_id, call_video_state,
+                                        call_state):
+    """Return True if video call is in expected video state and call state.
+
+    Args:
+        log: logger object
+        ad: android_device object
+        call_id: ad's call id
+        call_video_state: video state to validate.
+        call_state: call state to validate.
+
+    Returns:
+        True if video call is in expected video state and call state.
+    """
+    if not is_call_id_in_video_state(log, ad, call_id, call_video_state):
+        log.error("Call is not in expected {} state. Current state {}".format(
+            call_video_state, ad.droid.telecomCallVideoGetState(call_id)))
+        return False
+    if ad.droid.telecomCallGetCallState(call_id) != call_state:
+        log.error("Call is not in expected {} state. Current state {}".format(
+            call_state, ad.droid.telecomCallGetCallState(call_id)))
+        return False
+    return True
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
new file mode 100644
index 0000000..7868d86
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/tel_voice_utils.py
@@ -0,0 +1,1970 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+import time
+from acts import signals
+from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
+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 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 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 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_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_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 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 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 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_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
+
+CallResult = TelephonyVoiceTestResult.CallResult.Value
+
+
+def two_phone_call_leave_voice_mail(
+        log,
+        caller,
+        caller_idle_func,
+        caller_in_call_check_func,
+        callee,
+        callee_idle_func,
+        wait_time_in_call=WAIT_TIME_LEAVE_VOICE_MAIL):
+    """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:
+        caller: caller android device object.
+        caller_idle_func: function to check caller's idle state.
+        caller_in_call_check_func: function to check caller's in-call state.
+        callee: callee android device object.
+        callee_idle_func: function to check callee's idle state.
+        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
+    """
+
+    ads = [caller, callee]
+
+    # Make sure phones are idle.
+    ensure_phones_idle(log, ads)
+    if caller_idle_func and not caller_idle_func(log, caller):
+        caller.log.error("Caller Failed to Reselect")
+        return False
+    if callee_idle_func and not callee_idle_func(log, callee):
+        callee.log.error("Callee Failed to Reselect")
+        return False
+
+    # TODO: b/26337871 Need to use proper API to check phone registered.
+    time.sleep(WAIT_TIME_BETWEEN_REG_AND_CALL)
+
+    # Make call and leave a message.
+    if not call_reject_leave_message(
+            log, caller, callee, caller_in_call_check_func, wait_time_in_call):
+        log.error("make a call and leave a message failed.")
+        return False
+    return True
+
+
+def two_phone_call_short_seq(log,
+                             phone_a,
+                             phone_a_idle_func,
+                             phone_a_in_call_check_func,
+                             phone_b,
+                             phone_b_idle_func,
+                             phone_b_in_call_check_func,
+                             call_sequence_func=None,
+                             wait_time_in_call=WAIT_TIME_IN_CALL):
+    """Call process short sequence.
+    1. Ensure phone idle and in idle_func check return True.
+    2. Call from PhoneA to PhoneB, accept on PhoneB.
+    3. Check phone state, hangup on PhoneA.
+    4. Ensure phone idle and in idle_func check return True.
+    5. Call from PhoneA to PhoneB, accept on PhoneB.
+    6. Check phone state, hangup on PhoneB.
+
+    Args:
+        phone_a: PhoneA's android device object.
+        phone_a_idle_func: function to check PhoneA's idle state.
+        phone_a_in_call_check_func: function to check PhoneA's in-call state.
+        phone_b: PhoneB's android device object.
+        phone_b_idle_func: function to check PhoneB's idle state.
+        phone_b_in_call_check_func: function to check PhoneB's in-call state.
+        call_sequence_func: default parameter, not implemented.
+        wait_time_in_call: time to wait in call.
+            This is optional, default is WAIT_TIME_IN_CALL
+
+    Returns:
+        TelResultWrapper which will evaluate as False if error.
+    """
+    ads = [phone_a, phone_b]
+
+    call_params = [
+        (ads[0], ads[1], ads[0], phone_a_in_call_check_func,
+         phone_b_in_call_check_func),
+        (ads[0], ads[1], ads[1], phone_a_in_call_check_func,
+         phone_b_in_call_check_func),
+    ]
+
+    tel_result = TelResultWrapper(CallResult('SUCCESS'))
+    for param in call_params:
+        # Make sure phones are idle.
+        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 TelResultWrapper(CallResult('CALL_SETUP_FAILURE'))
+        if phone_b_idle_func and not phone_b_idle_func(log, phone_b):
+            phone_b.log.error("Phone B Failed to Reselect")
+            return TelResultWrapper(CallResult('CALL_SETUP_FAILURE'))
+
+        # TODO: b/26337871 Need to use proper API to check phone registered.
+        time.sleep(WAIT_TIME_BETWEEN_REG_AND_CALL)
+
+        # Make call.
+        log.info("---> Call test: %s to %s <---", param[0].serial,
+                 param[1].serial)
+        tel_result = call_setup_teardown(
+                log, *param, wait_time_in_call=wait_time_in_call)
+        if not tel_result:
+            log.error("Call Iteration Failed")
+            break
+
+    return tel_result
+
+def two_phone_call_msim_short_seq(log,
+                             phone_a,
+                             phone_a_idle_func,
+                             phone_a_in_call_check_func,
+                             phone_b,
+                             phone_b_idle_func,
+                             phone_b_in_call_check_func,
+                             call_sequence_func=None,
+                             wait_time_in_call=WAIT_TIME_IN_CALL):
+    """Call process short sequence.
+    1. Ensure phone idle and in idle_func check return True.
+    2. Call from PhoneA to PhoneB, accept on PhoneB.
+    3. Check phone state, hangup on PhoneA.
+    4. Ensure phone idle and in idle_func check return True.
+    5. Call from PhoneA to PhoneB, accept on PhoneB.
+    6. Check phone state, hangup on PhoneB.
+    Args:
+        phone_a: PhoneA's android device object.
+        phone_a_idle_func: function to check PhoneA's idle state.
+        phone_a_in_call_check_func: function to check PhoneA's in-call state.
+        phone_b: PhoneB's android device object.
+        phone_b_idle_func: function to check PhoneB's idle state.
+        phone_b_in_call_check_func: function to check PhoneB's in-call state.
+        call_sequence_func: default parameter, not implemented.
+        wait_time_in_call: time to wait in call.
+            This is optional, default is WAIT_TIME_IN_CALL
+    Returns:
+        True: if call sequence succeed.
+        False: for errors
+    """
+    ads = [phone_a, phone_b]
+    call_params = [
+        (ads[0], ads[1], ads[0], phone_a_in_call_check_func,
+         phone_b_in_call_check_func),
+        (ads[0], ads[1], ads[1], phone_a_in_call_check_func,
+         phone_b_in_call_check_func),
+    ]
+    for param in call_params:
+        # Make sure phones are idle.
+        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
+        if phone_b_idle_func and not phone_b_idle_func(log, phone_b):
+            phone_b.log.error("Phone B Failed to Reselect")
+            return False
+        # TODO: b/26337871 Need to use proper API to check phone registered.
+        time.sleep(WAIT_TIME_BETWEEN_REG_AND_CALL)
+        # Make call.
+        log.info("--> Call test: %s to %s <--", phone_a.serial, phone_b.serial)
+        slots = 2
+        for slot in range(slots):
+            set_subid_for_outgoing_call(
+                            ads[0], get_subid_from_slot_index(log,ads[0],slot))
+            set_subid_for_outgoing_call(
+                            ads[1], get_subid_from_slot_index(log,ads[1],slot))
+            time.sleep(WAIT_TIME_BETWEEN_REG_AND_CALL)
+            if not call_setup_teardown(log, *param,slot_id_callee = slot,
+                                       wait_time_in_call=wait_time_in_call):
+                log.error("Call Iteration Failed")
+                return False
+            if not call_setup_teardown(log, *param,slot_id_callee = 1-slot,
+                                       wait_time_in_call=wait_time_in_call):
+                log.error("Call Iteration Failed")
+                return False
+    return True
+
+def two_phone_call_long_seq(log,
+                            phone_a,
+                            phone_a_idle_func,
+                            phone_a_in_call_check_func,
+                            phone_b,
+                            phone_b_idle_func,
+                            phone_b_in_call_check_func,
+                            call_sequence_func=None,
+                            wait_time_in_call=WAIT_TIME_IN_CALL):
+    """Call process long sequence.
+    1. Ensure phone idle and in idle_func check return True.
+    2. Call from PhoneA to PhoneB, accept on PhoneB.
+    3. Check phone state, hangup on PhoneA.
+    4. Ensure phone idle and in idle_func check return True.
+    5. Call from PhoneA to PhoneB, accept on PhoneB.
+    6. Check phone state, hangup on PhoneB.
+    7. Ensure phone idle and in idle_func check return True.
+    8. Call from PhoneB to PhoneA, accept on PhoneA.
+    9. Check phone state, hangup on PhoneA.
+    10. Ensure phone idle and in idle_func check return True.
+    11. Call from PhoneB to PhoneA, accept on PhoneA.
+    12. Check phone state, hangup on PhoneB.
+
+    Args:
+        phone_a: PhoneA's android device object.
+        phone_a_idle_func: function to check PhoneA's idle state.
+        phone_a_in_call_check_func: function to check PhoneA's in-call state.
+        phone_b: PhoneB's android device object.
+        phone_b_idle_func: function to check PhoneB's idle state.
+        phone_b_in_call_check_func: function to check PhoneB's in-call state.
+        call_sequence_func: default parameter, not implemented.
+        wait_time_in_call: time to wait in call.
+            This is optional, default is WAIT_TIME_IN_CALL
+
+    Returns:
+        TelResultWrapper which will evaluate as False if error.
+
+    """
+    ads = [phone_a, phone_b]
+
+    call_params = [
+        (ads[0], ads[1], ads[0], phone_a_in_call_check_func,
+         phone_b_in_call_check_func),
+        (ads[0], ads[1], ads[1], phone_a_in_call_check_func,
+         phone_b_in_call_check_func),
+        (ads[1], ads[0], ads[0], phone_b_in_call_check_func,
+         phone_a_in_call_check_func),
+        (ads[1], ads[0], ads[1], phone_b_in_call_check_func,
+         phone_a_in_call_check_func),
+    ]
+
+    tel_result = TelResultWrapper(CallResult('SUCCESS'))
+    for param in call_params:
+        # Make sure phones are idle.
+        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 TelResultWrapper(CallResult('CALL_SETUP_FAILURE'))
+        if phone_b_idle_func and not phone_b_idle_func(log, phone_b):
+            phone_b.log.error("Phone B Failed to Reselect")
+            return TelResultWrapper(CallResult('CALL_SETUP_FAILURE'))
+
+        # TODO: b/26337871 Need to use proper API to check phone registered.
+        time.sleep(WAIT_TIME_BETWEEN_REG_AND_CALL)
+
+        # Make call.
+        log.info("---> Call test: %s to %s <---", param[0].serial,
+                 param[1].serial)
+        tel_result = call_setup_teardown(
+                log, *param, wait_time_in_call=wait_time_in_call)
+        if not tel_result:
+            log.error("Call Iteration Failed")
+            break
+
+    return tel_result
+
+def two_phone_call_msim_for_slot(log,
+                             phone_a,
+                             phone_a_slot,
+                             phone_a_idle_func,
+                             phone_a_in_call_check_func,
+                             phone_b,
+                             phone_b_slot,
+                             phone_b_idle_func,
+                             phone_b_in_call_check_func,
+                             call_sequence_func=None,
+                             wait_time_in_call=WAIT_TIME_IN_CALL,
+                             retry=2):
+    """Call process between 2 phones with specific slot.
+    1. Ensure phone idle and in idle_func    check return True.
+    2. Call from PhoneA to PhoneB, accept on PhoneB.
+    3. Check phone state, hangup on PhoneA.
+    4. Ensure phone idle and in idle_func check return True.
+    5. Call from PhoneA to PhoneB, accept on PhoneB.
+    6. Check phone state, hangup on PhoneB.
+
+    Args:
+        phone_a: PhoneA's android device object.
+        phone_a_slot: 0 or 1 (pSIM or eSIM)
+        phone_a_idle_func: function to check PhoneA's idle state.
+        phone_a_in_call_check_func: function to check PhoneA's in-call state.
+        phone_b: PhoneB's android device object.
+        phone_b_slot: 0 or 1 (pSIM or eSIM)
+        phone_b_idle_func: function to check PhoneB's idle state.
+        phone_b_in_call_check_func: function to check PhoneB's in-call state.
+        call_sequence_func: default parameter, not implemented.
+        wait_time_in_call: time to wait in call.
+            This is optional, default is WAIT_TIME_IN_CALL
+        retry: times of retry if call_setup_teardown failed.
+
+    Returns:
+        True: if call sequence succeed.
+        False: for errors
+    """
+    ads = [phone_a, phone_b]
+
+    call_params = [
+        (ads[0], ads[1], ads[0], phone_a_in_call_check_func,
+         phone_b_in_call_check_func),
+        (ads[0], ads[1], ads[1], phone_a_in_call_check_func,
+         phone_b_in_call_check_func),
+    ]
+
+    tel_result = TelResultWrapper(CallResult('SUCCESS'))
+    for param in call_params:
+        # Make sure phones are idle.
+        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 TelResultWrapper(CallResult('CALL_SETUP_FAILURE'))
+        if phone_b_idle_func and not phone_b_idle_func(log, phone_b):
+            phone_b.log.error("Phone B Failed to Reselect")
+            return TelResultWrapper(CallResult('CALL_SETUP_FAILURE'))
+
+        # TODO: b/26337871 Need to use proper API to check phone registered.
+        time.sleep(WAIT_TIME_BETWEEN_REG_AND_CALL)
+
+        # Make call.
+        log.info("--> Call test: %s slot %s to %s slot %s <--", phone_a.serial,
+            phone_a_slot, phone_b.serial, phone_b_slot)
+
+        mo_default_voice_subid = get_subid_from_slot_index(log,ads[0],
+            phone_a_slot)
+        if mo_default_voice_subid == INVALID_SUB_ID:
+            log.warning("Sub ID of MO (%s) slot %s is invalid.", phone_a.serial,
+                phone_a_slot)
+            return TelResultWrapper(CallResult('CALL_SETUP_FAILURE'))
+        set_subid_for_outgoing_call(
+                            ads[0], mo_default_voice_subid)
+
+        mt_default_voice_subid = get_subid_from_slot_index(log,ads[1],
+            phone_b_slot)
+        if mt_default_voice_subid == INVALID_SUB_ID:
+            log.warning("Sub ID of MT (%s) slot %s is invalid.", phone_b.serial,
+                phone_b_slot)
+            return TelResultWrapper(CallResult('CALL_SETUP_FAILURE'))
+
+        tel_result = call_setup_teardown(
+            log,
+            *param,
+            slot_id_callee=phone_b_slot,
+            wait_time_in_call=wait_time_in_call)
+
+        while not tel_result:
+            if retry <= 0:
+                log.error("Call Iteration failed.")
+                break
+            else:
+                log.info("RERUN call_setup_teardown.")
+                tel_result = call_setup_teardown(
+                    log,
+                    *param,
+                    slot_id_callee=phone_b_slot,
+                    wait_time_in_call=wait_time_in_call)
+
+            retry = retry - 1
+
+    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.
+
+    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
+
+def phone_setup_iwlan(log,
+                      ad,
+                      is_airplane_mode,
+                      wfc_mode,
+                      wifi_ssid=None,
+                      wifi_pwd=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.
+    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)
+
+
+def phone_setup_iwlan_for_subscription(log,
+                                       ad,
+                                       sub_id,
+                                       is_airplane_mode,
+                                       wfc_mode,
+                                       wifi_ssid=None,
+                                       wifi_pwd=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.
+    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.")
+    toggle_airplane_mode(log, ad, is_airplane_mode, strict_checking=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(log, ad, wfc_mode):
+        ad.log.error("Unable to set WFC mode to %s.", wfc_mode)
+        return False
+    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):
+    """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
+
+    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):
+        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):
+    """Setup phone for CSFB call test.
+
+    Setup Phone to be in 4G mode.
+    Disabled VoLTE.
+
+    Args:
+        log: log object
+        ad: Android device object.
+
+    Returns:
+        True if setup successfully.
+        False for errors.
+    """
+    return phone_setup_csfb_for_subscription(log, ad,
+                                             get_outgoing_voice_sub_id(ad))
+
+
+def phone_setup_csfb_for_subscription(log, ad, sub_id):
+    """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.
+
+    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 not phone_setup_4g_for_subscription(log, ad, sub_id):
+        ad.log.error("Failed to set to 4G data.")
+        return False
+
+    toggle_volte_for_subscription(log, ad, sub_id, False)
+
+    if not ensure_network_generation_for_subscription(
+            log, ad, sub_id, GEN_4G, 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)
+
+
+def phone_setup_volte(log, ad):
+    """Setup VoLTE enable.
+
+    Args:
+        log: log object
+        ad: android device object.
+
+    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))
+
+
+def phone_setup_volte_for_subscription(log, ad, sub_id):
+    """Setup VoLTE enable for subscription id.
+    Args:
+        log: log object
+        ad: android device object.
+        sub_id: subscription id.
+    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 not phone_setup_4g_for_subscription(log, ad, sub_id):
+        ad.log.error("Failed to set to 4G data.")
+        return False
+    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)
+
+
+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):
+    """Return if phone is idle for VoLTE call test for subscription id.
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    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):
+    """Return if phone is idle for CSFB call test for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    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):
+    """Return if phone is in VoLTE call.
+
+    Args:
+        ad: Android device object.
+    """
+    return is_phone_in_call_volte_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_volte_for_subscription(log, ad, sub_id):
+    """Return if phone is in VoLTE call for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    if not ad.droid.telecomIsInCall():
+        ad.log.error("Not in call.")
+        return False
+    nw_type = get_network_rat_for_subscription(log, ad, sub_id,
+                                               NETWORK_SERVICE_VOICE)
+    if nw_type != RAT_LTE:
+        ad.log.error("Voice rat on: %s. Expected: LTE", nw_type)
+        return False
+    return True
+
+
+def is_phone_in_call_csfb(log, ad):
+    """Return if phone is in CSFB call.
+
+    Args:
+        ad: Android device object.
+    """
+    return is_phone_in_call_csfb_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_csfb_for_subscription(log, ad, sub_id):
+    """Return if phone is in CSFB call for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    if not ad.droid.telecomIsInCall():
+        ad.log.error("Not in call.")
+        return False
+    nw_type = get_network_rat_for_subscription(log, ad, sub_id,
+                                               NETWORK_SERVICE_VOICE)
+    if nw_type == RAT_LTE:
+        ad.log.error("Voice rat on: %s. Expected: not LTE", nw_type)
+        return False
+    return True
+
+
+def is_phone_in_call_3g(log, ad):
+    """Return if phone is in 3G call.
+
+    Args:
+        ad: Android device object.
+    """
+    return is_phone_in_call_3g_for_subscription(log, ad,
+                                                get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_3g_for_subscription(log, ad, sub_id):
+    """Return if phone is in 3G call for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    if not ad.droid.telecomIsInCall():
+        ad.log.error("Not in call.")
+        return False
+    nw_gen = get_network_gen_for_subscription(log, ad, sub_id,
+                                              NETWORK_SERVICE_VOICE)
+    if nw_gen != GEN_3G:
+        ad.log.error("Voice rat on: %s. Expected: 3g", nw_gen)
+        return False
+    return True
+
+
+def is_phone_in_call_2g(log, ad):
+    """Return if phone is in 2G call.
+
+    Args:
+        ad: Android device object.
+    """
+    return is_phone_in_call_2g_for_subscription(log, ad,
+                                                get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_2g_for_subscription(log, ad, sub_id):
+    """Return if phone is in 2G call for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    if not ad.droid.telecomIsInCall():
+        ad.log.error("Not in call.")
+        return False
+    nw_gen = get_network_gen_for_subscription(log, ad, sub_id,
+                                              NETWORK_SERVICE_VOICE)
+    if nw_gen != GEN_2G:
+        ad.log.error("Voice rat on: %s. Expected: 2g", nw_gen)
+        return False
+    return True
+
+
+def is_phone_in_call_1x(log, ad):
+    """Return if phone is in 1x call.
+
+    Args:
+        ad: Android device object.
+    """
+    return is_phone_in_call_1x_for_subscription(log, ad,
+                                                get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_1x_for_subscription(log, ad, sub_id):
+    """Return if phone is in 1x call for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    if not ad.droid.telecomIsInCall():
+        ad.log.error("Not in call.")
+        return False
+    nw_type = get_network_rat_for_subscription(log, ad, sub_id,
+                                               NETWORK_SERVICE_VOICE)
+    if nw_type != RAT_1XRTT:
+        ad.log.error("Voice rat on: %s. Expected: 1xrtt", nw_type)
+        return False
+    return True
+
+
+def is_phone_in_call_wcdma(log, ad):
+    """Return if phone is in WCDMA call.
+
+    Args:
+        ad: Android device object.
+    """
+    return is_phone_in_call_wcdma_for_subscription(
+        log, ad, get_outgoing_voice_sub_id(ad))
+
+
+def is_phone_in_call_wcdma_for_subscription(log, ad, sub_id):
+    """Return if phone is in WCDMA call for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    # Currently checking 'umts'.
+    # Changes may needed in the future.
+    if not ad.droid.telecomIsInCall():
+        ad.log.error("Not in call.")
+        return False
+    nw_type = get_network_rat_for_subscription(log, ad, sub_id,
+                                               NETWORK_SERVICE_VOICE)
+    if nw_type != RAT_UMTS:
+        ad.log.error("%s voice rat on: %s. Expected: umts", nw_type)
+        return False
+    return True
+
+
+def is_phone_in_call_iwlan(log, ad, call_id=None):
+    """Return if phone is in WiFi call.
+
+    Args:
+        ad: Android device object.
+    """
+    if not ad.droid.telecomIsInCall():
+        ad.log.error("Not in call.")
+        return False
+    if not ad.droid.telephonyIsImsRegistered():
+        ad.log.info("IMS is not registered.")
+        return False
+    if not ad.droid.telephonyIsWifiCallingAvailable():
+        ad.log.info("IsWifiCallingAvailable is False")
+        return False
+    if not call_id:
+        call_ids = ad.droid.telecomCallGetCallIds()
+        if call_ids:
+            call_id = call_ids[-1]
+    if not call_id:
+        ad.log.error("Failed to get call id")
+        return False
+    else:
+        call_prop = ad.droid.telecomCallGetProperties(call_id)
+        if "WIFI" not in call_prop:
+            ad.log.info("callProperties = %s, expecting WIFI", call_prop)
+            return False
+    nw_type = get_network_rat(log, ad, NETWORK_SERVICE_DATA)
+    if nw_type != RAT_IWLAN:
+        ad.log.warning("Data rat on: %s. Expected: iwlan", nw_type)
+    return True
+
+
+def is_phone_in_call_not_iwlan(log, ad):
+    """Return if phone is in WiFi call for subscription id.
+
+    Args:
+        ad: Android device object.
+        sub_id: subscription id.
+    """
+    if not ad.droid.telecomIsInCall():
+        ad.log.error("Not in call.")
+        return False
+    nw_type = get_network_rat(log, ad, NETWORK_SERVICE_DATA)
+    if nw_type == RAT_IWLAN:
+        ad.log.error("Data rat on: %s. Expected: not iwlan", nw_type)
+        return False
+    if is_wfc_enabled(log, ad):
+        ad.log.error("WiFi Calling feature bit is True.")
+        return False
+    return True
+
+
+def swap_calls(log,
+               ads,
+               call_hold_id,
+               call_active_id,
+               num_swaps=1,
+               check_call_status=True):
+    """PhoneA in call with B and C. Swap active/holding call on PhoneA.
+
+    Swap call and check status on PhoneA.
+        (This step may have multiple times according to 'num_swaps'.)
+    Check if all 3 phones are 'in-call'.
+
+    Args:
+        ads: list of ad object, at least three need to pass in.
+            Swap operation will happen on ads[0].
+            ads[1] and ads[2] are call participants.
+        call_hold_id: id for the holding call in ads[0].
+            call_hold_id should be 'STATE_HOLDING' when calling this function.
+        call_active_id: id for the active call in ads[0].
+            call_active_id should be 'STATE_ACTIVE' when calling this function.
+        num_swaps: how many swap/check operations will be done before return.
+        check_call_status: This is optional. Default value is True.
+            If this value is True, then call status (active/hold) will be
+            be checked after each swap operation.
+
+    Returns:
+        If no error happened, return True, otherwise, return False.
+    """
+    if check_call_status:
+        # Check status before swap.
+        if ads[0].droid.telecomCallGetCallState(
+                call_active_id) != CALL_STATE_ACTIVE:
+            ads[0].log.error(
+                "Call_id:%s, state:%s, expected: STATE_ACTIVE", call_active_id,
+                ads[0].droid.telecomCallGetCallState(call_active_id))
+            return False
+        if ads[0].droid.telecomCallGetCallState(
+                call_hold_id) != CALL_STATE_HOLDING:
+            ads[0].log.error(
+                "Call_id:%s, state:%s, expected: STATE_HOLDING", call_hold_id,
+                ads[0].droid.telecomCallGetCallState(call_hold_id))
+            return False
+
+    i = 1
+    while (i <= num_swaps):
+        ads[0].log.info("swap_test %s: swap and check call status.", i)
+        ads[0].droid.telecomCallHold(call_active_id)
+        time.sleep(WAIT_TIME_IN_CALL)
+        # Swap object reference
+        call_active_id, call_hold_id = call_hold_id, call_active_id
+        if check_call_status:
+            # Check status
+            if ads[0].droid.telecomCallGetCallState(
+                    call_active_id) != CALL_STATE_ACTIVE:
+                ads[0].log.error(
+                    "Call_id:%s, state:%s, expected: STATE_ACTIVE",
+                    call_active_id,
+                    ads[0].droid.telecomCallGetCallState(call_active_id))
+                return False
+            if ads[0].droid.telecomCallGetCallState(
+                    call_hold_id) != CALL_STATE_HOLDING:
+                ads[0].log.error(
+                    "Call_id:%s, state:%s, expected: STATE_HOLDING",
+                    call_hold_id,
+                    ads[0].droid.telecomCallGetCallState(call_hold_id))
+                return False
+        # TODO: b/26296375 add voice check.
+
+        i += 1
+
+    #In the end, check all three phones are 'in-call'.
+    if not verify_incall_state(log, [ads[0], ads[1], ads[2]], True):
+        return False
+
+    return True
+
+
+def get_audio_route(log, ad):
+    """Gets the audio route for the active call
+
+    Args:
+        log: logger object
+        ad: android_device object
+
+    Returns:
+        Audio route string ["BLUETOOTH", "EARPIECE", "SPEAKER", "WIRED_HEADSET"
+            "WIRED_OR_EARPIECE"]
+    """
+
+    audio_state = ad.droid.telecomCallGetAudioState()
+    return audio_state["AudioRoute"]
+
+
+def set_audio_route(log, ad, route):
+    """Sets the audio route for the active call
+
+    Args:
+        log: logger object
+        ad: android_device object
+        route: string ["BLUETOOTH", "EARPIECE", "SPEAKER", "WIRED_HEADSET"
+            "WIRED_OR_EARPIECE"]
+
+    Returns:
+        If no error happened, return True, otherwise, return False.
+    """
+    ad.droid.telecomCallSetAudioRoute(route)
+    return True
+
+
+def is_property_in_call_properties(log, ad, call_id, expected_property):
+    """Return if the call_id has the expected property
+
+    Args:
+        log: logger object
+        ad: android_device object
+        call_id: call id.
+        expected_property: expected property.
+
+    Returns:
+        True if call_id has expected_property. False if not.
+    """
+    properties = ad.droid.telecomCallGetProperties(call_id)
+    return (expected_property in properties)
+
+
+def is_call_hd(log, ad, call_id):
+    """Return if the call_id is HD call.
+
+    Args:
+        log: logger object
+        ad: android_device object
+        call_id: call id.
+
+    Returns:
+        True if call_id is HD call. False if not.
+    """
+    return is_property_in_call_properties(log, ad, call_id,
+                                          CALL_PROPERTY_HIGH_DEF_AUDIO)
+
+
+def get_cep_conference_call_id(ad):
+    """Get CEP conference call id if there is an ongoing CEP conference call.
+
+    Args:
+        ad: android device object.
+
+    Returns:
+        call id for CEP conference call if there is an ongoing CEP conference call.
+        None otherwise.
+    """
+    for call in ad.droid.telecomCallGetCallIds():
+        if len(ad.droid.telecomCallGetCallChildren(call)) != 0:
+            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() == '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':
+        if only_return_fn:
+            return is_phone_in_call_volte
+        else:
+            return is_phone_in_call_volte(log, ad)
+
+    elif rat.lower() == 'csfb':
+        if only_return_fn:
+            return is_phone_in_call_csfb
+        else:
+            return is_phone_in_call_csfb(log, ad)
+
+    elif rat.lower() == '3g':
+        if only_return_fn:
+            return is_phone_in_call_3g
+        else:
+            return is_phone_in_call_3g(log, ad)
+
+    elif rat.lower() == 'wfc':
+        if only_return_fn:
+            return is_phone_in_call_iwlan
+        else:
+            return is_phone_in_call_iwlan(log, ad)
+    else:
+        return None
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/tel/twilio_client.py b/acts_tests/acts_contrib/test_utils/tel/twilio_client.py
new file mode 100644
index 0000000..fe13287
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/tel/twilio_client.py
@@ -0,0 +1,75 @@
+#! /usr/bin/env python3
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+"""The twilio client that initiates the call."""
+
+# TODO(danielvernon):Generalize client to use any service including phone.
+
+from twilio.rest import Client
+import yaml
+
+ACCOUNT_SID_KEY = 'account_sid'
+AUTH_TOKEN_KEY = 'auth_token'
+PHONE_NUMBER_KEY = 'phone_number'
+MUSIC_URL = 'http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient'
+URLS_KEY = 'urls'
+
+class TwilioClient:
+    """A class that wraps the Twilio Client class and can make calls.
+
+    Attributes:
+         __account_sid: The account id
+         __auth_token: The authentication token
+         __phone_number: The phoone number
+         urls: urls that will be played during call
+    """
+
+    def __init__(self, cfg_path):
+        self.__account_sid = None
+        self.__auth_token = None
+        self.__phone_number = None
+        self.urls = None
+
+        self.load_config(cfg_path)
+        self.client = Client(self.__account_sid, self.__auth_token)
+        self.call_handle = self.client.api.account.calls
+
+    def load_config(self, cfg_path):
+        """Loads the config for twilio.
+
+        Args:
+            cfg_path: A string, which is the path to the config file.
+        """
+        with open(cfg_path) as cfg_file:
+            cfg = yaml.load(cfg_file)
+            self.__account_sid = cfg[ACCOUNT_SID_KEY]
+            self.__auth_token = cfg[AUTH_TOKEN_KEY]
+            self.__phone_number = cfg[PHONE_NUMBER_KEY]
+            self.urls = cfg[URLS_KEY]
+
+    def call(self, to):
+        """Makes request to Twilio API to call number and play music.
+
+        Must be registered with Twilio account in order to use this client.
+        Arguments:
+            to: the number to call (str). example -- '+12345678910'
+
+        Returns:
+            call.sid: the sid of the call request.
+        """
+
+        call = self.call_handle.create(to=to, from_=self.__phone_number,
+                                       url=MUSIC_URL, status_callback=MUSIC_URL)
+        return call.sid
diff --git a/acts_tests/acts_contrib/test_utils/users/__init__.py b/acts_tests/acts_contrib/test_utils/users/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/users/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/users/users.py b/acts_tests/acts_contrib/test_utils/users/users.py
new file mode 100644
index 0000000..72f1892
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/users/users.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python3
+#
+#   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.
+#
+#
+# Defines utilities that can be used to create android user account
+
+import re
+import time
+import logging as log
+
+
+
+def get_all_users(android_device):
+    all_users = {}
+    out = android_device.adb.shell("pm list users")
+
+    for user in re.findall("UserInfo{(.*\d*\w):", out):
+        all = user.split(":")
+        all_users[all[1]] = all_users.get(all[1], all[0])
+    return all_users
+
+
+def create_new_user(android_device, user_name):
+    out = android_device.adb.shell("pm create-user {}".format(user_name))
+    return re.search("Success(.* (.*\d))", out).group(2)
+
+
+def switch_user(android_device, user_id):
+    prev_user = get_current_user(android_device)
+    android_device.adb.shell("am switch-user {}".format(user_id))
+    if not _wait_for_user_to_take_place(android_device, prev_user):
+        log.error("Failed to successfully switch user {}".format(user_id))
+        return False
+    return True
+
+
+def remove_user(android_device, user_id):
+    return "Success" in android_device.adb.shell("pm remove-user {}".format(user_id))
+
+
+def get_current_user(android_device):
+    out = android_device.adb.shell("dumpsys activity")
+    result = re.search("mCurrentUserId:(\d+)", out)
+    return result.group(1)
+
+
+def _wait_for_user_to_take_place(android_device, user_id, timeout=10):
+    start_time = time.time()
+    while (start_time + timeout) > time.time():
+        time.sleep(1)
+        if user_id != get_current_user(android_device):
+            return True
+    return False
diff --git a/acts_tests/acts_contrib/test_utils/wifi/OWNERS b/acts_tests/acts_contrib/test_utils/wifi/OWNERS
new file mode 100644
index 0000000..edb3e3e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/OWNERS
@@ -0,0 +1,6 @@
+bkleung@google.com
+dysu@google.com
+etancohen@google.com
+gmoturu@google.com
+rpius@google.com
+satk@google.com
diff --git a/acts_tests/acts_contrib/test_utils/wifi/RttPostFlightTest.py b/acts_tests/acts_contrib/test_utils/wifi/RttPostFlightTest.py
new file mode 100644
index 0000000..b058a07
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/RttPostFlightTest.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+import acts_contrib.test_utils.wifi.rpm_controller_utils as rutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts import asserts
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+SSID = "DO_NOT_CONNECT"
+TIMEOUT = 60
+WAIT_TIME = 10
+
+class RttPostFlightTest(WifiBaseTest):
+    """Turns off 802.11mc AP after RTT tests."""
+
+    def setup_class(self):
+        super().setup_class()
+        self.dut = self.android_devices[0]
+        required_params = ["rpm_ip", "rpm_port"]
+        self.unpack_userparams(req_param_names=required_params)
+        self.rpm_telnet = rutils.create_telnet_session(self.rpm_ip)
+
+    ### Tests ###
+
+    def test_turn_off_80211mc_ap(self):
+        self.rpm_telnet.turn_off(self.rpm_port)
+        curr_time = time.time()
+        while time.time() < curr_time + TIMEOUT:
+            time.sleep(WAIT_TIME)
+            if not wutils.start_wifi_connection_scan_and_check_for_network(
+                self.dut, SSID):
+                return True
+        self.log.error("Failed to turn off AP")
+        return False
diff --git a/acts_tests/acts_contrib/test_utils/wifi/RttPreFlightTest.py b/acts_tests/acts_contrib/test_utils/wifi/RttPreFlightTest.py
new file mode 100644
index 0000000..db87976
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/RttPreFlightTest.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+import acts_contrib.test_utils.wifi.rpm_controller_utils as rutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts import asserts
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+
+SSID = "DO_NOT_CONNECT"
+TIMEOUT = 60
+WAIT_TIME = 10
+
+class RttPreFlightTest(WifiBaseTest):
+    """Turns on/off 802.11mc AP before and after RTT tests."""
+
+    def setup_class(self):
+        super().setup_class()
+        self.dut = self.android_devices[0]
+        required_params = ["rpm_ip", "rpm_port"]
+        self.unpack_userparams(req_param_names=required_params)
+        self.rpm_telnet = rutils.create_telnet_session(self.rpm_ip)
+
+    ### Tests ###
+
+    def test_turn_on_80211mc_ap(self):
+        self.rpm_telnet.turn_on(self.rpm_port)
+        curr_time = time.time()
+        while time.time() < curr_time + TIMEOUT:
+            time.sleep(WAIT_TIME)
+            if wutils.start_wifi_connection_scan_and_check_for_network(
+                self.dut, SSID):
+                return True
+        self.log.error("Failed to turn on AP")
+        return False
diff --git a/acts_tests/acts_contrib/test_utils/wifi/WifiBaseTest.py b/acts_tests/acts_contrib/test_utils/wifi/WifiBaseTest.py
new file mode 100644
index 0000000..e191599
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/WifiBaseTest.py
@@ -0,0 +1,801 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 - 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.
+"""
+    Base Class for Defining Common WiFi Test Functionality
+"""
+
+import copy
+import itertools
+import time
+
+import acts.controllers.access_point as ap
+
+from acts import asserts
+from acts import signals
+from acts import utils
+from acts.base_test import BaseTestClass
+from acts.signals import TestSignal
+from acts.controllers import android_device
+from acts.controllers.access_point import AccessPoint
+from acts.controllers.ap_lib import hostapd_ap_preset
+from acts.controllers.ap_lib import hostapd_bss_settings
+from acts.controllers.ap_lib import hostapd_constants
+from acts.controllers.ap_lib import hostapd_security
+
+AP_1 = 0
+AP_2 = 1
+MAX_AP_COUNT = 2
+
+
+class WifiBaseTest(BaseTestClass):
+    def setup_class(self):
+        if hasattr(self, 'attenuators') and self.attenuators:
+            for attenuator in self.attenuators:
+                attenuator.set_atten(0)
+
+    def get_psk_network(
+            self,
+            mirror_ap,
+            reference_networks,
+            hidden=False,
+            same_ssid=False,
+            security_mode=hostapd_constants.WPA2_STRING,
+            ssid_length_2g=hostapd_constants.AP_SSID_LENGTH_2G,
+            ssid_length_5g=hostapd_constants.AP_SSID_LENGTH_5G,
+            passphrase_length_2g=hostapd_constants.AP_PASSPHRASE_LENGTH_2G,
+            passphrase_length_5g=hostapd_constants.AP_PASSPHRASE_LENGTH_5G):
+        """Generates SSID and passphrase for a WPA2 network using random
+           generator.
+
+           Args:
+               mirror_ap: Boolean, determines if both APs use the same hostapd
+                          config or different configs.
+               reference_networks: List of PSK networks.
+               same_ssid: Boolean, determines if both bands on AP use the same
+                          SSID.
+               ssid_length_2gecond AP Int, number of characters to use for 2G SSID.
+               ssid_length_5g: Int, number of characters to use for 5G SSID.
+               passphrase_length_2g: Int, length of password for 2G network.
+               passphrase_length_5g: Int, length of password for 5G network.
+
+           Returns: A dict of 2G and 5G network lists for hostapd configuration.
+
+        """
+        network_dict_2g = {}
+        network_dict_5g = {}
+        ref_5g_security = security_mode
+        ref_2g_security = security_mode
+
+        if same_ssid:
+            ref_2g_ssid = 'xg_%s' % utils.rand_ascii_str(ssid_length_2g)
+            ref_5g_ssid = ref_2g_ssid
+
+            ref_2g_passphrase = utils.rand_ascii_str(passphrase_length_2g)
+            ref_5g_passphrase = ref_2g_passphrase
+
+        else:
+            ref_2g_ssid = '2g_%s' % utils.rand_ascii_str(ssid_length_2g)
+            ref_2g_passphrase = utils.rand_ascii_str(passphrase_length_2g)
+
+            ref_5g_ssid = '5g_%s' % utils.rand_ascii_str(ssid_length_5g)
+            ref_5g_passphrase = utils.rand_ascii_str(passphrase_length_5g)
+
+        network_dict_2g = {
+            "SSID": ref_2g_ssid,
+            "security": ref_2g_security,
+            "password": ref_2g_passphrase,
+            "hiddenSSID": hidden
+        }
+
+        network_dict_5g = {
+            "SSID": ref_5g_ssid,
+            "security": ref_5g_security,
+            "password": ref_5g_passphrase,
+            "hiddenSSID": hidden
+        }
+
+        ap = 0
+        for ap in range(MAX_AP_COUNT):
+            reference_networks.append({
+                "2g": copy.copy(network_dict_2g),
+                "5g": copy.copy(network_dict_5g)
+            })
+            if not mirror_ap:
+                break
+        return {"2g": network_dict_2g, "5g": network_dict_5g}
+
+    def get_open_network(self,
+                         mirror_ap,
+                         open_network,
+                         hidden=False,
+                         same_ssid=False,
+                         ssid_length_2g=hostapd_constants.AP_SSID_LENGTH_2G,
+                         ssid_length_5g=hostapd_constants.AP_SSID_LENGTH_5G,
+                         security_mode='none'):
+        """Generates SSIDs for a open network using a random generator.
+
+        Args:
+            mirror_ap: Boolean, determines if both APs use the same hostapd
+                       config or different configs.
+            open_network: List of open networks.
+            same_ssid: Boolean, determines if both bands on AP use the same
+                       SSID.
+            ssid_length_2g: Int, number of characters to use for 2G SSID.
+            ssid_length_5g: Int, number of characters to use for 5G SSID.
+            security_mode: 'none' for open and 'OWE' for WPA3 OWE.
+
+        Returns: A dict of 2G and 5G network lists for hostapd configuration.
+
+        """
+        network_dict_2g = {}
+        network_dict_5g = {}
+
+        if same_ssid:
+            open_2g_ssid = 'xg_%s' % utils.rand_ascii_str(ssid_length_2g)
+            open_5g_ssid = open_2g_ssid
+
+        else:
+            open_2g_ssid = '2g_%s' % utils.rand_ascii_str(ssid_length_2g)
+            open_5g_ssid = '5g_%s' % utils.rand_ascii_str(ssid_length_5g)
+
+        network_dict_2g = {
+            "SSID": open_2g_ssid,
+            "security": security_mode,
+            "hiddenSSID": hidden
+        }
+
+        network_dict_5g = {
+            "SSID": open_5g_ssid,
+            "security": security_mode,
+            "hiddenSSID": hidden
+        }
+
+        ap = 0
+        for ap in range(MAX_AP_COUNT):
+            open_network.append({
+                "2g": copy.copy(network_dict_2g),
+                "5g": copy.copy(network_dict_5g)
+            })
+            if not mirror_ap:
+                break
+        return {"2g": network_dict_2g, "5g": network_dict_5g}
+
+    def get_wep_network(
+            self,
+            mirror_ap,
+            networks,
+            hidden=False,
+            same_ssid=False,
+            ssid_length_2g=hostapd_constants.AP_SSID_LENGTH_2G,
+            ssid_length_5g=hostapd_constants.AP_SSID_LENGTH_5G,
+            passphrase_length_2g=hostapd_constants.AP_PASSPHRASE_LENGTH_2G,
+            passphrase_length_5g=hostapd_constants.AP_PASSPHRASE_LENGTH_5G):
+        """Generates SSID and passphrase for a WEP network using random
+           generator.
+
+           Args:
+               mirror_ap: Boolean, determines if both APs use the same hostapd
+                          config or different configs.
+               networks: List of WEP networks.
+               same_ssid: Boolean, determines if both bands on AP use the same
+                          SSID.
+               ssid_length_2gecond AP Int, number of characters to use for 2G SSID.
+               ssid_length_5g: Int, number of characters to use for 5G SSID.
+               passphrase_length_2g: Int, length of password for 2G network.
+               passphrase_length_5g: Int, length of password for 5G network.
+
+           Returns: A dict of 2G and 5G network lists for hostapd configuration.
+
+        """
+        network_dict_2g = {}
+        network_dict_5g = {}
+        ref_5g_security = hostapd_constants.WEP_STRING
+        ref_2g_security = hostapd_constants.WEP_STRING
+
+        if same_ssid:
+            ref_2g_ssid = 'xg_%s' % utils.rand_ascii_str(ssid_length_2g)
+            ref_5g_ssid = ref_2g_ssid
+
+            ref_2g_passphrase = utils.rand_hex_str(passphrase_length_2g)
+            ref_5g_passphrase = ref_2g_passphrase
+
+        else:
+            ref_2g_ssid = '2g_%s' % utils.rand_ascii_str(ssid_length_2g)
+            ref_2g_passphrase = utils.rand_hex_str(passphrase_length_2g)
+
+            ref_5g_ssid = '5g_%s' % utils.rand_ascii_str(ssid_length_5g)
+            ref_5g_passphrase = utils.rand_hex_str(passphrase_length_5g)
+
+        network_dict_2g = {
+            "SSID": ref_2g_ssid,
+            "security": ref_2g_security,
+            "wepKeys": [ref_2g_passphrase] * 4,
+            "hiddenSSID": hidden
+        }
+
+        network_dict_5g = {
+            "SSID": ref_5g_ssid,
+            "security": ref_5g_security,
+            "wepKeys": [ref_2g_passphrase] * 4,
+            "hiddenSSID": hidden
+        }
+
+        ap = 0
+        for ap in range(MAX_AP_COUNT):
+            networks.append({
+                "2g": copy.copy(network_dict_2g),
+                "5g": copy.copy(network_dict_5g)
+            })
+            if not mirror_ap:
+                break
+        return {"2g": network_dict_2g, "5g": network_dict_5g}
+
+    def update_bssid(self, ap_instance, ap, network, band):
+        """Get bssid and update network dictionary.
+
+        Args:
+            ap_instance: Accesspoint index that was configured.
+            ap: Accesspoint object corresponding to ap_instance.
+            network: Network dictionary.
+            band: Wifi networks' band.
+
+        """
+        bssid = ap.get_bssid_from_ssid(network["SSID"], band)
+
+        if network["security"] == hostapd_constants.WPA2_STRING:
+            # TODO:(bamahadev) Change all occurances of reference_networks
+            # to wpa_networks.
+            self.reference_networks[ap_instance][band]["bssid"] = bssid
+        if network["security"] == hostapd_constants.WPA_STRING:
+            self.wpa_networks[ap_instance][band]["bssid"] = bssid
+        if network["security"] == hostapd_constants.WEP_STRING:
+            self.wep_networks[ap_instance][band]["bssid"] = bssid
+        if network["security"] == hostapd_constants.ENT_STRING:
+            if "bssid" not in self.ent_networks[ap_instance][band]:
+                self.ent_networks[ap_instance][band]["bssid"] = bssid
+            else:
+                self.ent_networks_pwd[ap_instance][band]["bssid"] = bssid
+        if network["security"] == 'none':
+            self.open_network[ap_instance][band]["bssid"] = bssid
+
+    def populate_bssid(self, ap_instance, ap, networks_5g, networks_2g):
+        """Get bssid for a given SSID and add it to the network dictionary.
+
+        Args:
+            ap_instance: Accesspoint index that was configured.
+            ap: Accesspoint object corresponding to ap_instance.
+            networks_5g: List of 5g networks configured on the APs.
+            networks_2g: List of 2g networks configured on the APs.
+
+        """
+
+        if not (networks_5g or networks_2g):
+            return
+
+        for network in networks_5g:
+            if 'channel' in network:
+                continue
+            self.update_bssid(ap_instance, ap, network,
+                hostapd_constants.BAND_5G)
+
+        for network in networks_2g:
+            if 'channel' in network:
+                continue
+            self.update_bssid(ap_instance, ap, network,
+                hostapd_constants.BAND_2G)
+
+    def configure_openwrt_ap_and_start(
+            self,
+            channel_5g=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
+            channel_2g=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
+            ssid_length_2g=hostapd_constants.AP_SSID_LENGTH_2G,
+            passphrase_length_2g=hostapd_constants.AP_PASSPHRASE_LENGTH_2G,
+            ssid_length_5g=hostapd_constants.AP_SSID_LENGTH_5G,
+            passphrase_length_5g=hostapd_constants.AP_PASSPHRASE_LENGTH_5G,
+            mirror_ap=False,
+            hidden=False,
+            same_ssid=False,
+            open_network=False,
+            wpa_network=False,
+            wep_network=False,
+            ent_network=False,
+            ent_network_pwd=False,
+            owe_network=False,
+            sae_network=False,
+            radius_conf_2g=None,
+            radius_conf_5g=None,
+            radius_conf_pwd=None,
+            ap_count=1):
+        """Create, configure and start OpenWrt AP.
+
+        Args:
+            channel_5g: 5G channel to configure.
+            channel_2g: 2G channel to configure.
+            ssid_length_2g: Int, number of characters to use for 2G SSID.
+            passphrase_length_2g: Int, length of password for 2G network.
+            ssid_length_5g: Int, number of characters to use for 5G SSID.
+            passphrase_length_5g: Int, length of password for 5G network.
+            same_ssid: Boolean, determines if both bands on AP use the same SSID.
+            open_network: Boolean, to check if open network should be configured.
+            wpa_network: Boolean, to check if wpa network should be configured.
+            wep_network: Boolean, to check if wep network should be configured.
+            ent_network: Boolean, to check if ent network should be configured.
+            ent_network_pwd: Boolean, to check if ent pwd network should be configured.
+            owe_network: Boolean, to check if owe network should be configured.
+            sae_network: Boolean, to check if sae network should be configured.
+            radius_conf_2g: dictionary with enterprise radius server details.
+            radius_conf_5g: dictionary with enterprise radius server details.
+            radius_conf_pwd: dictionary with enterprise radiuse server details.
+            ap_count: APs to configure.
+        """
+        if mirror_ap and ap_count == 1:
+             raise ValueError("ap_count cannot be 1 if mirror_ap is True.")
+
+        self.reference_networks = []
+        self.wpa_networks = []
+        self.wep_networks = []
+        self.ent_networks = []
+        self.ent_networks_pwd = []
+        self.open_network = []
+        self.owe_networks = []
+        self.sae_networks = []
+        self.bssid_map = []
+        for i in range(ap_count):
+            network_list = []
+            if wpa_network:
+                wpa_dict = self.get_psk_network(mirror_ap,
+                                                self.reference_networks,
+                                                hidden,
+                                                same_ssid,
+                                                ssid_length_2g,
+                                                ssid_length_5g,
+                                                passphrase_length_2g,
+                                                passphrase_length_5g)
+                wpa_dict[hostapd_constants.BAND_2G]["security"] = "psk2"
+                wpa_dict[hostapd_constants.BAND_5G]["security"] = "psk2"
+                self.wpa_networks.append(wpa_dict)
+                network_list.append(wpa_dict)
+            if wep_network:
+                wep_dict = self.get_wep_network(mirror_ap,
+                                                self.wep_networks,
+                                                hidden,
+                                                same_ssid,
+                                                ssid_length_2g,
+                                                ssid_length_5g)
+                network_list.append(wep_dict)
+            if ent_network:
+                ent_dict = self.get_open_network(mirror_ap,
+                                                 self.ent_networks,
+                                                 hidden,
+                                                 same_ssid,
+                                                 ssid_length_2g,
+                                                 ssid_length_5g)
+                ent_dict["2g"]["security"] = "wpa2"
+                ent_dict["2g"].update(radius_conf_2g)
+                ent_dict["5g"]["security"] = "wpa2"
+                ent_dict["5g"].update(radius_conf_5g)
+                network_list.append(ent_dict)
+            if ent_network_pwd:
+                ent_pwd_dict = self.get_open_network(mirror_ap,
+                                                     self.ent_networks_pwd,
+                                                     hidden,
+                                                     same_ssid,
+                                                     ssid_length_2g,
+                                                     ssid_length_5g)
+                ent_pwd_dict["2g"]["security"] = "wpa2"
+                ent_pwd_dict["2g"].update(radius_conf_pwd)
+                ent_pwd_dict["5g"]["security"] = "wpa2"
+                ent_pwd_dict["5g"].update(radius_conf_pwd)
+                network_list.append(ent_pwd_dict)
+            if open_network:
+                open_dict = self.get_open_network(mirror_ap,
+                                                  self.open_network,
+                                                  hidden,
+                                                  same_ssid,
+                                                  ssid_length_2g,
+                                                  ssid_length_5g)
+                network_list.append(open_dict)
+            if owe_network:
+                owe_dict = self.get_open_network(mirror_ap,
+                                                 self.owe_networks,
+                                                 hidden,
+                                                 same_ssid,
+                                                 ssid_length_2g,
+                                                 ssid_length_5g,
+                                                 "OWE")
+                owe_dict[hostapd_constants.BAND_2G]["security"] = "owe"
+                owe_dict[hostapd_constants.BAND_5G]["security"] = "owe"
+                network_list.append(owe_dict)
+            if sae_network:
+                sae_dict = self.get_psk_network(mirror_ap,
+                                                self.sae_networks,
+                                                hidden,
+                                                same_ssid,
+                                                hostapd_constants.WPA3_KEY_MGMT,
+                                                ssid_length_2g,
+                                                ssid_length_5g,
+                                                passphrase_length_2g,
+                                                passphrase_length_5g)
+                sae_dict[hostapd_constants.BAND_2G]["security"] = "sae"
+                sae_dict[hostapd_constants.BAND_5G]["security"] = "sae"
+                network_list.append(sae_dict)
+            self.access_points[i].configure_ap(network_list,
+                                               channel_2g,
+                                               channel_5g)
+            self.access_points[i].start_ap()
+            self.bssid_map.append(
+                self.access_points[i].get_bssids_for_wifi_networks())
+            if mirror_ap:
+              self.access_points[i+1].configure_ap(network_list,
+                                                   channel_2g,
+                                                   channel_5g)
+              self.access_points[i+1].start_ap()
+              self.bssid_map.append(
+                  self.access_points[i+1].get_bssids_for_wifi_networks())
+              break
+
+    def legacy_configure_ap_and_start(
+            self,
+            channel_5g=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
+            channel_2g=hostapd_constants.AP_DEFAULT_CHANNEL_2G,
+            max_2g_networks=hostapd_constants.AP_DEFAULT_MAX_SSIDS_2G,
+            max_5g_networks=hostapd_constants.AP_DEFAULT_MAX_SSIDS_5G,
+            ap_ssid_length_2g=hostapd_constants.AP_SSID_LENGTH_2G,
+            ap_passphrase_length_2g=hostapd_constants.AP_PASSPHRASE_LENGTH_2G,
+            ap_ssid_length_5g=hostapd_constants.AP_SSID_LENGTH_5G,
+            ap_passphrase_length_5g=hostapd_constants.AP_PASSPHRASE_LENGTH_5G,
+            hidden=False,
+            same_ssid=False,
+            mirror_ap=True,
+            wpa_network=False,
+            wep_network=False,
+            ent_network=False,
+            radius_conf_2g=None,
+            radius_conf_5g=None,
+            ent_network_pwd=False,
+            radius_conf_pwd=None,
+            ap_count=1):
+
+        config_count = 1
+        count = 0
+
+        # For example, the NetworkSelector tests use 2 APs and require that
+        # both APs are not mirrored.
+        if not mirror_ap and ap_count == 1:
+             raise ValueError("ap_count cannot be 1 if mirror_ap is False.")
+
+        if not mirror_ap:
+            config_count = ap_count
+
+        self.user_params["reference_networks"] = []
+        self.user_params["open_network"] = []
+        if wpa_network:
+            self.user_params["wpa_networks"] = []
+        if wep_network:
+            self.user_params["wep_networks"] = []
+        if ent_network:
+            self.user_params["ent_networks"] = []
+        if ent_network_pwd:
+            self.user_params["ent_networks_pwd"] = []
+
+        # kill hostapd & dhcpd if the cleanup was not successful
+        for i in range(len(self.access_points)):
+            self.log.debug("Check ap state and cleanup")
+            self._cleanup_hostapd_and_dhcpd(i)
+
+        for count in range(config_count):
+
+            network_list_2g = []
+            network_list_5g = []
+
+            orig_network_list_2g = []
+            orig_network_list_5g = []
+
+            network_list_2g.append({"channel": channel_2g})
+            network_list_5g.append({"channel": channel_5g})
+
+            networks_dict = self.get_psk_network(
+                                mirror_ap,
+                                self.user_params["reference_networks"],
+                                hidden=hidden,
+                                same_ssid=same_ssid)
+            self.reference_networks = self.user_params["reference_networks"]
+
+            network_list_2g.append(networks_dict["2g"])
+            network_list_5g.append(networks_dict["5g"])
+
+            # When same_ssid is set, only configure one set of WPA networks.
+            # We cannot have more than one set because duplicate interface names
+            # are not allowed.
+            # TODO(bmahadev): Provide option to select the type of network,
+            # instead of defaulting to WPA.
+            if not same_ssid:
+                networks_dict = self.get_open_network(
+                                    mirror_ap,
+                                    self.user_params["open_network"],
+                                    hidden=hidden,
+                                    same_ssid=same_ssid)
+                self.open_network = self.user_params["open_network"]
+
+                network_list_2g.append(networks_dict["2g"])
+                network_list_5g.append(networks_dict["5g"])
+
+                if wpa_network:
+                    networks_dict = self.get_psk_network(
+                                        mirror_ap,
+                                        self.user_params["wpa_networks"],
+                                        hidden=hidden,
+                                        same_ssid=same_ssid,
+                                        security_mode=hostapd_constants.WPA_STRING)
+                    self.wpa_networks = self.user_params["wpa_networks"]
+
+                    network_list_2g.append(networks_dict["2g"])
+                    network_list_5g.append(networks_dict["5g"])
+
+                if wep_network:
+                    networks_dict = self.get_wep_network(
+                                        mirror_ap,
+                                        self.user_params["wep_networks"],
+                                        hidden=hidden,
+                                        same_ssid=same_ssid)
+                    self.wep_networks = self.user_params["wep_networks"]
+
+                    network_list_2g.append(networks_dict["2g"])
+                    network_list_5g.append(networks_dict["5g"])
+
+                if ent_network:
+                    networks_dict = self.get_open_network(
+                                        mirror_ap,
+                                        self.user_params["ent_networks"],
+                                        hidden=hidden,
+                                        same_ssid=same_ssid)
+                    networks_dict["2g"]["security"] = hostapd_constants.ENT_STRING
+                    networks_dict["2g"].update(radius_conf_2g)
+                    networks_dict["5g"]["security"] = hostapd_constants.ENT_STRING
+                    networks_dict["5g"].update(radius_conf_5g)
+                    self.ent_networks = self.user_params["ent_networks"]
+
+                    network_list_2g.append(networks_dict["2g"])
+                    network_list_5g.append(networks_dict["5g"])
+
+                if ent_network_pwd:
+                    networks_dict = self.get_open_network(
+                                        mirror_ap,
+                                        self.user_params["ent_networks_pwd"],
+                                        hidden=hidden,
+                                        same_ssid=same_ssid)
+                    networks_dict["2g"]["security"] = hostapd_constants.ENT_STRING
+                    networks_dict["2g"].update(radius_conf_pwd)
+                    networks_dict["5g"]["security"] = hostapd_constants.ENT_STRING
+                    networks_dict["5g"].update(radius_conf_pwd)
+                    self.ent_networks_pwd = self.user_params["ent_networks_pwd"]
+
+                    network_list_2g.append(networks_dict["2g"])
+                    network_list_5g.append(networks_dict["5g"])
+
+            orig_network_list_5g = copy.copy(network_list_5g)
+            orig_network_list_2g = copy.copy(network_list_2g)
+
+            if len(network_list_5g) > 1:
+                self.config_5g = self._generate_legacy_ap_config(network_list_5g)
+            if len(network_list_2g) > 1:
+                self.config_2g = self._generate_legacy_ap_config(network_list_2g)
+
+            self.access_points[count].start_ap(self.config_2g)
+            self.access_points[count].start_ap(self.config_5g)
+            self.populate_bssid(count, self.access_points[count], orig_network_list_5g,
+                                orig_network_list_2g)
+
+        # Repeat configuration on the second router.
+        if mirror_ap and ap_count == 2:
+            self.access_points[AP_2].start_ap(self.config_2g)
+            self.access_points[AP_2].start_ap(self.config_5g)
+            self.populate_bssid(AP_2, self.access_points[AP_2],
+                orig_network_list_5g, orig_network_list_2g)
+
+    def _kill_processes(self, ap, daemon):
+        """ Kill hostapd and dhcpd daemons
+
+        Args:
+            ap: AP to cleanup
+            daemon: process to kill
+
+        Returns: True/False if killing process is successful
+        """
+        self.log.info("Killing %s" % daemon)
+        pids = ap.ssh.run('pidof %s' % daemon, ignore_status=True)
+        if pids.stdout:
+            ap.ssh.run('kill %s' % pids.stdout, ignore_status=True)
+        time.sleep(3)
+        pids = ap.ssh.run('pidof %s' % daemon, ignore_status=True)
+        if pids.stdout:
+            return False
+        return True
+
+    def _cleanup_hostapd_and_dhcpd(self, count):
+        """ Check if AP was cleaned up properly
+
+        Kill hostapd and dhcpd processes if cleanup was not successful in the
+        last run
+
+        Args:
+            count: AP to check
+
+        Returns:
+            New AccessPoint object if AP required cleanup
+
+        Raises:
+            Error: if the AccessPoint timed out to setup
+        """
+        ap = self.access_points[count]
+        phy_ifaces = ap.interfaces.get_physical_interface()
+        kill_hostapd = False
+        for iface in phy_ifaces:
+            if '2g_' in iface or '5g_' in iface or 'xg_' in iface:
+                kill_hostapd = True
+                break
+
+        if not kill_hostapd:
+            return
+
+        self.log.debug("Cleanup AP")
+        if not self._kill_processes(ap, 'hostapd') or \
+            not self._kill_processes(ap, 'dhcpd'):
+              raise("Failed to cleanup AP")
+
+        ap.__init__(self.user_params['AccessPoint'][count])
+
+    def _generate_legacy_ap_config(self, network_list):
+        bss_settings = []
+        wlan_2g = self.access_points[AP_1].wlan_2g
+        wlan_5g = self.access_points[AP_1].wlan_5g
+        ap_settings = network_list.pop(0)
+        # TODO:(bmahadev) This is a bug. We should not have to pop the first
+        # network in the list and treat it as a separate case. Instead,
+        # create_ap_preset() should be able to take NULL ssid and security and
+        # build config based on the bss_Settings alone.
+        hostapd_config_settings = network_list.pop(0)
+        for network in network_list:
+            if "password" in network:
+                bss_settings.append(
+                    hostapd_bss_settings.BssSettings(
+                        name=network["SSID"],
+                        ssid=network["SSID"],
+                        hidden=network["hiddenSSID"],
+                        security=hostapd_security.Security(
+                            security_mode=network["security"],
+                            password=network["password"])))
+            elif "wepKeys" in network:
+                bss_settings.append(
+                    hostapd_bss_settings.BssSettings(
+                        name=network["SSID"],
+                        ssid=network["SSID"],
+                        hidden=network["hiddenSSID"],
+                        security=hostapd_security.Security(
+                            security_mode=network["security"],
+                            password=network["wepKeys"][0])))
+            elif network["security"] == hostapd_constants.ENT_STRING:
+                bss_settings.append(
+                    hostapd_bss_settings.BssSettings(
+                        name=network["SSID"],
+                        ssid=network["SSID"],
+                        hidden=network["hiddenSSID"],
+                        security=hostapd_security.Security(
+                            security_mode=network["security"],
+                            radius_server_ip=network["radius_server_ip"],
+                            radius_server_port=network["radius_server_port"],
+                            radius_server_secret=network["radius_server_secret"])))
+            else:
+                bss_settings.append(
+                    hostapd_bss_settings.BssSettings(
+                        name=network["SSID"],
+                        ssid=network["SSID"],
+                        hidden=network["hiddenSSID"]))
+        if "password" in hostapd_config_settings:
+            config = hostapd_ap_preset.create_ap_preset(
+                iface_wlan_2g=wlan_2g,
+                iface_wlan_5g=wlan_5g,
+                channel=ap_settings["channel"],
+                ssid=hostapd_config_settings["SSID"],
+                hidden=hostapd_config_settings["hiddenSSID"],
+                security=hostapd_security.Security(
+                    security_mode=hostapd_config_settings["security"],
+                    password=hostapd_config_settings["password"]),
+                bss_settings=bss_settings)
+        elif "wepKeys" in hostapd_config_settings:
+            config = hostapd_ap_preset.create_ap_preset(
+                iface_wlan_2g=wlan_2g,
+                iface_wlan_5g=wlan_5g,
+                channel=ap_settings["channel"],
+                ssid=hostapd_config_settings["SSID"],
+                hidden=hostapd_config_settings["hiddenSSID"],
+                security=hostapd_security.Security(
+                    security_mode=hostapd_config_settings["security"],
+                    password=hostapd_config_settings["wepKeys"][0]),
+                bss_settings=bss_settings)
+        else:
+            config = hostapd_ap_preset.create_ap_preset(
+                iface_wlan_2g=wlan_2g,
+                iface_wlan_5g=wlan_5g,
+                channel=ap_settings["channel"],
+                ssid=hostapd_config_settings["SSID"],
+                hidden=hostapd_config_settings["hiddenSSID"],
+                bss_settings=bss_settings)
+        return config
+
+    def configure_packet_capture(
+            self,
+            channel_5g=hostapd_constants.AP_DEFAULT_CHANNEL_5G,
+            channel_2g=hostapd_constants.AP_DEFAULT_CHANNEL_2G):
+        """Configure packet capture for 2G and 5G bands.
+
+        Args:
+            channel_5g: Channel to set the monitor mode to for 5G band.
+            channel_2g: Channel to set the monitor mode to for 2G band.
+        """
+        self.packet_capture = self.packet_capture[0]
+        result = self.packet_capture.configure_monitor_mode(
+            hostapd_constants.BAND_2G, channel_2g)
+        if not result:
+            raise ValueError("Failed to configure channel for 2G band")
+
+        result = self.packet_capture.configure_monitor_mode(
+            hostapd_constants.BAND_5G, channel_5g)
+        if not result:
+            raise ValueError("Failed to configure channel for 5G band.")
+
+    @staticmethod
+    def wifi_test_wrap(fn):
+        def _safe_wrap_test_case(self, *args, **kwargs):
+            test_id = "%s:%s:%s" % (self.__class__.__name__, self.test_name,
+                                    self.log_begin_time.replace(' ', '-'))
+            self.test_id = test_id
+            self.result_detail = ""
+            tries = int(self.user_params.get("wifi_auto_rerun", 3))
+            for ad in self.android_devices:
+                ad.log_path = self.log_path
+            for i in range(tries + 1):
+                result = True
+                if i > 0:
+                    log_string = "[Test Case] RETRY:%s %s" % (i, self.test_name)
+                    self.log.info(log_string)
+                    self._teardown_test(self.test_name)
+                    self._setup_test(self.test_name)
+                try:
+                    result = fn(self, *args, **kwargs)
+                except signals.TestFailure as e:
+                    self.log.warn("Error msg: %s" % e)
+                    if self.result_detail:
+                        signal.details = self.result_detail
+                    result = False
+                except signals.TestSignal:
+                    if self.result_detail:
+                        signal.details = self.result_detail
+                    raise
+                except Exception as e:
+                    self.log.exception(e)
+                    asserts.fail(self.result_detail)
+                if result is False:
+                    if i < tries:
+                        continue
+                else:
+                    break
+            if result is not False:
+                asserts.explicit_pass(self.result_detail)
+            else:
+                asserts.fail(self.result_detail)
+
+        return _safe_wrap_test_case
diff --git a/acts_tests/acts_contrib/test_utils/wifi/__init__.py b/acts_tests/acts_contrib/test_utils/wifi/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/wifi/aware/AwareBaseTest.py b/acts_tests/acts_contrib/test_utils/wifi/aware/AwareBaseTest.py
new file mode 100644
index 0000000..4758129
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/AwareBaseTest.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 - 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 asserts
+from acts import utils
+from acts.base_test import BaseTestClass
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+
+
+class AwareBaseTest(BaseTestClass):
+    # message ID counter to make sure all uses are unique
+    msg_id = 0
+
+    # offset (in seconds) to separate the start-up of multiple devices.
+    # De-synchronizes the start-up time so that they don't start and stop scanning
+    # at the same time - which can lead to very long clustering times.
+    device_startup_offset = 2
+
+    def setup_test(self):
+        required_params = ("aware_default_power_mode",
+                           "dbs_supported_models",)
+        self.unpack_userparams(required_params)
+
+        for ad in self.android_devices:
+            asserts.skip_if(
+                not ad.droid.doesDeviceSupportWifiAwareFeature(),
+                "Device under test does not support Wi-Fi Aware - skipping test"
+            )
+            aware_avail = ad.droid.wifiIsAwareAvailable()
+            ad.droid.wifiP2pClose()
+            wutils.wifi_toggle_state(ad, True)
+            utils.set_location_service(ad, True)
+            if not aware_avail:
+                self.log.info('Aware not available. Waiting ...')
+                autils.wait_for_event(ad,
+                                      aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+            ad.aware_capabilities = autils.get_aware_capabilities(ad)
+            self.reset_device_parameters(ad)
+            self.reset_device_statistics(ad)
+            self.set_power_mode_parameters(ad)
+            wutils.set_wifi_country_code(ad, wutils.WifiEnums.CountryCode.US)
+            autils.configure_ndp_allow_any_override(ad, True)
+            # set randomization interval to 0 (disable) to reduce likelihood of
+            # interference in tests
+            autils.configure_mac_random_interval(ad, 0)
+            ad.ed.clear_all_events()
+
+    def teardown_test(self):
+        for ad in self.android_devices:
+            if not ad.droid.doesDeviceSupportWifiAwareFeature():
+                return
+            ad.droid.wifiP2pClose()
+            ad.droid.wifiAwareDestroyAll()
+            self.reset_device_parameters(ad)
+            autils.validate_forbidden_callbacks(ad)
+
+    def reset_device_parameters(self, ad):
+        """Reset device configurations which may have been set by tests. Should be
+    done before tests start (in case previous one was killed without tearing
+    down) and after they end (to leave device in usable state).
+
+    Args:
+      ad: device to be reset
+    """
+        ad.adb.shell("cmd wifiaware reset")
+
+    def reset_device_statistics(self, ad):
+        """Reset device statistics.
+
+    Args:
+        ad: device to be reset
+    """
+        ad.adb.shell("cmd wifiaware native_cb get_cb_count --reset")
+
+    def set_power_mode_parameters(self, ad):
+        """Set the power configuration DW parameters for the device based on any
+    configuration overrides (if provided)"""
+        if self.aware_default_power_mode == "INTERACTIVE":
+            autils.config_settings_high_power(ad)
+        elif self.aware_default_power_mode == "NON_INTERACTIVE":
+            autils.config_settings_low_power(ad)
+        else:
+            asserts.assert_false(
+                "The 'aware_default_power_mode' configuration must be INTERACTIVE or "
+                "NON_INTERACTIVE")
+
+    def get_next_msg_id(self):
+        """Increment the message ID and returns the new value. Guarantees that
+    each call to the method returns a unique value.
+
+    Returns: a new message id value.
+    """
+        self.msg_id = self.msg_id + 1
+        return self.msg_id
+
+    def on_fail(self, test_name, begin_time):
+        for ad in self.android_devices:
+            ad.take_bug_report(test_name, begin_time)
+            ad.cat_adb_log(test_name, begin_time)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/aware/__init__.py b/acts_tests/acts_contrib/test_utils/wifi/aware/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py
new file mode 100644
index 0000000..d5a0381
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_const.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+######################################################
+# Aware power settings values for interactive (high power) and
+# non-interactive (low power) modes
+######################################################
+
+POWER_DW_24_INTERACTIVE = 1
+POWER_DW_5_INTERACTIVE = 1
+POWER_DISC_BEACON_INTERVAL_INTERACTIVE = 0
+POWER_NUM_SS_IN_DISC_INTERACTIVE = 0
+POWER_ENABLE_DW_EARLY_TERM_INTERACTIVE = 0
+
+POWER_DW_24_NON_INTERACTIVE = 4
+POWER_DW_5_NON_INTERACTIVE = 0
+POWER_DISC_BEACON_INTERVAL_NON_INTERACTIVE = 0
+POWER_NUM_SS_IN_DISC_NON_INTERACTIVE = 0
+POWER_ENABLE_DW_EARLY_TERM_NON_INTERACTIVE = 0
+
+######################################################
+# Broadcast events
+######################################################
+BROADCAST_WIFI_AWARE_AVAILABLE = "WifiAwareAvailable"
+BROADCAST_WIFI_AWARE_NOT_AVAILABLE = "WifiAwareNotAvailable"
+
+######################################################
+# ConfigRequest keys
+######################################################
+
+CONFIG_KEY_5G_BAND = "Support5gBand"
+CONFIG_KEY_MASTER_PREF = "MasterPreference"
+CONFIG_KEY_CLUSTER_LOW = "ClusterLow"
+CONFIG_KEY_CLUSTER_HIGH = "ClusterHigh"
+CONFIG_KEY_ENABLE_IDEN_CB = "EnableIdentityChangeCallback"
+
+######################################################
+# Publish & Subscribe Config keys
+######################################################
+
+DISCOVERY_KEY_SERVICE_NAME = "ServiceName"
+DISCOVERY_KEY_SSI = "ServiceSpecificInfo"
+DISCOVERY_KEY_MATCH_FILTER = "MatchFilter"
+DISCOVERY_KEY_MATCH_FILTER_LIST = "MatchFilterList"
+DISCOVERY_KEY_DISCOVERY_TYPE = "DiscoveryType"
+DISCOVERY_KEY_TTL = "TtlSec"
+DISCOVERY_KEY_TERM_CB_ENABLED = "TerminateNotificationEnabled"
+DISCOVERY_KEY_RANGING_ENABLED = "RangingEnabled"
+DISCOVERY_KEY_MIN_DISTANCE_MM = "MinDistanceMm"
+DISCOVERY_KEY_MAX_DISTANCE_MM = "MaxDistanceMm"
+
+PUBLISH_TYPE_UNSOLICITED = 0
+PUBLISH_TYPE_SOLICITED = 1
+
+SUBSCRIBE_TYPE_PASSIVE = 0
+SUBSCRIBE_TYPE_ACTIVE = 1
+
+######################################################
+# WifiAwareAttachCallback events
+######################################################
+EVENT_CB_ON_ATTACHED = "WifiAwareOnAttached"
+EVENT_CB_ON_ATTACH_FAILED = "WifiAwareOnAttachFailed"
+
+######################################################
+# WifiAwareIdentityChangedListener events
+######################################################
+EVENT_CB_ON_IDENTITY_CHANGED = "WifiAwareOnIdentityChanged"
+
+# WifiAwareAttachCallback & WifiAwareIdentityChangedListener events keys
+EVENT_CB_KEY_REASON = "reason"
+EVENT_CB_KEY_MAC = "mac"
+EVENT_CB_KEY_LATENCY_MS = "latencyMs"
+EVENT_CB_KEY_TIMESTAMP_MS = "timestampMs"
+
+######################################################
+# WifiAwareDiscoverySessionCallback events
+######################################################
+SESSION_CB_ON_PUBLISH_STARTED = "WifiAwareSessionOnPublishStarted"
+SESSION_CB_ON_SUBSCRIBE_STARTED = "WifiAwareSessionOnSubscribeStarted"
+SESSION_CB_ON_SESSION_CONFIG_UPDATED = "WifiAwareSessionOnSessionConfigUpdated"
+SESSION_CB_ON_SESSION_CONFIG_FAILED = "WifiAwareSessionOnSessionConfigFailed"
+SESSION_CB_ON_SESSION_TERMINATED = "WifiAwareSessionOnSessionTerminated"
+SESSION_CB_ON_SERVICE_DISCOVERED = "WifiAwareSessionOnServiceDiscovered"
+SESSION_CB_ON_MESSAGE_SENT = "WifiAwareSessionOnMessageSent"
+SESSION_CB_ON_MESSAGE_SEND_FAILED = "WifiAwareSessionOnMessageSendFailed"
+SESSION_CB_ON_MESSAGE_RECEIVED = "WifiAwareSessionOnMessageReceived"
+
+# WifiAwareDiscoverySessionCallback events keys
+SESSION_CB_KEY_CB_ID = "callbackId"
+SESSION_CB_KEY_SESSION_ID = "discoverySessionId"
+SESSION_CB_KEY_REASON = "reason"
+SESSION_CB_KEY_PEER_ID = "peerId"
+SESSION_CB_KEY_SERVICE_SPECIFIC_INFO = "serviceSpecificInfo"
+SESSION_CB_KEY_MATCH_FILTER = "matchFilter"
+SESSION_CB_KEY_MATCH_FILTER_LIST = "matchFilterList"
+SESSION_CB_KEY_MESSAGE = "message"
+SESSION_CB_KEY_MESSAGE_ID = "messageId"
+SESSION_CB_KEY_MESSAGE_AS_STRING = "messageAsString"
+SESSION_CB_KEY_LATENCY_MS = "latencyMs"
+SESSION_CB_KEY_TIMESTAMP_MS = "timestampMs"
+SESSION_CB_KEY_DISTANCE_MM = "distanceMm"
+
+######################################################
+# WifiAwareRangingListener events (RttManager.RttListener)
+######################################################
+RTT_LISTENER_CB_ON_SUCCESS = "WifiAwareRangingListenerOnSuccess"
+RTT_LISTENER_CB_ON_FAILURE = "WifiAwareRangingListenerOnFailure"
+RTT_LISTENER_CB_ON_ABORT = "WifiAwareRangingListenerOnAborted"
+
+# WifiAwareRangingListener events (RttManager.RttListener) keys
+RTT_LISTENER_CB_KEY_CB_ID = "callbackId"
+RTT_LISTENER_CB_KEY_SESSION_ID = "sessionId"
+RTT_LISTENER_CB_KEY_RESULTS = "Results"
+RTT_LISTENER_CB_KEY_REASON = "reason"
+RTT_LISTENER_CB_KEY_DESCRIPTION = "description"
+
+######################################################
+# Capabilities keys
+######################################################
+
+CAP_MAX_CONCURRENT_AWARE_CLUSTERS = "maxConcurrentAwareClusters"
+CAP_MAX_PUBLISHES = "maxPublishes"
+CAP_MAX_SUBSCRIBES = "maxSubscribes"
+CAP_MAX_SERVICE_NAME_LEN = "maxServiceNameLen"
+CAP_MAX_MATCH_FILTER_LEN = "maxMatchFilterLen"
+CAP_MAX_TOTAL_MATCH_FILTER_LEN = "maxTotalMatchFilterLen"
+CAP_MAX_SERVICE_SPECIFIC_INFO_LEN = "maxServiceSpecificInfoLen"
+CAP_MAX_EXTENDED_SERVICE_SPECIFIC_INFO_LEN = "maxExtendedServiceSpecificInfoLen"
+CAP_MAX_NDI_INTERFACES = "maxNdiInterfaces"
+CAP_MAX_NDP_SESSIONS = "maxNdpSessions"
+CAP_MAX_APP_INFO_LEN = "maxAppInfoLen"
+CAP_MAX_QUEUED_TRANSMIT_MESSAGES = "maxQueuedTransmitMessages"
+CAP_MAX_SUBSCRIBE_INTERFACE_ADDRESSES = "maxSubscribeInterfaceAddresses"
+CAP_SUPPORTED_CIPHER_SUITES = "supportedCipherSuites"
+
+######################################################
+# WifiAwareNetworkCapabilities keys
+######################################################
+
+NET_CAP_IPV6 = "aware_ipv6"
+NET_CAP_PORT = "aware_port"
+NET_CAP_TRANSPORT_PROTOCOL = "aware_transport_protocol"
+
+######################################################
+
+# Aware NDI (NAN data-interface) name prefix
+AWARE_NDI_PREFIX = "aware_data"
+
+# Aware discovery channels
+AWARE_DISCOVERY_CHANNEL_24_BAND = 6
+AWARE_DISCOVERY_CHANNEL_5_BAND = 149
+
+# Aware Data-Path Constants
+DATA_PATH_INITIATOR = 0
+DATA_PATH_RESPONDER = 1
+
+# Maximum send retry
+MAX_TX_RETRIES = 5
+
+# Callback keys (for 'adb shell cmd wifiaware native_cb get_cb_count')
+CB_EV_CLUSTER = "0"
+CB_EV_DISABLED = "1"
+CB_EV_PUBLISH_TERMINATED = "2"
+CB_EV_SUBSCRIBE_TERMINATED = "3"
+CB_EV_MATCH = "4"
+CB_EV_MATCH_EXPIRED = "5"
+CB_EV_FOLLOWUP_RECEIVED = "6"
+CB_EV_TRANSMIT_FOLLOWUP = "7"
+CB_EV_DATA_PATH_REQUEST = "8"
+CB_EV_DATA_PATH_CONFIRM = "9"
+CB_EV_DATA_PATH_TERMINATED = "10"
diff --git a/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py
new file mode 100644
index 0000000..222c6d8
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py
@@ -0,0 +1,1009 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 - 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 base64
+import json
+import queue
+import re
+import statistics
+import time
+from acts import asserts
+
+from acts_contrib.test_utils.net import connectivity_const as cconsts
+from acts_contrib.test_utils.net import socket_test_utils as sutils
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+
+# arbitrary timeout for events
+EVENT_TIMEOUT = 10
+
+# semi-arbitrary timeout for network formation events. Based on framework
+# timeout for NDP (NAN data-path) negotiation to be completed.
+EVENT_NDP_TIMEOUT = 20
+
+# number of second to 'reasonably' wait to make sure that devices synchronize
+# with each other - useful for OOB test cases, where the OOB discovery would
+# take some time
+WAIT_FOR_CLUSTER = 5
+
+
+def decorate_event(event_name, id):
+    return '%s_%d' % (event_name, id)
+
+
+def wait_for_event(ad, event_name, timeout=EVENT_TIMEOUT):
+    """Wait for the specified event or timeout.
+
+  Args:
+    ad: The android device
+    event_name: The event to wait on
+    timeout: Number of seconds to wait
+  Returns:
+    The event (if available)
+  """
+    prefix = ''
+    if hasattr(ad, 'pretty_name'):
+        prefix = '[%s] ' % ad.pretty_name
+    try:
+        event = ad.ed.pop_event(event_name, timeout)
+        ad.log.info('%s%s: %s', prefix, event_name, event['data'])
+        return event
+    except queue.Empty:
+        ad.log.info('%sTimed out while waiting for %s', prefix, event_name)
+        asserts.fail(event_name)
+
+
+def wait_for_event_with_keys(ad, event_name, timeout=EVENT_TIMEOUT,
+                             *keyvalues):
+    """Wait for the specified event contain the key/value pairs or timeout
+
+  Args:
+    ad: The android device
+    event_name: The event to wait on
+    timeout: Number of seconds to wait
+    keyvalues: (kay, value) pairs
+  Returns:
+    The event (if available)
+  """
+
+    def filter_callbacks(event, keyvalues):
+        for keyvalue in keyvalues:
+            key, value = keyvalue
+            if event['data'][key] != value:
+                return False
+        return True
+
+    prefix = ''
+    if hasattr(ad, 'pretty_name'):
+        prefix = '[%s] ' % ad.pretty_name
+    try:
+        event = ad.ed.wait_for_event(event_name, filter_callbacks, timeout,
+                                     keyvalues)
+        ad.log.info('%s%s: %s', prefix, event_name, event['data'])
+        return event
+    except queue.Empty:
+        ad.log.info('%sTimed out while waiting for %s (%s)', prefix,
+                    event_name, keyvalues)
+        asserts.fail(event_name)
+
+
+def fail_on_event(ad, event_name, timeout=EVENT_TIMEOUT):
+    """Wait for a timeout period and looks for the specified event - fails if it
+  is observed.
+
+  Args:
+    ad: The android device
+    event_name: The event to wait for (and fail on its appearance)
+  """
+    prefix = ''
+    if hasattr(ad, 'pretty_name'):
+        prefix = '[%s] ' % ad.pretty_name
+    try:
+        event = ad.ed.pop_event(event_name, timeout)
+        ad.log.info('%sReceived unwanted %s: %s', prefix, event_name,
+                    event['data'])
+        asserts.fail(event_name, extras=event)
+    except queue.Empty:
+        ad.log.info('%s%s not seen (as expected)', prefix, event_name)
+        return
+
+
+def fail_on_event_with_keys(ad, event_name, timeout=EVENT_TIMEOUT, *keyvalues):
+    """Wait for a timeout period and looks for the specified event which contains
+  the key/value pairs - fails if it is observed.
+
+  Args:
+    ad: The android device
+    event_name: The event to wait on
+    timeout: Number of seconds to wait
+    keyvalues: (kay, value) pairs
+  """
+
+    def filter_callbacks(event, keyvalues):
+        for keyvalue in keyvalues:
+            key, value = keyvalue
+            if event['data'][key] != value:
+                return False
+        return True
+
+    prefix = ''
+    if hasattr(ad, 'pretty_name'):
+        prefix = '[%s] ' % ad.pretty_name
+    try:
+        event = ad.ed.wait_for_event(event_name, filter_callbacks, timeout,
+                                     keyvalues)
+        ad.log.info('%sReceived unwanted %s: %s', prefix, event_name,
+                    event['data'])
+        asserts.fail(event_name, extras=event)
+    except queue.Empty:
+        ad.log.info('%s%s (%s) not seen (as expected)', prefix, event_name,
+                    keyvalues)
+        return
+
+
+def verify_no_more_events(ad, timeout=EVENT_TIMEOUT):
+    """Verify that there are no more events in the queue.
+  """
+    prefix = ''
+    if hasattr(ad, 'pretty_name'):
+        prefix = '[%s] ' % ad.pretty_name
+    should_fail = False
+    try:
+        while True:
+            event = ad.ed.pop_events('.*', timeout, freq=0)
+            ad.log.info('%sQueue contains %s', prefix, event)
+            should_fail = True
+    except queue.Empty:
+        if should_fail:
+            asserts.fail('%sEvent queue not empty' % prefix)
+        ad.log.info('%sNo events in the queue (as expected)', prefix)
+        return
+
+
+def encode_list(list_of_objects):
+    """Converts the list of strings or bytearrays to a list of b64 encoded
+  bytearrays.
+
+  A None object is treated as a zero-length bytearray.
+
+  Args:
+    list_of_objects: A list of strings or bytearray objects
+  Returns: A list of the same objects, converted to bytes and b64 encoded.
+  """
+    encoded_list = []
+    for obj in list_of_objects:
+        if obj is None:
+            obj = bytes()
+        if isinstance(obj, str):
+            encoded_list.append(
+                base64.b64encode(bytes(obj, 'utf-8')).decode('utf-8'))
+        else:
+            encoded_list.append(base64.b64encode(obj).decode('utf-8'))
+    return encoded_list
+
+
+def decode_list(list_of_b64_strings):
+    """Converts the list of b64 encoded strings to a list of bytearray.
+
+  Args:
+    list_of_b64_strings: list of strings, each of which is b64 encoded array
+  Returns: a list of bytearrays.
+  """
+    decoded_list = []
+    for str in list_of_b64_strings:
+        decoded_list.append(base64.b64decode(str))
+    return decoded_list
+
+
+def construct_max_match_filter(max_size):
+    """Constructs a maximum size match filter that fits into the 'max_size' bytes.
+
+  Match filters are a set of LVs (Length, Value pairs) where L is 1 byte. The
+  maximum size match filter will contain max_size/2 LVs with all Vs (except
+  possibly the last one) of 1 byte, the last V may be 2 bytes for odd max_size.
+
+  Args:
+    max_size: Maximum size of the match filter.
+  Returns: an array of bytearrays.
+  """
+    mf_list = []
+    num_lvs = max_size // 2
+    for i in range(num_lvs - 1):
+        mf_list.append(bytes([i]))
+    if (max_size % 2 == 0):
+        mf_list.append(bytes([255]))
+    else:
+        mf_list.append(bytes([254, 255]))
+    return mf_list
+
+
+def assert_equal_strings(first, second, msg=None, extras=None):
+    """Assert equality of the string operands - where None is treated as equal to
+  an empty string (''), otherwise fail the test.
+
+  Error message is "first != second" by default. Additional explanation can
+  be supplied in the message.
+
+  Args:
+      first, seconds: The strings that are evaluated for equality.
+      msg: A string that adds additional info about the failure.
+      extras: An optional field for extra information to be included in
+              test result.
+  """
+    if first == None:
+        first = ''
+    if second == None:
+        second = ''
+    asserts.assert_equal(first, second, msg, extras)
+
+
+def get_aware_capabilities(ad):
+    """Get the Wi-Fi Aware capabilities from the specified device. The
+  capabilities are a dictionary keyed by aware_const.CAP_* keys.
+
+  Args:
+    ad: the Android device
+  Returns: the capability dictionary.
+  """
+    return json.loads(ad.adb.shell('cmd wifiaware state_mgr get_capabilities'))
+
+
+def get_wifi_mac_address(ad):
+    """Get the Wi-Fi interface MAC address as a upper-case string of hex digits
+  without any separators (e.g. ':').
+
+  Args:
+    ad: Device on which to run.
+  """
+    return ad.droid.wifiGetConnectionInfo()['mac_address'].upper().replace(
+        ':', '')
+
+
+def validate_forbidden_callbacks(ad, limited_cb=None):
+    """Validate that the specified callbacks have not been called more then permitted.
+
+  In addition to the input configuration also validates that forbidden callbacks
+  have never been called.
+
+  Args:
+    ad: Device on which to run.
+    limited_cb: Dictionary of CB_EV_* ids and maximum permitted calls (0
+                meaning never).
+  """
+    cb_data = json.loads(ad.adb.shell('cmd wifiaware native_cb get_cb_count'))
+
+    if limited_cb is None:
+        limited_cb = {}
+    # add callbacks which should never be called
+    limited_cb[aconsts.CB_EV_MATCH_EXPIRED] = 0
+
+    fail = False
+    for cb_event in limited_cb.keys():
+        if cb_event in cb_data:
+            if cb_data[cb_event] > limited_cb[cb_event]:
+                fail = True
+                ad.log.info(
+                    'Callback %s observed %d times: more then permitted %d times',
+                    cb_event, cb_data[cb_event], limited_cb[cb_event])
+
+    asserts.assert_false(fail, 'Forbidden callbacks observed', extras=cb_data)
+
+
+def extract_stats(ad, data, results, key_prefix, log_prefix):
+    """Extract statistics from the data, store in the results dictionary, and
+  output to the info log.
+
+  Args:
+    ad: Android device (for logging)
+    data: A list containing the data to be analyzed.
+    results: A dictionary into which to place the statistics.
+    key_prefix: A string prefix to use for the dict keys storing the
+                extracted stats.
+    log_prefix: A string prefix to use for the info log.
+    include_data: If True includes the raw data in the dictionary,
+                  otherwise just the stats.
+  """
+    num_samples = len(data)
+    results['%snum_samples' % key_prefix] = num_samples
+
+    if not data:
+        return
+
+    data_min = min(data)
+    data_max = max(data)
+    data_mean = statistics.mean(data)
+    data_cdf = extract_cdf(data)
+    data_cdf_decile = extract_cdf_decile(data_cdf)
+
+    results['%smin' % key_prefix] = data_min
+    results['%smax' % key_prefix] = data_max
+    results['%smean' % key_prefix] = data_mean
+    results['%scdf' % key_prefix] = data_cdf
+    results['%scdf_decile' % key_prefix] = data_cdf_decile
+    results['%sraw_data' % key_prefix] = data
+
+    if num_samples > 1:
+        data_stdev = statistics.stdev(data)
+        results['%sstdev' % key_prefix] = data_stdev
+        ad.log.info(
+            '%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f, stdev=%.2f, cdf_decile=%s',
+            log_prefix, num_samples, data_min, data_max, data_mean, data_stdev,
+            data_cdf_decile)
+    else:
+        ad.log.info(
+            '%s: num_samples=%d, min=%.2f, max=%.2f, mean=%.2f, cdf_decile=%s',
+            log_prefix, num_samples, data_min, data_max, data_mean,
+            data_cdf_decile)
+
+
+def extract_cdf_decile(cdf):
+    """Extracts the 10%, 20%, ..., 90% points from the CDF and returns their
+  value (a list of 9 values).
+
+  Since CDF may not (will not) have exact x% value picks the value >= x%.
+
+  Args:
+    cdf: a list of 2 lists, the X and Y of the CDF.
+  """
+    decades = []
+    next_decade = 10
+    for x, y in zip(cdf[0], cdf[1]):
+        while 100 * y >= next_decade:
+            decades.append(x)
+            next_decade = next_decade + 10
+        if next_decade == 100:
+            break
+    return decades
+
+
+def extract_cdf(data):
+    """Calculates the Cumulative Distribution Function (CDF) of the data.
+
+  Args:
+      data: A list containing data (does not have to be sorted).
+
+  Returns: a list of 2 lists: the X and Y axis of the CDF.
+  """
+    x = []
+    cdf = []
+    if not data:
+        return (x, cdf)
+
+    all_values = sorted(data)
+    for val in all_values:
+        if not x:
+            x.append(val)
+            cdf.append(1)
+        else:
+            if x[-1] == val:
+                cdf[-1] += 1
+            else:
+                x.append(val)
+                cdf.append(cdf[-1] + 1)
+
+    scale = 1.0 / len(all_values)
+    for i in range(len(cdf)):
+        cdf[i] = cdf[i] * scale
+
+    return (x, cdf)
+
+
+def get_mac_addr(device, interface):
+    """Get the MAC address of the specified interface. Uses ifconfig and parses
+  its output. Normalizes string to remove ':' and upper case.
+
+  Args:
+    device: Device on which to query the interface MAC address.
+    interface: Name of the interface for which to obtain the MAC address.
+  """
+    out = device.adb.shell("ifconfig %s" % interface)
+    res = re.match(".* HWaddr (\S+).*", out, re.S)
+    asserts.assert_true(
+        res,
+        'Unable to obtain MAC address for interface %s' % interface,
+        extras=out)
+    return res.group(1).upper().replace(':', '')
+
+
+def get_ipv6_addr(device, interface):
+    """Get the IPv6 address of the specified interface. Uses ifconfig and parses
+  its output. Returns a None if the interface does not have an IPv6 address
+  (indicating it is not UP).
+
+  Args:
+    device: Device on which to query the interface IPv6 address.
+    interface: Name of the interface for which to obtain the IPv6 address.
+  """
+    out = device.adb.shell("ifconfig %s" % interface)
+    res = re.match(".*inet6 addr: (\S+)/.*", out, re.S)
+    if not res:
+        return None
+    return res.group(1)
+
+
+def verify_socket_connect(dut_s, dut_c, ipv6_s, ipv6_c, port):
+    """Verify the socket connection between server (dut_s) and client (dut_c)
+    using the given IPv6 addresses.
+
+    Opens a ServerSocket on the server and tries to connect to it
+    from the client.
+
+    Args:
+        dut_s, dut_c: the server and client devices under test (DUTs)
+        ipv6_s, ipv6_c: the scoped link-local addresses of the server and client.
+        port: the port to use
+    Return: True on success, False otherwise
+    """
+    server_sock = None
+    sock_c = None
+    sock_s = None
+    try:
+        server_sock = sutils.open_server_socket(dut_s, ipv6_s, port)
+        port_to_use = port
+        if port == 0:
+            port_to_use = dut_s.droid.getTcpServerSocketPort(server_sock)
+        sock_c, sock_s = sutils.open_connect_socket(
+            dut_c, dut_s, ipv6_c, ipv6_s, 0, port_to_use, server_sock)
+    except:
+        return False
+    finally:
+        if sock_c is not None:
+            sutils.close_socket(dut_c, sock_c)
+        if sock_s is not None:
+            sutils.close_socket(dut_s, sock_s)
+        if server_sock is not None:
+            sutils.close_server_socket(dut_s, server_sock)
+    return True
+
+
+def run_ping6(dut, target_ip, duration=60):
+    """Run ping test and return the latency result
+
+    Args:
+        dut: the dut which run the ping cmd
+        target_ip: target IP Address for ping
+        duration: the duration time of the ping
+
+    return: dict contains "min/avg/max/mdev" result
+    """
+    cmd = "ping6 -w %d %s" % (duration, target_ip)
+    ping_result = dut.adb.shell(cmd, timeout=duration + 1)
+    res = re.match(".*mdev = (\S+) .*", ping_result, re.S)
+    asserts.assert_true(res, "Cannot reach the IP address %s", target_ip)
+    title = ["min", "avg", "max", "mdev"]
+    result = res.group(1).split("/")
+    latency_result = {}
+    for i in range(len(title)):
+        latency_result[title[i]] = result[i]
+    return latency_result
+
+
+#########################################################
+# Aware primitives
+#########################################################
+
+
+def request_network(dut, ns):
+    """Request a Wi-Fi Aware network.
+
+  Args:
+    dut: Device
+    ns: Network specifier
+  Returns: the request key
+  """
+    network_req = {"TransportType": 5, "NetworkSpecifier": ns}
+    return dut.droid.connectivityRequestWifiAwareNetwork(network_req)
+
+
+def get_network_specifier(dut, id, dev_type, peer_mac, sec):
+    """Create a network specifier for the device based on the security
+  configuration.
+
+  Args:
+    dut: device
+    id: session ID
+    dev_type: device type - Initiator or Responder
+    peer_mac: the discovery MAC address of the peer
+    sec: security configuration
+  """
+    if sec is None:
+        return dut.droid.wifiAwareCreateNetworkSpecifierOob(
+            id, dev_type, peer_mac)
+    if isinstance(sec, str):
+        return dut.droid.wifiAwareCreateNetworkSpecifierOob(
+            id, dev_type, peer_mac, sec)
+    return dut.droid.wifiAwareCreateNetworkSpecifierOob(
+        id, dev_type, peer_mac, None, sec)
+
+
+def configure_power_setting(device, mode, name, value):
+    """Use the command-line API to configure the power setting
+
+  Args:
+    device: Device on which to perform configuration
+    mode: The power mode being set, should be "default", "inactive", or "idle"
+    name: One of the power settings from 'wifiaware set-power'.
+    value: An integer.
+  """
+    device.adb.shell(
+        "cmd wifiaware native_api set-power %s %s %d" % (mode, name, value))
+
+
+def configure_mac_random_interval(device, interval_sec):
+    """Use the command-line API to configure the MAC address randomization
+  interval.
+
+  Args:
+    device: Device on which to perform configuration
+    interval_sec: The MAC randomization interval in seconds. A value of 0
+                  disables all randomization.
+  """
+    device.adb.shell("cmd wifiaware native_api set mac_random_interval_sec %d"
+                     % interval_sec)
+
+
+def configure_ndp_allow_any_override(device, override_api_check):
+    """Use the command-line API to configure whether an NDP Responder may be
+  configured to accept an NDP request from ANY peer.
+
+  By default the target API level of the requesting app determines whether such
+  configuration is permitted. This allows overriding the API check and allowing
+  it.
+
+  Args:
+    device: Device on which to perform configuration.
+    override_api_check: True to allow a Responder to ANY configuration, False to
+                        perform the API level check.
+  """
+    device.adb.shell("cmd wifiaware state_mgr allow_ndp_any %s" %
+                     ("true" if override_api_check else "false"))
+
+
+def config_settings_high_power(device):
+    """Configure device's power settings values to high power mode -
+  whether device is in interactive or non-interactive modes"""
+    configure_power_setting(device, "default", "dw_24ghz",
+                            aconsts.POWER_DW_24_INTERACTIVE)
+    configure_power_setting(device, "default", "dw_5ghz",
+                            aconsts.POWER_DW_5_INTERACTIVE)
+    configure_power_setting(device, "default", "disc_beacon_interval_ms",
+                            aconsts.POWER_DISC_BEACON_INTERVAL_INTERACTIVE)
+    configure_power_setting(device, "default", "num_ss_in_discovery",
+                            aconsts.POWER_NUM_SS_IN_DISC_INTERACTIVE)
+    configure_power_setting(device, "default", "enable_dw_early_term",
+                            aconsts.POWER_ENABLE_DW_EARLY_TERM_INTERACTIVE)
+
+    configure_power_setting(device, "inactive", "dw_24ghz",
+                            aconsts.POWER_DW_24_INTERACTIVE)
+    configure_power_setting(device, "inactive", "dw_5ghz",
+                            aconsts.POWER_DW_5_INTERACTIVE)
+    configure_power_setting(device, "inactive", "disc_beacon_interval_ms",
+                            aconsts.POWER_DISC_BEACON_INTERVAL_INTERACTIVE)
+    configure_power_setting(device, "inactive", "num_ss_in_discovery",
+                            aconsts.POWER_NUM_SS_IN_DISC_INTERACTIVE)
+    configure_power_setting(device, "inactive", "enable_dw_early_term",
+                            aconsts.POWER_ENABLE_DW_EARLY_TERM_INTERACTIVE)
+
+
+def config_settings_low_power(device):
+    """Configure device's power settings values to low power mode - whether
+  device is in interactive or non-interactive modes"""
+    configure_power_setting(device, "default", "dw_24ghz",
+                            aconsts.POWER_DW_24_NON_INTERACTIVE)
+    configure_power_setting(device, "default", "dw_5ghz",
+                            aconsts.POWER_DW_5_NON_INTERACTIVE)
+    configure_power_setting(device, "default", "disc_beacon_interval_ms",
+                            aconsts.POWER_DISC_BEACON_INTERVAL_NON_INTERACTIVE)
+    configure_power_setting(device, "default", "num_ss_in_discovery",
+                            aconsts.POWER_NUM_SS_IN_DISC_NON_INTERACTIVE)
+    configure_power_setting(device, "default", "enable_dw_early_term",
+                            aconsts.POWER_ENABLE_DW_EARLY_TERM_NON_INTERACTIVE)
+
+    configure_power_setting(device, "inactive", "dw_24ghz",
+                            aconsts.POWER_DW_24_NON_INTERACTIVE)
+    configure_power_setting(device, "inactive", "dw_5ghz",
+                            aconsts.POWER_DW_5_NON_INTERACTIVE)
+    configure_power_setting(device, "inactive", "disc_beacon_interval_ms",
+                            aconsts.POWER_DISC_BEACON_INTERVAL_NON_INTERACTIVE)
+    configure_power_setting(device, "inactive", "num_ss_in_discovery",
+                            aconsts.POWER_NUM_SS_IN_DISC_NON_INTERACTIVE)
+    configure_power_setting(device, "inactive", "enable_dw_early_term",
+                            aconsts.POWER_ENABLE_DW_EARLY_TERM_NON_INTERACTIVE)
+
+
+def config_power_settings(device,
+                          dw_24ghz,
+                          dw_5ghz,
+                          disc_beacon_interval=None,
+                          num_ss_in_disc=None,
+                          enable_dw_early_term=None):
+    """Configure device's discovery window (DW) values to the specified values -
+  whether the device is in interactive or non-interactive mode.
+
+  Args:
+    dw_24ghz: DW interval in the 2.4GHz band.
+    dw_5ghz: DW interval in the 5GHz band.
+    disc_beacon_interval: The discovery beacon interval (in ms). If None then
+                          not set.
+    num_ss_in_disc: Number of spatial streams to use for discovery. If None then
+                    not set.
+    enable_dw_early_term: If True then enable early termination of the DW. If
+                          None then not set.
+  """
+    configure_power_setting(device, "default", "dw_24ghz", dw_24ghz)
+    configure_power_setting(device, "default", "dw_5ghz", dw_5ghz)
+    configure_power_setting(device, "inactive", "dw_24ghz", dw_24ghz)
+    configure_power_setting(device, "inactive", "dw_5ghz", dw_5ghz)
+
+    if disc_beacon_interval is not None:
+        configure_power_setting(device, "default", "disc_beacon_interval_ms",
+                                disc_beacon_interval)
+        configure_power_setting(device, "inactive", "disc_beacon_interval_ms",
+                                disc_beacon_interval)
+
+    if num_ss_in_disc is not None:
+        configure_power_setting(device, "default", "num_ss_in_discovery",
+                                num_ss_in_disc)
+        configure_power_setting(device, "inactive", "num_ss_in_discovery",
+                                num_ss_in_disc)
+
+    if enable_dw_early_term is not None:
+        configure_power_setting(device, "default", "enable_dw_early_term",
+                                enable_dw_early_term)
+        configure_power_setting(device, "inactive", "enable_dw_early_term",
+                                enable_dw_early_term)
+
+
+def create_discovery_config(service_name,
+                            d_type,
+                            ssi=None,
+                            match_filter=None,
+                            match_filter_list=None,
+                            ttl=0,
+                            term_cb_enable=True):
+    """Create a publish discovery configuration based on input parameters.
+
+  Args:
+    service_name: Service name - required
+    d_type: Discovery type (publish or subscribe constants)
+    ssi: Supplemental information - defaults to None
+    match_filter, match_filter_list: The match_filter, only one mechanism can
+                                     be used to specify. Defaults to None.
+    ttl: Time-to-live - defaults to 0 (i.e. non-self terminating)
+    term_cb_enable: True (default) to enable callback on termination, False
+                    means that no callback is called when session terminates.
+  Returns:
+    publish discovery configuration object.
+  """
+    config = {}
+    config[aconsts.DISCOVERY_KEY_SERVICE_NAME] = service_name
+    config[aconsts.DISCOVERY_KEY_DISCOVERY_TYPE] = d_type
+    if ssi is not None:
+        config[aconsts.DISCOVERY_KEY_SSI] = ssi
+    if match_filter is not None:
+        config[aconsts.DISCOVERY_KEY_MATCH_FILTER] = match_filter
+    if match_filter_list is not None:
+        config[aconsts.DISCOVERY_KEY_MATCH_FILTER_LIST] = match_filter_list
+    config[aconsts.DISCOVERY_KEY_TTL] = ttl
+    config[aconsts.DISCOVERY_KEY_TERM_CB_ENABLED] = term_cb_enable
+    return config
+
+
+def add_ranging_to_pub(p_config, enable_ranging):
+    """Add ranging enabled configuration to a publish configuration (only relevant
+  for publish configuration).
+
+  Args:
+    p_config: The Publish discovery configuration.
+    enable_ranging: True to enable ranging, False to disable.
+  Returns:
+    The modified publish configuration.
+  """
+    p_config[aconsts.DISCOVERY_KEY_RANGING_ENABLED] = enable_ranging
+    return p_config
+
+
+def add_ranging_to_sub(s_config, min_distance_mm, max_distance_mm):
+    """Add ranging distance configuration to a subscribe configuration (only
+  relevant to a subscribe configuration).
+
+  Args:
+    s_config: The Subscribe discovery configuration.
+    min_distance_mm, max_distance_mm: The min and max distance specification.
+                                      Used if not None.
+  Returns:
+    The modified subscribe configuration.
+  """
+    if min_distance_mm is not None:
+        s_config[aconsts.DISCOVERY_KEY_MIN_DISTANCE_MM] = min_distance_mm
+    if max_distance_mm is not None:
+        s_config[aconsts.DISCOVERY_KEY_MAX_DISTANCE_MM] = max_distance_mm
+    return s_config
+
+
+def attach_with_identity(dut):
+    """Start an Aware session (attach) and wait for confirmation and identity
+  information (mac address).
+
+  Args:
+    dut: Device under test
+  Returns:
+    id: Aware session ID.
+    mac: Discovery MAC address of this device.
+  """
+    id = dut.droid.wifiAwareAttach(True)
+    wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
+    event = wait_for_event(dut, aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    mac = event["data"]["mac"]
+
+    return id, mac
+
+
+def create_discovery_pair(p_dut,
+                          s_dut,
+                          p_config,
+                          s_config,
+                          device_startup_offset,
+                          msg_id=None):
+    """Creates a discovery session (publish and subscribe), and waits for
+  service discovery - at that point the sessions are connected and ready for
+  further messaging of data-path setup.
+
+  Args:
+    p_dut: Device to use as publisher.
+    s_dut: Device to use as subscriber.
+    p_config: Publish configuration.
+    s_config: Subscribe configuration.
+    device_startup_offset: Number of seconds to offset the enabling of NAN on
+                           the two devices.
+    msg_id: Controls whether a message is sent from Subscriber to Publisher
+            (so that publisher has the sub's peer ID). If None then not sent,
+            otherwise should be an int for the message id.
+  Returns: variable size list of:
+    p_id: Publisher attach session id
+    s_id: Subscriber attach session id
+    p_disc_id: Publisher discovery session id
+    s_disc_id: Subscriber discovery session id
+    peer_id_on_sub: Peer ID of the Publisher as seen on the Subscriber
+    peer_id_on_pub: Peer ID of the Subscriber as seen on the Publisher. Only
+                    included if |msg_id| is not None.
+  """
+    p_dut.pretty_name = 'Publisher'
+    s_dut.pretty_name = 'Subscriber'
+
+    # Publisher+Subscriber: attach and wait for confirmation
+    p_id = p_dut.droid.wifiAwareAttach()
+    wait_for_event(p_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    time.sleep(device_startup_offset)
+    s_id = s_dut.droid.wifiAwareAttach()
+    wait_for_event(s_dut, aconsts.EVENT_CB_ON_ATTACHED)
+
+    # Publisher: start publish and wait for confirmation
+    p_disc_id = p_dut.droid.wifiAwarePublish(p_id, p_config)
+    wait_for_event(p_dut, aconsts.SESSION_CB_ON_PUBLISH_STARTED)
+
+    # Subscriber: start subscribe and wait for confirmation
+    s_disc_id = s_dut.droid.wifiAwareSubscribe(s_id, s_config)
+    wait_for_event(s_dut, aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED)
+
+    # Subscriber: wait for service discovery
+    discovery_event = wait_for_event(s_dut,
+                                     aconsts.SESSION_CB_ON_SERVICE_DISCOVERED)
+    peer_id_on_sub = discovery_event['data'][aconsts.SESSION_CB_KEY_PEER_ID]
+
+    # Optionally send a message from Subscriber to Publisher
+    if msg_id is not None:
+        ping_msg = 'PING'
+
+        # Subscriber: send message to peer (Publisher)
+        s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub, msg_id,
+                                         ping_msg, aconsts.MAX_TX_RETRIES)
+        sub_tx_msg_event = wait_for_event(s_dut,
+                                          aconsts.SESSION_CB_ON_MESSAGE_SENT)
+        asserts.assert_equal(
+            msg_id,
+            sub_tx_msg_event['data'][aconsts.SESSION_CB_KEY_MESSAGE_ID],
+            'Subscriber -> Publisher message ID corrupted')
+
+        # Publisher: wait for received message
+        pub_rx_msg_event = wait_for_event(
+            p_dut, aconsts.SESSION_CB_ON_MESSAGE_RECEIVED)
+        peer_id_on_pub = pub_rx_msg_event['data'][
+            aconsts.SESSION_CB_KEY_PEER_ID]
+        asserts.assert_equal(
+            ping_msg,
+            pub_rx_msg_event['data'][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING],
+            'Subscriber -> Publisher message corrupted')
+        return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub, peer_id_on_pub
+
+    return p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub
+
+
+def create_ib_ndp(p_dut, s_dut, p_config, s_config, device_startup_offset):
+    """Create an NDP (using in-band discovery)
+
+  Args:
+    p_dut: Device to use as publisher.
+    s_dut: Device to use as subscriber.
+    p_config: Publish configuration.
+    s_config: Subscribe configuration.
+    device_startup_offset: Number of seconds to offset the enabling of NAN on
+                           the two devices.
+  """
+    (p_id, s_id, p_disc_id, s_disc_id, peer_id_on_sub,
+     peer_id_on_pub) = create_discovery_pair(
+         p_dut, s_dut, p_config, s_config, device_startup_offset, msg_id=9999)
+
+    # Publisher: request network
+    p_req_key = request_network(
+        p_dut,
+        p_dut.droid.wifiAwareCreateNetworkSpecifier(p_disc_id, peer_id_on_pub,
+                                                    None))
+
+    # Subscriber: request network
+    s_req_key = request_network(
+        s_dut,
+        s_dut.droid.wifiAwareCreateNetworkSpecifier(s_disc_id, peer_id_on_sub,
+                                                    None))
+
+    # Publisher & Subscriber: wait for network formation
+    p_net_event_nc = wait_for_event_with_keys(
+        p_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+    s_net_event_nc = wait_for_event_with_keys(
+        s_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+    # validate no leak of information
+    asserts.assert_false(
+        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in p_net_event_nc["data"],
+        "Network specifier leak!")
+    asserts.assert_false(
+        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in s_net_event_nc["data"],
+        "Network specifier leak!")
+
+    # note that Pub <-> Sub since IPv6 are of peer's!
+    p_ipv6 = s_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+    s_ipv6 = p_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+
+    p_net_event_lp = wait_for_event_with_keys(
+        p_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, p_req_key))
+    s_net_event_lp = wait_for_event_with_keys(
+        s_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, s_req_key))
+
+    p_aware_if = p_net_event_lp["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+    s_aware_if = s_net_event_lp["data"][cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+    return p_req_key, s_req_key, p_aware_if, s_aware_if, p_ipv6, s_ipv6
+
+
+def create_oob_ndp_on_sessions(init_dut, resp_dut, init_id, init_mac, resp_id,
+                               resp_mac):
+    """Create an NDP on top of existing Aware sessions (using OOB discovery)
+
+  Args:
+    init_dut: Initiator device
+    resp_dut: Responder device
+    init_id: Initiator attach session id
+    init_mac: Initiator discovery MAC address
+    resp_id: Responder attach session id
+    resp_mac: Responder discovery MAC address
+  Returns:
+    init_req_key: Initiator network request
+    resp_req_key: Responder network request
+    init_aware_if: Initiator Aware data interface
+    resp_aware_if: Responder Aware data interface
+    init_ipv6: Initiator IPv6 address
+    resp_ipv6: Responder IPv6 address
+  """
+    # Responder: request network
+    resp_req_key = request_network(
+        resp_dut,
+        resp_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+            resp_id, aconsts.DATA_PATH_RESPONDER, init_mac, None))
+
+    # Initiator: request network
+    init_req_key = request_network(
+        init_dut,
+        init_dut.droid.wifiAwareCreateNetworkSpecifierOob(
+            init_id, aconsts.DATA_PATH_INITIATOR, resp_mac, None))
+
+    # Initiator & Responder: wait for network formation
+    init_net_event_nc = wait_for_event_with_keys(
+        init_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+    resp_net_event_nc = wait_for_event_with_keys(
+        resp_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_CAPABILITIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+    # validate no leak of information
+    asserts.assert_false(
+        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in init_net_event_nc["data"],
+        "Network specifier leak!")
+    asserts.assert_false(
+        cconsts.NETWORK_CB_KEY_NETWORK_SPECIFIER in resp_net_event_nc["data"],
+        "Network specifier leak!")
+
+    # note that Init <-> Resp since IPv6 are of peer's!
+    resp_ipv6 = init_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+    init_ipv6 = resp_net_event_nc["data"][aconsts.NET_CAP_IPV6]
+
+    init_net_event_lp = wait_for_event_with_keys(
+        init_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, init_req_key))
+    resp_net_event_lp = wait_for_event_with_keys(
+        resp_dut, cconsts.EVENT_NETWORK_CALLBACK, EVENT_NDP_TIMEOUT,
+        (cconsts.NETWORK_CB_KEY_EVENT,
+         cconsts.NETWORK_CB_LINK_PROPERTIES_CHANGED),
+        (cconsts.NETWORK_CB_KEY_ID, resp_req_key))
+
+    init_aware_if = init_net_event_lp['data'][
+        cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+    resp_aware_if = resp_net_event_lp['data'][
+        cconsts.NETWORK_CB_KEY_INTERFACE_NAME]
+
+    return (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
+            init_ipv6, resp_ipv6)
+
+
+def create_oob_ndp(init_dut, resp_dut):
+    """Create an NDP (using OOB discovery)
+
+  Args:
+    init_dut: Initiator device
+    resp_dut: Responder device
+  """
+    init_dut.pretty_name = 'Initiator'
+    resp_dut.pretty_name = 'Responder'
+
+    # Initiator+Responder: attach and wait for confirmation & identity
+    init_id = init_dut.droid.wifiAwareAttach(True)
+    wait_for_event(init_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    init_ident_event = wait_for_event(init_dut,
+                                      aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    init_mac = init_ident_event['data']['mac']
+    resp_id = resp_dut.droid.wifiAwareAttach(True)
+    wait_for_event(resp_dut, aconsts.EVENT_CB_ON_ATTACHED)
+    resp_ident_event = wait_for_event(resp_dut,
+                                      aconsts.EVENT_CB_ON_IDENTITY_CHANGED)
+    resp_mac = resp_ident_event['data']['mac']
+
+    # wait for for devices to synchronize with each other - there are no other
+    # mechanisms to make sure this happens for OOB discovery (except retrying
+    # to execute the data-path request)
+    time.sleep(WAIT_FOR_CLUSTER)
+
+    (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
+     init_ipv6, resp_ipv6) = create_oob_ndp_on_sessions(
+         init_dut, resp_dut, init_id, init_mac, resp_id, resp_mac)
+
+    return (init_req_key, resp_req_key, init_aware_if, resp_aware_if,
+            init_ipv6, resp_ipv6)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py b/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py
new file mode 100644
index 0000000..4274603
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/ota_chamber.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import contextlib
+import io
+import serial
+import time
+from acts import logger
+from acts import utils
+
+SHORT_SLEEP = 1
+CHAMBER_SLEEP = 30
+
+
+def create(configs):
+    """Factory method for OTA chambers.
+
+    Args:
+        configs: list of dicts with chamber settings. settings must contain the
+        following: type (string denoting type of chamber)
+    """
+    objs = []
+    for config in configs:
+        try:
+            chamber_class = globals()[config['model']]
+        except KeyError:
+            raise KeyError('Invalid chamber configuration.')
+        objs.append(chamber_class(config))
+    return objs
+
+
+def detroy(objs):
+    return
+
+
+class OtaChamber(object):
+    """Base class implementation for OTA chamber.
+
+    Base class provides functions whose implementation is shared by all
+    chambers.
+    """
+    def reset_chamber(self):
+        """Resets the chamber to its zero/home state."""
+        raise NotImplementedError
+
+    def set_orientation(self, orientation):
+        """Set orientation for turn table in OTA chamber.
+
+        Args:
+            angle: desired turn table orientation in degrees
+        """
+        raise NotImplementedError
+
+    def set_stirrer_pos(self, stirrer_id, position):
+        """Starts turntables and stirrers in OTA chamber."""
+        raise NotImplementedError
+
+    def start_continuous_stirrers(self):
+        """Starts turntables and stirrers in OTA chamber."""
+        raise NotImplementedError
+
+    def stop_continuous_stirrers(self):
+        """Stops turntables and stirrers in OTA chamber."""
+        raise NotImplementedError
+
+    def step_stirrers(self, steps):
+        """Move stepped stirrers in OTA chamber to next step."""
+        raise NotImplementedError
+
+
+class MockChamber(OtaChamber):
+    """Class that implements mock chamber for test development and debug."""
+    def __init__(self, config):
+        self.config = config.copy()
+        self.device_id = self.config['device_id']
+        self.log = logger.create_tagged_trace_logger('OtaChamber|{}'.format(
+            self.device_id))
+        self.current_mode = None
+
+    def set_orientation(self, orientation):
+        self.log.info('Setting orientation to {} degrees.'.format(orientation))
+
+    def reset_chamber(self):
+        self.log.info('Resetting chamber to home state')
+
+    def set_stirrer_pos(self, stirrer_id, position):
+        """Starts turntables and stirrers in OTA chamber."""
+        self.log.info('Setting stirrer {} to {}.'.format(stirrer_id, position))
+
+    def start_continuous_stirrers(self):
+        """Starts turntables and stirrers in OTA chamber."""
+        self.log.info('Starting continuous stirrer motion')
+
+    def stop_continuous_stirrers(self):
+        """Stops turntables and stirrers in OTA chamber."""
+        self.log.info('Stopping continuous stirrer motion')
+
+    def configure_stepped_stirrers(self, steps):
+        """Programs parameters for stepped stirrers in OTA chamber."""
+        self.log.info('Configuring stepped stirrers')
+
+    def step_stirrers(self, steps):
+        """Move stepped stirrers in OTA chamber to next step."""
+        self.log.info('Moving stirrers to the next step')
+
+
+class OctoboxChamber(OtaChamber):
+    """Class that implements Octobox chamber."""
+    def __init__(self, config):
+        self.config = config.copy()
+        self.device_id = self.config['device_id']
+        self.log = logger.create_tagged_trace_logger('OtaChamber|{}'.format(
+            self.device_id))
+        self.TURNTABLE_FILE_PATH = '/usr/local/bin/fnPerformaxCmd'
+        utils.exe_cmd('sudo {} -d {} -i 0'.format(self.TURNTABLE_FILE_PATH,
+                                                  self.device_id))
+        self.current_mode = None
+
+    def set_orientation(self, orientation):
+        self.log.info('Setting orientation to {} degrees.'.format(orientation))
+        utils.exe_cmd('sudo {} -d {} -p {}'.format(self.TURNTABLE_FILE_PATH,
+                                                   self.device_id,
+                                                   orientation))
+
+    def reset_chamber(self):
+        self.log.info('Resetting chamber to home state')
+        self.set_orientation(0)
+
+
+class ChamberAutoConnect(object):
+    def __init__(self, chamber, chamber_config):
+        self._chamber = chamber
+        self._config = chamber_config
+
+    def __getattr__(self, item):
+        def chamber_call(*args, **kwargs):
+            self._chamber.connect(self._config['ip_address'],
+                                  self._config['username'],
+                                  self._config['password'])
+            return getattr(self._chamber, item)(*args, **kwargs)
+
+        return chamber_call
+
+
+class BluetestChamber(OtaChamber):
+    """Class that implements Octobox chamber."""
+    def __init__(self, config):
+        import flow
+        self.config = config.copy()
+        self.log = logger.create_tagged_trace_logger('OtaChamber|{}'.format(
+            self.config['ip_address']))
+        self.chamber = ChamberAutoConnect(flow.Flow(), self.config)
+        self.stirrer_ids = [0, 1, 2]
+        self.current_mode = None
+
+    # Capture print output decorator
+    @staticmethod
+    def _capture_output(func, *args, **kwargs):
+        """Creates a decorator to capture stdout from bluetest module"""
+        f = io.StringIO()
+        with contextlib.redirect_stdout(f):
+            func(*args, **kwargs)
+        output = f.getvalue()
+        return output
+
+    def _connect(self):
+        self.chamber.connect(self.config['ip_address'],
+                             self.config['username'], self.config['password'])
+
+    def _init_manual_mode(self):
+        self.current_mode = 'manual'
+        for stirrer_id in self.stirrer_ids:
+            out = self._capture_output(
+                self.chamber.chamber_stirring_manual_init, stirrer_id)
+            if "failed" in out:
+                self.log.warning("Initialization error: {}".format(out))
+        time.sleep(CHAMBER_SLEEP)
+
+    def _init_continuous_mode(self):
+        self.current_mode = 'continuous'
+        self.chamber.chamber_stirring_continuous_init()
+
+    def _init_stepped_mode(self, steps):
+        self.current_mode = 'stepped'
+        self.current_stepped_pos = 0
+        self.chamber.chamber_stirring_stepped_init(steps, False)
+
+    def set_stirrer_pos(self, stirrer_id, position):
+        if self.current_mode != 'manual':
+            self._init_manual_mode()
+        self.log.info('Setting stirrer {} to {}.'.format(stirrer_id, position))
+        out = self._capture_output(
+            self.chamber.chamber_stirring_manual_set_pos, stirrer_id, position)
+        if "failed" in out:
+            self.log.warning("Bluetest error: {}".format(out))
+            self.log.warning("Set position failed. Retrying.")
+            self.current_mode = None
+            self.set_stirrer_pos(stirrer_id, position)
+        else:
+            self._capture_output(self.chamber.chamber_stirring_manual_wait,
+                                 CHAMBER_SLEEP)
+            self.log.warning('Stirrer {} at {}.'.format(stirrer_id, position))
+
+    def set_orientation(self, orientation):
+        self.set_stirrer_pos(2, orientation * 100 / 360)
+
+    def start_continuous_stirrers(self):
+        if self.current_mode != 'continuous':
+            self._init_continuous_mode()
+        self.chamber.chamber_stirring_continuous_start()
+
+    def stop_continuous_stirrers(self):
+        self.chamber.chamber_stirring_continuous_stop()
+
+    def step_stirrers(self, steps):
+        if self.current_mode != 'stepped':
+            self._init_stepped_mode(steps)
+        if self.current_stepped_pos == 0:
+            self.current_stepped_pos += 1
+            return
+        self.current_stepped_pos += 1
+        self.chamber.chamber_stirring_stepped_next_pos()
+
+    def reset_chamber(self):
+        if self.current_mode == 'continuous':
+            self._init_continuous_mode()
+            time.sleep(SHORT_SLEEP)
+            self._init_continuous_mode()
+        else:
+            self._init_manual_mode()
+
+
+class EInstrumentChamber(OtaChamber):
+    """Class that implements Einstrument Chamber."""
+    def __init__(self, config):
+        self.config = config.copy()
+        self.device_id = self.config['device_id']
+        self.log = logger.create_tagged_trace_logger('EInstrumentChamber|{}'.format(
+            self.device_id))
+        self.current_mode = None
+        self.ser = self._get_serial(config['port'])
+
+    def _get_serial(self, port, baud=9600):
+        """Read com port.
+
+        Args:
+            port: turn table com port
+            baud: baud rate
+        """
+        ser = serial.Serial(port, baud)
+        return ser
+
+    def set_orientation(self, orientation):
+        if int(orientation) > 360:
+            orientation = int(orientation) % 360
+        elif int(orientation) < 0:
+            orientation = 0
+        self.log.info('Setting orientation to {} degrees.'.format(orientation))
+        orientation = str('DG') + str(orientation) + str(';')
+        self.ser.write(orientation.encode())
+        return orientation
+
+    def reset_chamber(self):
+        self.log.info('Resetting turn table to zero degree')
+        self.set_orientation(0)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py b/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py
new file mode 100644
index 0000000..73fb033
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/ota_sniffer.py
@@ -0,0 +1,572 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import csv
+import os
+import posixpath
+import time
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+
+from acts import context
+from acts import logger
+from acts import utils
+from acts.controllers.utils_lib import ssh
+
+WifiEnums = wutils.WifiEnums
+SNIFFER_TIMEOUT = 6
+
+
+def create(configs):
+    """Factory method for sniffer.
+    Args:
+        configs: list of dicts with sniffer settings.
+        Settings must contain the following : ssh_settings, type, OS, interface.
+
+    Returns:
+        objs: list of sniffer class objects.
+    """
+    objs = []
+    for config in configs:
+        try:
+            if config['type'] == 'tshark':
+                if config['os'] == 'unix':
+                    objs.append(TsharkSnifferOnUnix(config))
+                elif config['os'] == 'linux':
+                    objs.append(TsharkSnifferOnLinux(config))
+                else:
+                    raise RuntimeError('Wrong sniffer config')
+
+            elif config['type'] == 'mock':
+                objs.append(MockSniffer(config))
+        except KeyError:
+            raise KeyError('Invalid sniffer configurations')
+        return objs
+
+
+def destroy(objs):
+    return
+
+
+class OtaSnifferBase(object):
+    """Base class defining common sniffers functions."""
+
+    _log_file_counter = 0
+
+    @property
+    def started(self):
+        raise NotImplementedError('started must be specified.')
+
+    def start_capture(self, network, duration=30):
+        """Starts the sniffer Capture.
+
+        Args:
+            network: dict containing network information such as SSID, etc.
+            duration: duration of sniffer capture in seconds.
+        """
+        raise NotImplementedError('start_capture must be specified.')
+
+    def stop_capture(self, tag=''):
+        """Stops the sniffer Capture.
+
+        Args:
+            tag: string to tag sniffer capture file name with.
+        """
+        raise NotImplementedError('stop_capture must be specified.')
+
+    def _get_remote_dump_path(self):
+        """Returns name of the sniffer dump file."""
+        remote_file_name = 'sniffer_dump.{}'.format(
+            self.sniffer_output_file_type)
+        remote_dump_path = posixpath.join(posixpath.sep, 'tmp', remote_file_name)
+        return remote_dump_path
+
+    def _get_full_file_path(self, tag=None):
+        """Returns the full file path for the sniffer capture dump file.
+
+        Returns the full file path (on test machine) for the sniffer capture
+        dump file.
+
+        Args:
+            tag: The tag appended to the sniffer capture dump file .
+        """
+        tags = [tag, 'count', OtaSnifferBase._log_file_counter]
+        out_file_name = 'Sniffer_Capture_%s.%s' % ('_'.join([
+            str(x) for x in tags if x != '' and x is not None
+        ]), self.sniffer_output_file_type)
+        OtaSnifferBase._log_file_counter += 1
+
+        file_path = os.path.join(self.log_path, out_file_name)
+        return file_path
+
+    @property
+    def log_path(self):
+        current_context = context.get_current_context()
+        full_out_dir = os.path.join(current_context.get_full_output_path(),
+                                    'sniffer_captures')
+
+        # Ensure the directory exists.
+        os.makedirs(full_out_dir, exist_ok=True)
+
+        return full_out_dir
+
+
+class MockSniffer(OtaSnifferBase):
+    """Class that implements mock sniffer for test development and debug."""
+    def __init__(self, config):
+        self.log = logger.create_tagged_trace_logger('Mock Sniffer')
+
+    def start_capture(self, network, duration=30):
+        """Starts sniffer capture on the specified machine.
+
+        Args:
+            network: dict of network credentials.
+            duration: duration of the sniff.
+        """
+        self.log.info('Starting sniffer.')
+
+    def stop_capture(self):
+        """Stops the sniffer.
+
+        Returns:
+            log_file: name of processed sniffer.
+        """
+
+        self.log.info('Stopping sniffer.')
+        log_file = self._get_full_file_path()
+        with open(log_file, 'w') as file:
+            file.write('this is a sniffer dump.')
+        return log_file
+
+
+class TsharkSnifferBase(OtaSnifferBase):
+    """Class that implements Tshark based sniffer controller. """
+
+    TYPE_SUBTYPE_DICT = {
+        '0': 'Association Requests',
+        '1': 'Association Responses',
+        '2': 'Reassociation Requests',
+        '3': 'Resssociation Responses',
+        '4': 'Probe Requests',
+        '5': 'Probe Responses',
+        '8': 'Beacon',
+        '9': 'ATIM',
+        '10': 'Disassociations',
+        '11': 'Authentications',
+        '12': 'Deauthentications',
+        '13': 'Actions',
+        '24': 'Block ACK Requests',
+        '25': 'Block ACKs',
+        '26': 'PS-Polls',
+        '27': 'RTS',
+        '28': 'CTS',
+        '29': 'ACK',
+        '30': 'CF-Ends',
+        '31': 'CF-Ends/CF-Acks',
+        '32': 'Data',
+        '33': 'Data+CF-Ack',
+        '34': 'Data+CF-Poll',
+        '35': 'Data+CF-Ack+CF-Poll',
+        '36': 'Null',
+        '37': 'CF-Ack',
+        '38': 'CF-Poll',
+        '39': 'CF-Ack+CF-Poll',
+        '40': 'QoS Data',
+        '41': 'QoS Data+CF-Ack',
+        '42': 'QoS Data+CF-Poll',
+        '43': 'QoS Data+CF-Ack+CF-Poll',
+        '44': 'QoS Null',
+        '46': 'QoS CF-Poll (Null)',
+        '47': 'QoS CF-Ack+CF-Poll (Null)'
+    }
+
+    TSHARK_COLUMNS = [
+        'frame_number', 'frame_time_relative', 'mactime', 'frame_len', 'rssi',
+        'channel', 'ta', 'ra', 'bssid', 'type', 'subtype', 'duration', 'seq',
+        'retry', 'pwrmgmt', 'moredata', 'ds', 'phy', 'radio_datarate',
+        'vht_datarate', 'radiotap_mcs_index', 'vht_mcs', 'wlan_data_rate',
+        '11n_mcs_index', '11ac_mcs', '11n_bw', '11ac_bw', 'vht_nss', 'mcs_gi',
+        'vht_gi', 'vht_coding', 'ba_bm', 'fc_status', 'bf_report'
+    ]
+
+    TSHARK_OUTPUT_COLUMNS = [
+        'frame_number', 'frame_time_relative', 'mactime', 'ta', 'ra', 'bssid',
+        'rssi', 'channel', 'frame_len', 'Info', 'radio_datarate',
+        'radiotap_mcs_index', 'pwrmgmt', 'phy', 'vht_nss', 'vht_mcs',
+        'vht_datarate', '11ac_mcs', '11ac_bw', 'vht_gi', 'vht_coding',
+        'wlan_data_rate', '11n_mcs_index', '11n_bw', 'mcs_gi', 'type',
+        'subtype', 'duration', 'seq', 'retry', 'moredata', 'ds', 'ba_bm',
+        'fc_status', 'bf_report'
+    ]
+
+    TSHARK_FIELDS_LIST = [
+        'frame.number', 'frame.time_relative', 'radiotap.mactime', 'frame.len',
+        'radiotap.dbm_antsignal', 'wlan_radio.channel', 'wlan.ta', 'wlan.ra',
+        'wlan.bssid', 'wlan.fc.type', 'wlan.fc.type_subtype', 'wlan.duration',
+        'wlan.seq', 'wlan.fc.retry', 'wlan.fc.pwrmgt', 'wlan.fc.moredata',
+        'wlan.fc.ds', 'wlan_radio.phy', 'radiotap.datarate',
+        'radiotap.vht.datarate.0', 'radiotap.mcs.index', 'radiotap.vht.mcs.0',
+        'wlan_radio.data_rate', 'wlan_radio.11n.mcs_index',
+        'wlan_radio.11ac.mcs', 'wlan_radio.11n.bandwidth',
+        'wlan_radio.11ac.bandwidth', 'radiotap.vht.nss.0', 'radiotap.mcs.gi',
+        'radiotap.vht.gi', 'radiotap.vht.coding.0', 'wlan.ba.bm',
+        'wlan.fcs.status', 'wlan.vht.compressed_beamforming_report.snr'
+    ]
+
+    def __init__(self, config):
+        self.sniffer_proc_pid = None
+        self.log = logger.create_tagged_trace_logger('Tshark Sniffer')
+        self.ssh_config = config['ssh_config']
+        self.sniffer_os = config['os']
+        self.run_as_sudo = config.get('run_as_sudo', False)
+        self.sniffer_output_file_type = config['output_file_type']
+        self.sniffer_snap_length = config['snap_length']
+        self.sniffer_interface = config['interface']
+
+        #Logging into sniffer
+        self.log.info('Logging into sniffer.')
+        self._sniffer_server = ssh.connection.SshConnection(
+            ssh.settings.from_config(self.ssh_config))
+        # Get tshark params
+        self.tshark_fields = self._generate_tshark_fields(
+            self.TSHARK_FIELDS_LIST)
+        self.tshark_path = self._sniffer_server.run('which tshark').stdout
+
+    @property
+    def _started(self):
+        return self.sniffer_proc_pid is not None
+
+    def _scan_for_networks(self):
+        """Scans for wireless networks on the sniffer."""
+        raise NotImplementedError
+
+    def _get_tshark_command(self, duration):
+        """Frames the appropriate tshark command.
+
+        Args:
+            duration: duration to sniff for.
+
+        Returns:
+            tshark_command : appropriate tshark command.
+        """
+        tshark_command = '{} -l -i {} -I -t u -a duration:{}'.format(
+            self.tshark_path, self.sniffer_interface, int(duration))
+        if self.run_as_sudo:
+            tshark_command = 'sudo {}'.format(tshark_command)
+
+        return tshark_command
+
+    def _get_sniffer_command(self, tshark_command):
+        """
+        Frames the appropriate sniffer command.
+
+        Args:
+            tshark_command: framed tshark command
+
+        Returns:
+            sniffer_command: appropriate sniffer command
+        """
+        if self.sniffer_output_file_type in ['pcap', 'pcapng']:
+            sniffer_command = ' {tshark} -s {snaplength} -w {log_file} '.format(
+                tshark=tshark_command,
+                snaplength=self.sniffer_snap_length,
+                log_file=self._get_remote_dump_path())
+
+        elif self.sniffer_output_file_type == 'csv':
+            sniffer_command = '{tshark} {fields} > {log_file}'.format(
+                tshark=tshark_command,
+                fields=self.tshark_fields,
+                log_file=self._get_remote_dump_path())
+
+        else:
+            raise KeyError('Sniffer output file type not configured correctly')
+
+        return sniffer_command
+
+    def _generate_tshark_fields(self, fields):
+        """Generates tshark fields to be appended to the tshark command.
+
+        Args:
+            fields: list of tshark fields to be appended to the tshark command.
+
+        Returns:
+            tshark_fields: string of tshark fields to be appended
+            to the tshark command.
+        """
+        tshark_fields = "-T fields -y IEEE802_11_RADIO -E separator='^'"
+        for field in fields:
+            tshark_fields = tshark_fields + ' -e {}'.format(field)
+        return tshark_fields
+
+    def _configure_sniffer(self, network, chan, bw):
+        """ Connects to a wireless network using networksetup utility.
+
+        Args:
+            network: dictionary of network credentials; SSID and password.
+        """
+        raise NotImplementedError
+
+    def _run_tshark(self, sniffer_command):
+        """Starts the sniffer.
+
+        Args:
+            sniffer_command: sniffer command to execute.
+        """
+        self.log.info('Starting sniffer.')
+        sniffer_job = self._sniffer_server.run_async(sniffer_command)
+        self.sniffer_proc_pid = sniffer_job.stdout
+
+    def _stop_tshark(self):
+        """ Stops the sniffer."""
+        self.log.info('Stopping sniffer')
+
+        # while loop to kill the sniffer process
+        stop_time = time.time() + SNIFFER_TIMEOUT
+        while time.time() < stop_time:
+            # Wait before sending more kill signals
+            time.sleep(0.1)
+            try:
+                # Returns 1 if process was killed
+                self._sniffer_server.run(
+                    'ps aux| grep {} | grep -v grep'.format(
+                        self.sniffer_proc_pid))
+            except:
+                return
+            try:
+                # Returns error if process was killed already
+                self._sniffer_server.run('sudo kill -15 {}'.format(
+                    str(self.sniffer_proc_pid)))
+            except:
+                # Except is hit when tshark is already dead but we will break
+                # out of the loop when confirming process is dead using ps aux
+                pass
+        self.log.warning('Could not stop sniffer. Trying with SIGKILL.')
+        try:
+            self.log.debug('Killing sniffer with SIGKILL.')
+            self._sniffer_server.run('sudo kill -9 {}'.format(
+                    str(self.sniffer_proc_pid)))
+        except:
+            self.log.debug('Sniffer process may have stopped succesfully.')
+
+    def _process_tshark_dump(self, log_file):
+        """ Process tshark dump for better readability.
+
+        Processes tshark dump for better readability and saves it to a file.
+        Adds an info column at the end of each row. Format of the info columns:
+        subtype of the frame, sequence no and retry status.
+
+        Args:
+            log_file : unprocessed sniffer output
+        Returns:
+            log_file : processed sniffer output
+        """
+        temp_dump_file = os.path.join(self.log_path, 'sniffer_temp_dump.csv')
+        utils.exe_cmd('cp {} {}'.format(log_file, temp_dump_file))
+
+        with open(temp_dump_file, 'r') as input_csv, open(log_file,
+                                                          'w') as output_csv:
+            reader = csv.DictReader(input_csv,
+                                    fieldnames=self.TSHARK_COLUMNS,
+                                    delimiter='^')
+            writer = csv.DictWriter(output_csv,
+                                    fieldnames=self.TSHARK_OUTPUT_COLUMNS,
+                                    delimiter='\t')
+            writer.writeheader()
+            for row in reader:
+                if row['subtype'] in self.TYPE_SUBTYPE_DICT:
+                    row['Info'] = '{sub} S={seq} retry={retry_status}'.format(
+                        sub=self.TYPE_SUBTYPE_DICT[row['subtype']],
+                        seq=row['seq'],
+                        retry_status=row['retry'])
+                else:
+                    row['Info'] = '{} S={} retry={}\n'.format(
+                        row['subtype'], row['seq'], row['retry'])
+                writer.writerow(row)
+
+        utils.exe_cmd('rm -f {}'.format(temp_dump_file))
+        return log_file
+
+    def start_capture(self, network, chan, bw, duration=60):
+        """Starts sniffer capture on the specified machine.
+
+        Args:
+            network: dict describing network to sniff on.
+            duration: duration of sniff.
+        """
+        # Checking for existing sniffer processes
+        if self._started:
+            self.log.info('Sniffer already running')
+            return
+
+        # Configure sniffer
+        self._configure_sniffer(network, chan, bw)
+        tshark_command = self._get_tshark_command(duration)
+        sniffer_command = self._get_sniffer_command(tshark_command)
+
+        # Starting sniffer capture by executing tshark command
+        self._run_tshark(sniffer_command)
+
+    def stop_capture(self, tag=''):
+        """Stops the sniffer.
+
+        Args:
+            tag: tag to be appended to the sniffer output file.
+        Returns:
+            log_file: path to sniffer dump.
+        """
+        # Checking if there is an ongoing sniffer capture
+        if not self._started:
+            self.log.error('No sniffer process running')
+            return
+        # Killing sniffer process
+        self._stop_tshark()
+
+        # Processing writing capture output to file
+        log_file = self._get_full_file_path(tag)
+        self._sniffer_server.run('sudo chmod 777 {}'.format(
+            self._get_remote_dump_path()))
+        self._sniffer_server.pull_file(log_file, self._get_remote_dump_path())
+
+        if self.sniffer_output_file_type == 'csv':
+            log_file = self._process_tshark_dump(log_file)
+
+        self.sniffer_proc_pid = None
+        return log_file
+
+
+class TsharkSnifferOnUnix(TsharkSnifferBase):
+    """Class that implements Tshark based sniffer controller on Unix systems."""
+    def _scan_for_networks(self):
+        """Scans the wireless networks on the sniffer.
+
+        Returns:
+            scan_results : output of the scan command.
+        """
+        scan_command = '/usr/local/bin/airport -s'
+        scan_result = self._sniffer_server.run(scan_command).stdout
+
+        return scan_result
+
+    def _configure_sniffer(self, network, chan, bw):
+        """Connects to a wireless network using networksetup utility.
+
+        Args:
+            network: dictionary of network credentials; SSID and password.
+        """
+
+        self.log.debug('Connecting to network {}'.format(network['SSID']))
+
+        if 'password' not in network:
+            network['password'] = ''
+
+        connect_command = 'networksetup -setairportnetwork en0 {} {}'.format(
+            network['SSID'], network['password'])
+        self._sniffer_server.run(connect_command)
+
+
+class TsharkSnifferOnLinux(TsharkSnifferBase):
+    """Class that implements Tshark based sniffer controller on Linux."""
+    def __init__(self, config):
+        super().__init__(config)
+        self._init_sniffer()
+        self.channel = None
+        self.bandwidth = None
+
+    def _init_sniffer(self):
+        """Function to configure interface for the first time"""
+        self._sniffer_server.run('sudo modprobe -r iwlwifi')
+        self._sniffer_server.run('sudo dmesg -C')
+        self._sniffer_server.run('cat /dev/null | sudo tee /var/log/syslog')
+        self._sniffer_server.run('sudo modprobe iwlwifi debug=0x1')
+        # Wait for wifi config changes before trying to further configuration
+        # e.g. setting monitor mode (which will fail if above is not complete)
+        time.sleep(1)
+
+    def set_monitor_mode(self, chan, bw):
+        """Function to configure interface to monitor mode
+
+        Brings up the sniffer wireless interface in monitor mode and
+        tunes it to the appropriate channel and bandwidth
+
+        Args:
+            chan: primary channel (int) to tune the sniffer to
+            bw: bandwidth (int) to tune the sniffer to
+        """
+        if chan == self.channel and bw == self.bandwidth:
+            return
+
+        self.channel = chan
+        self.bandwidth = bw
+
+        channel_map = {
+            80: {
+                tuple(range(36, 50, 2)): 42,
+                tuple(range(52, 66, 2)): 58,
+                tuple(range(100, 114, 2)): 106,
+                tuple(range(116, 130, 2)): 122,
+                tuple(range(132, 146, 2)): 138,
+                tuple(range(149, 163, 2)): 155
+            },
+            40: {
+                (36, 38, 40): 38,
+                (44, 46, 48): 46,
+                (52, 54, 56): 54,
+                (60, 62, 64): 62,
+                (100, 102, 104): 102,
+                (108, 110, 112): 108,
+                (116, 118, 120): 118,
+                (124, 126, 128): 126,
+                (132, 134, 136): 134,
+                (140, 142, 144): 142,
+                (149, 151, 153): 151,
+                (157, 159, 161): 159
+            }
+        }
+
+        if chan <= 13:
+            primary_freq = WifiEnums.channel_2G_to_freq[chan]
+        else:
+            primary_freq = WifiEnums.channel_5G_to_freq[chan]
+
+        self._sniffer_server.run('sudo ifconfig {} down'.format(
+            self.sniffer_interface))
+        self._sniffer_server.run('sudo iwconfig {} mode monitor'.format(
+            self.sniffer_interface))
+        self._sniffer_server.run('sudo ifconfig {} up'.format(
+            self.sniffer_interface))
+
+        if bw in channel_map:
+            for tuple_chan in channel_map[bw]:
+                if chan in tuple_chan:
+                    center_freq = WifiEnums.channel_5G_to_freq[channel_map[bw]
+                                                               [tuple_chan]]
+                    self._sniffer_server.run(
+                        'sudo iw dev {} set freq {} {} {}'.format(
+                            self.sniffer_interface, primary_freq, bw,
+                            center_freq))
+
+        else:
+            self._sniffer_server.run('sudo iw dev {} set freq {}'.format(
+                self.sniffer_interface, primary_freq))
+
+    def _configure_sniffer(self, network, chan, bw):
+        """ Connects to a wireless network using networksetup utility.
+
+        Args:
+            network: dictionary of network credentials; SSID and password.
+        """
+
+        self.log.debug('Connecting to network {}'.format(network['SSID']))
+        self.set_monitor_mode(chan, bw)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/p2p/WifiP2pBaseTest.py b/acts_tests/acts_contrib/test_utils/wifi/p2p/WifiP2pBaseTest.py
new file mode 100644
index 0000000..f236470
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/WifiP2pBaseTest.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - 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 acts.utils
+import re
+import time
+
+from acts import asserts
+from acts import utils
+from acts.base_test import BaseTestClass
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+
+WAIT_TIME = 60
+
+
+class WifiP2pBaseTest(BaseTestClass):
+    def __init__(self, controllers):
+        if not hasattr(self, 'android_devices'):
+            super(WifiP2pBaseTest, self).__init__(controllers)
+
+    def setup_class(self):
+        for ad in self.android_devices:
+            ad.droid.wakeLockAcquireBright()
+            ad.droid.wakeUpNow()
+        required_params = ()
+        optional_params = ("skip_read_factory_mac", )
+        self.unpack_userparams(required_params,
+                               optional_params,
+                               skip_read_factory_mac=0)
+
+        self.dut1 = self.android_devices[0]
+        self.dut2 = self.android_devices[1]
+        if self.skip_read_factory_mac:
+            self.dut1_mac = None
+            self.dut2_mac = None
+        else:
+            self.dut1_mac = self.get_p2p_mac_address(self.dut1)
+            self.dut2_mac = self.get_p2p_mac_address(self.dut2)
+
+        #init location before init p2p
+        acts.utils.set_location_service(self.dut1, True)
+        acts.utils.set_location_service(self.dut2, True)
+
+        wutils.wifi_test_device_init(self.dut1)
+        utils.sync_device_time(self.dut1)
+        self.dut1.droid.wifiP2pInitialize()
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        asserts.assert_true(self.dut1.droid.wifiP2pIsEnabled(),
+                            "DUT1's p2p should be initialized but it didn't")
+        self.dut1.name = "Android_" + self.dut1.serial
+        self.dut1.droid.wifiP2pSetDeviceName(self.dut1.name)
+        wutils.wifi_test_device_init(self.dut2)
+        utils.sync_device_time(self.dut2)
+        self.dut2.droid.wifiP2pInitialize()
+        time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+        asserts.assert_true(self.dut2.droid.wifiP2pIsEnabled(),
+                            "DUT2's p2p should be initialized but it didn't")
+        self.dut2.name = "Android_" + self.dut2.serial
+        self.dut2.droid.wifiP2pSetDeviceName(self.dut2.name)
+
+        if len(self.android_devices) > 2:
+            self.dut3 = self.android_devices[2]
+            acts.utils.set_location_service(self.dut3, True)
+            wutils.wifi_test_device_init(self.dut3)
+            utils.sync_device_time(self.dut3)
+            self.dut3.droid.wifiP2pInitialize()
+            time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+            asserts.assert_true(
+                self.dut3.droid.wifiP2pIsEnabled(),
+                "DUT3's p2p should be initialized but it didn't")
+            self.dut3.name = "Android_" + self.dut3.serial
+            self.dut3.droid.wifiP2pSetDeviceName(self.dut3.name)
+
+    def teardown_class(self):
+        self.dut1.droid.wifiP2pClose()
+        self.dut2.droid.wifiP2pClose()
+        acts.utils.set_location_service(self.dut1, False)
+        acts.utils.set_location_service(self.dut2, False)
+
+        if len(self.android_devices) > 2:
+            self.dut3.droid.wifiP2pClose()
+            acts.utils.set_location_service(self.dut3, False)
+        for ad in self.android_devices:
+            ad.droid.wakeLockRelease()
+            ad.droid.goToSleepNow()
+
+    def setup_test(self):
+        for ad in self.android_devices:
+            ad.ed.clear_all_events()
+
+    def teardown_test(self):
+        for ad in self.android_devices:
+            # Clear p2p group info
+            ad.droid.wifiP2pRequestPersistentGroupInfo()
+            event = ad.ed.pop_event("WifiP2pOnPersistentGroupInfoAvailable",
+                                    p2pconsts.DEFAULT_TIMEOUT)
+            for network in event['data']:
+                ad.droid.wifiP2pDeletePersistentGroup(network['NetworkId'])
+            # Clear p2p local service
+            ad.droid.wifiP2pClearLocalServices()
+
+    def on_fail(self, test_name, begin_time):
+        for ad in self.android_devices:
+            ad.take_bug_report(test_name, begin_time)
+            ad.cat_adb_log(test_name, begin_time)
+
+    def get_p2p_mac_address(self, dut):
+        """Gets the current MAC address being used for Wi-Fi Direct."""
+        dut.reboot()
+        time.sleep(WAIT_TIME)
+        out = dut.adb.shell("ifconfig p2p0")
+        return re.match(".* HWaddr (\S+).*", out, re.S).group(1)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/p2p/__init__.py b/acts_tests/acts_contrib/test_utils/wifi/p2p/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_const.py b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_const.py
new file mode 100644
index 0000000..87f4059
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_const.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 - 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.
+
+######################################################
+# Wifi P2p framework designed value
+######################################################
+P2P_FIND_TIMEOUT = 120
+GO_IP_ADDRESS = '192.168.49.1'
+
+######################################################
+# Wifi P2p Acts flow control timer value
+######################################################
+
+DEFAULT_TIMEOUT = 30
+DEFAULT_SLEEPTIME = 5
+DEFAULT_FUNCTION_SWITCH_TIME = 10
+DEFAULT_SERVICE_WAITING_TIME = 20
+DEFAULT_GROUP_CLIENT_LOST_TIME = 60
+
+P2P_CONNECT_NEGOTIATION = 0
+P2P_CONNECT_JOIN = 1
+P2P_CONNECT_INVITATION = 2
+######################################################
+# Wifi P2p sl4a Event String
+######################################################
+CONNECTED_EVENT = "WifiP2pConnected"
+DISCONNECTED_EVENT = "WifiP2pDisconnected"
+PEER_AVAILABLE_EVENT = "WifiP2pOnPeersAvailable"
+CONNECTION_INFO_AVAILABLE_EVENT = "WifiP2pOnConnectionInfoAvailable"
+ONGOING_PEER_INFO_AVAILABLE_EVENT = "WifiP2pOnOngoingPeerAvailable"
+ONGOING_PEER_SET_SUCCESS_EVENT = "WifiP2psetP2pPeerConfigureOnSuccess"
+CONNECT_SUCCESS_EVENT = "WifiP2pConnectOnSuccess"
+CREATE_GROUP_SUCCESS_EVENT = "WifiP2pCreateGroupOnSuccess"
+SET_CHANNEL_SUCCESS_EVENT = "WifiP2pSetChannelsOnSuccess"
+GROUP_INFO_AVAILABLE_EVENT = "WifiP2pOnGroupInfoAvailable"
+
+######################################################
+# Wifi P2p local service event
+####################################################
+
+DNSSD_EVENT = "WifiP2pOnDnsSdServiceAvailable"
+DNSSD_TXRECORD_EVENT = "WifiP2pOnDnsSdTxtRecordAvailable"
+UPNP_EVENT = "WifiP2pOnUpnpServiceAvailable"
+
+DNSSD_EVENT_INSTANCENAME_KEY = "InstanceName"
+DNSSD_EVENT_REGISTRATIONTYPE_KEY = "RegistrationType"
+DNSSD_TXRECORD_EVENT_FULLDOMAINNAME_KEY = "FullDomainName"
+DNSSD_TXRECORD_EVENT_TXRECORDMAP_KEY = "TxtRecordMap"
+UPNP_EVENT_SERVICELIST_KEY = "ServiceList"
+
+######################################################
+# Wifi P2p local service type
+####################################################
+P2P_LOCAL_SERVICE_UPNP = 0
+P2P_LOCAL_SERVICE_IPP = 1
+P2P_LOCAL_SERVICE_AFP = 2
+
+######################################################
+# Wifi P2p group capability
+######################################################
+P2P_GROUP_CAPAB_GROUP_OWNER = 1
+
+
+######################################################
+# Wifi P2p UPnP MediaRenderer local service
+######################################################
+class UpnpTestData():
+    AVTransport = "urn:schemas-upnp-org:service:AVTransport:1"
+    ConnectionManager = "urn:schemas-upnp-org:service:ConnectionManager:1"
+    serviceType = "urn:schemas-upnp-org:device:MediaRenderer:1"
+    uuid = "6859dede-8574-59ab-9332-123456789011"
+    rootdevice = "upnp:rootdevice"
+
+
+######################################################
+# Wifi P2p Bonjour IPP & AFP local service
+######################################################
+class IppTestData():
+    ippInstanceName = "MyPrinter"
+    ippRegistrationType = "_ipp._tcp"
+    ippDomainName = "myprinter._ipp._tcp.local."
+    ipp_txtRecord = {"txtvers": "1", "pdl": "application/postscript"}
+
+
+class AfpTestData():
+    afpInstanceName = "Example"
+    afpRegistrationType = "_afpovertcp._tcp"
+    afpDomainName = "example._afpovertcp._tcp.local."
+    afp_txtRecord = {}
diff --git a/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py
new file mode 100755
index 0000000..006529a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py
@@ -0,0 +1,694 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 Google, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import os
+import time
+
+from queue import Empty
+
+from acts import asserts
+from acts import utils
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+import acts.utils
+
+
+def is_discovered(event, ad):
+    """Check an Android device exist in WifiP2pOnPeersAvailable event or not.
+
+    Args:
+        event: WifiP2pOnPeersAvailable which include all of p2p devices.
+        ad: The android device
+    Returns:
+        True: if an Android device exist in p2p list
+        False: if not exist
+    """
+    for device in event['data']['Peers']:
+        if device['Name'] == ad.name:
+            ad.deviceAddress = device['Address']
+            return True
+    return False
+
+
+def check_disconnect(ad, timeout=p2pconsts.DEFAULT_TIMEOUT):
+    """Check an Android device disconnect or not
+
+    Args:
+        ad: The android device
+    """
+    ad.droid.wifiP2pRequestConnectionInfo()
+    # wait disconnect event
+    ad.ed.pop_event(p2pconsts.DISCONNECTED_EVENT, timeout)
+
+
+def p2p_disconnect(ad):
+    """Invoke an Android device removeGroup to trigger p2p disconnect
+
+    Args:
+        ad: The android device
+    """
+    ad.log.debug("Disconnect")
+    ad.droid.wifiP2pRemoveGroup()
+    check_disconnect(ad)
+
+
+def p2p_connection_ping_test(ad, target_ip_address):
+    """Let an Android device to start ping target_ip_address
+
+    Args:
+        ad: The android device
+        target_ip_address: ip address which would like to ping
+    """
+    ad.log.debug("Run Ping Test, %s ping %s " % (ad.serial, target_ip_address))
+    asserts.assert_true(
+        acts.utils.adb_shell_ping(ad,
+                                  count=6,
+                                  dest_ip=target_ip_address,
+                                  timeout=20), "%s ping failed" % (ad.serial))
+
+
+def is_go(ad):
+    """Check an Android p2p role is Go or not
+
+    Args:
+        ad: The android device
+    Return:
+        True: An Android device is p2p  go
+        False: An Android device is p2p gc
+    """
+    ad.log.debug("is go check")
+    ad.droid.wifiP2pRequestConnectionInfo()
+    ad_connect_info_event = ad.ed.pop_event(
+        p2pconsts.CONNECTION_INFO_AVAILABLE_EVENT, p2pconsts.DEFAULT_TIMEOUT)
+    if ad_connect_info_event['data']['isGroupOwner']:
+        return True
+    return False
+
+
+def p2p_go_ip(ad):
+    """Get GO IP address
+
+    Args:
+        ad: The android device
+    Return:
+        GO IP address
+    """
+    ad.log.debug("p2p go ip")
+    ad.droid.wifiP2pRequestConnectionInfo()
+    ad_connect_info_event = ad.ed.pop_event(
+        p2pconsts.CONNECTION_INFO_AVAILABLE_EVENT, p2pconsts.DEFAULT_TIMEOUT)
+    ad.log.debug("p2p go ip: %s" %
+                 ad_connect_info_event['data']['groupOwnerHostAddress'])
+    return ad_connect_info_event['data']['groupOwnerHostAddress']
+
+
+def p2p_get_current_group(ad):
+    """Get current group information
+
+    Args:
+        ad: The android device
+    Return:
+        p2p group information
+    """
+    ad.log.debug("get current group")
+    ad.droid.wifiP2pRequestGroupInfo()
+    ad_group_info_event = ad.ed.pop_event(p2pconsts.GROUP_INFO_AVAILABLE_EVENT,
+                                          p2pconsts.DEFAULT_TIMEOUT)
+    ad.log.debug(
+        "p2p group: SSID:%s, password:%s, owner address: %s, interface: %s" %
+        (ad_group_info_event['data']['NetworkName'],
+         ad_group_info_event['data']['Passphrase'],
+         ad_group_info_event['data']['OwnerAddress'],
+         ad_group_info_event['data']['Interface']))
+    return ad_group_info_event['data']
+
+
+#trigger p2p connect to ad2 from ad1
+def p2p_connect(ad1,
+                ad2,
+                isReconnect,
+                wpsSetup,
+                p2p_connect_type=p2pconsts.P2P_CONNECT_NEGOTIATION,
+                go_ad=None):
+    """trigger p2p connect to ad2 from ad1
+
+    Args:
+        ad1: The android device
+        ad2: The android device
+        isReconnect: boolean, if persist group is exist,
+                isReconnect is true, otherswise is false.
+        wpsSetup: which wps connection would like to use
+        p2p_connect_type: enumeration, which type this p2p connection is
+        go_ad: The group owner android device which is used for the invitation connection
+    """
+    ad1.log.info("Create p2p connection from %s to %s via wps: %s type %d" %
+                 (ad1.name, ad2.name, wpsSetup, p2p_connect_type))
+    if p2p_connect_type == p2pconsts.P2P_CONNECT_INVITATION:
+        if go_ad is None:
+            go_ad = ad1
+        find_p2p_device(ad1, ad2)
+        find_p2p_group_owner(ad2, go_ad)
+    elif p2p_connect_type == p2pconsts.P2P_CONNECT_JOIN:
+        find_p2p_group_owner(ad1, ad2)
+    else:
+        find_p2p_device(ad1, ad2)
+    time.sleep(p2pconsts.DEFAULT_SLEEPTIME)
+    wifi_p2p_config = {
+        WifiP2PEnums.WifiP2pConfig.DEVICEADDRESS_KEY: ad2.deviceAddress,
+        WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY: {
+            WifiP2PEnums.WpsInfo.WPS_SETUP_KEY: wpsSetup
+        }
+    }
+    ad1.droid.wifiP2pConnect(wifi_p2p_config)
+    ad1.ed.pop_event(p2pconsts.CONNECT_SUCCESS_EVENT,
+                     p2pconsts.DEFAULT_TIMEOUT)
+    time.sleep(p2pconsts.DEFAULT_SLEEPTIME)
+    if not isReconnect:
+        ad1.droid.requestP2pPeerConfigure()
+        ad1_peerConfig = ad1.ed.pop_event(
+            p2pconsts.ONGOING_PEER_INFO_AVAILABLE_EVENT,
+            p2pconsts.DEFAULT_TIMEOUT)
+        ad1.log.debug(ad1_peerConfig['data'])
+        ad2.droid.requestP2pPeerConfigure()
+        ad2_peerConfig = ad2.ed.pop_event(
+            p2pconsts.ONGOING_PEER_INFO_AVAILABLE_EVENT,
+            p2pconsts.DEFAULT_TIMEOUT)
+        ad2.log.debug(ad2_peerConfig['data'])
+        if wpsSetup == WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY:
+            asserts.assert_true(
+                WifiP2PEnums.WpsInfo.WPS_PIN_KEY in ad1_peerConfig['data'][
+                    WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY],
+                "Can't get pin value")
+            ad2_peerConfig['data'][WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY][
+                WifiP2PEnums.WpsInfo.WPS_PIN_KEY] = ad1_peerConfig['data'][
+                    WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY][
+                        WifiP2PEnums.WpsInfo.WPS_PIN_KEY]
+            ad2.droid.setP2pPeerConfigure(ad2_peerConfig['data'])
+            ad2.ed.pop_event(p2pconsts.ONGOING_PEER_SET_SUCCESS_EVENT,
+                             p2pconsts.DEFAULT_TIMEOUT)
+            ad2.droid.wifiP2pAcceptConnection()
+        elif wpsSetup == WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_KEYPAD:
+            asserts.assert_true(
+                WifiP2PEnums.WpsInfo.WPS_PIN_KEY in ad2_peerConfig['data'][
+                    WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY],
+                "Can't get pin value")
+            ad1_peerConfig['data'][WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY][
+                WifiP2PEnums.WpsInfo.WPS_PIN_KEY] = ad2_peerConfig['data'][
+                    WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY][
+                        WifiP2PEnums.WpsInfo.WPS_PIN_KEY]
+            ad1.droid.setP2pPeerConfigure(ad1_peerConfig['data'])
+            ad1.ed.pop_event(p2pconsts.ONGOING_PEER_SET_SUCCESS_EVENT,
+                             p2pconsts.DEFAULT_TIMEOUT)
+            #Need to Accept first in ad1 to avoid connect time out in ad2,
+            #the timeout just 1 sec in ad2
+            ad1.droid.wifiP2pAcceptConnection()
+            time.sleep(p2pconsts.DEFAULT_SLEEPTIME)
+            ad2.droid.wifiP2pConfirmConnection()
+        elif wpsSetup == WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC:
+            ad2.droid.wifiP2pAcceptConnection()
+            if p2p_connect_type == p2pconsts.P2P_CONNECT_INVITATION:
+                time.sleep(p2pconsts.DEFAULT_SLEEPTIME)
+                go_ad.droid.wifiP2pAcceptConnection()
+
+    #wait connected event
+    if p2p_connect_type == p2pconsts.P2P_CONNECT_INVITATION:
+        go_ad.ed.pop_event(p2pconsts.CONNECTED_EVENT,
+                           p2pconsts.DEFAULT_TIMEOUT)
+    else:
+        ad1.ed.pop_event(p2pconsts.CONNECTED_EVENT, p2pconsts.DEFAULT_TIMEOUT)
+    ad2.ed.pop_event(p2pconsts.CONNECTED_EVENT, p2pconsts.DEFAULT_TIMEOUT)
+
+
+def p2p_connect_with_config(ad1, ad2, network_name, passphrase, band):
+    """trigger p2p connect to ad2 from ad1 with config
+
+    Args:
+        ad1: The android device
+        ad2: The android device
+        network_name: the network name of the desired group.
+        passphrase: the passphrase of the desired group.
+        band: the operating band of the desired group.
+    """
+    ad1.log.info("Create p2p connection from %s to %s" % (ad1.name, ad2.name))
+    find_p2p_device(ad1, ad2)
+    time.sleep(p2pconsts.DEFAULT_SLEEPTIME)
+    wifi_p2p_config = {
+        WifiP2PEnums.WifiP2pConfig.NETWORK_NAME: network_name,
+        WifiP2PEnums.WifiP2pConfig.PASSPHRASE: passphrase,
+        WifiP2PEnums.WifiP2pConfig.GROUP_BAND: band,
+        WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY: {
+            WifiP2PEnums.WpsInfo.WPS_SETUP_KEY:
+            WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC
+        }
+    }
+    ad1.droid.wifiP2pConnect(wifi_p2p_config)
+    ad1.ed.pop_event(p2pconsts.CONNECT_SUCCESS_EVENT,
+                     p2pconsts.DEFAULT_TIMEOUT)
+    time.sleep(p2pconsts.DEFAULT_SLEEPTIME)
+
+    #wait connected event
+    ad1.ed.pop_event(p2pconsts.CONNECTED_EVENT, p2pconsts.DEFAULT_TIMEOUT)
+    ad2.ed.pop_event(p2pconsts.CONNECTED_EVENT, p2pconsts.DEFAULT_TIMEOUT)
+
+
+def find_p2p_device(ad1, ad2):
+    """Check an Android device ad1 can discover an Android device ad2
+
+    Args:
+        ad1: The android device
+        ad2: The android device
+    """
+    ad1.droid.wifiP2pDiscoverPeers()
+    ad2.droid.wifiP2pDiscoverPeers()
+    p2p_find_result = False
+    while not p2p_find_result:
+        ad1_event = ad1.ed.pop_event(p2pconsts.PEER_AVAILABLE_EVENT,
+                                     p2pconsts.P2P_FIND_TIMEOUT)
+        ad1.log.debug(ad1_event['data'])
+        p2p_find_result = is_discovered(ad1_event, ad2)
+    asserts.assert_true(p2p_find_result,
+                        "DUT didn't discovered peer:%s device" % (ad2.name))
+
+
+def find_p2p_group_owner(ad1, ad2):
+    """Check an Android device ad1 can discover an Android device ad2 which
+       is a group owner
+
+    Args:
+        ad1: The android device
+        ad2: The android device which is a group owner
+    """
+    ad2.droid.wifiP2pStopPeerDiscovery()
+    time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+    ad1.droid.wifiP2pDiscoverPeers()
+    p2p_find_result = False
+    while not p2p_find_result:
+        ad1_event = ad1.ed.pop_event(p2pconsts.PEER_AVAILABLE_EVENT,
+                                     p2pconsts.P2P_FIND_TIMEOUT)
+        ad1.log.debug(ad1_event['data'])
+        for device in ad1_event['data']['Peers']:
+            if (device['Name'] == ad2.name and int(device['GroupCapability'])
+                    & p2pconsts.P2P_GROUP_CAPAB_GROUP_OWNER):
+                ad2.deviceAddress = device['Address']
+                p2p_find_result = True
+    asserts.assert_true(
+        p2p_find_result,
+        "DUT didn't discovered group owner peer:%s device" % (ad2.name))
+
+
+def createP2pLocalService(ad, serviceCategory):
+    """Based on serviceCategory to create p2p local service
+            on an Android device ad
+
+    Args:
+        ad: The android device
+        serviceCategory: p2p local service type, UPNP / IPP / AFP,
+    """
+    testData = genTestData(serviceCategory)
+    if serviceCategory == p2pconsts.P2P_LOCAL_SERVICE_UPNP:
+        ad.droid.wifiP2pCreateUpnpServiceInfo(testData[0], testData[1],
+                                              testData[2])
+    elif (serviceCategory == p2pconsts.P2P_LOCAL_SERVICE_IPP
+          or serviceCategory == p2pconsts.P2P_LOCAL_SERVICE_AFP):
+        ad.droid.wifiP2pCreateBonjourServiceInfo(testData[0], testData[1],
+                                                 testData[2])
+    ad.droid.wifiP2pAddLocalService()
+
+
+def requestServiceAndCheckResult(ad_serviceProvider, ad_serviceReceiver,
+                                 serviceType, queryString1, queryString2):
+    """Based on serviceType and query info, check service request result
+            same as expect or not on an Android device ad_serviceReceiver.
+            And remove p2p service request after result check.
+
+    Args:
+        ad_serviceProvider: The android device which provide p2p local service
+        ad_serviceReceiver: The android device which query p2p local service
+        serviceType: P2p local service type, Upnp or Bonjour
+        queryString1: Query String, NonNull
+        queryString2: Query String, used for Bonjour, Nullable
+    """
+    expectData = genExpectTestData(serviceType, queryString1, queryString2)
+    find_p2p_device(ad_serviceReceiver, ad_serviceProvider)
+    ad_serviceReceiver.droid.wifiP2pStopPeerDiscovery()
+    ad_serviceReceiver.droid.wifiP2pClearServiceRequests()
+    time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+    ad_serviceReceiver.droid.wifiP2pDiscoverServices()
+    serviceData = {}
+    service_id = 0
+    if (serviceType ==
+            WifiP2PEnums.WifiP2pServiceInfo.WIFI_P2P_SERVICE_TYPE_BONJOUR):
+        ad_serviceReceiver.log.info(
+            "Request bonjour service in \
+                %s with Query String %s and %s " %
+            (ad_serviceReceiver.name, queryString1, queryString2))
+        ad_serviceReceiver.log.info("expectData %s" % expectData)
+        if queryString1 != None:
+            service_id = ad_serviceReceiver.droid.wifiP2pAddDnssdServiceRequest(
+                queryString1, queryString2)
+        else:
+            service_id = ad_serviceReceiver.droid.wifiP2pAddServiceRequest(
+                serviceType)
+            ad_serviceReceiver.log.info("request bonjour service id %s" %
+                                        service_id)
+        ad_serviceReceiver.droid.wifiP2pSetDnsSdResponseListeners()
+        ad_serviceReceiver.droid.wifiP2pDiscoverServices()
+        ad_serviceReceiver.log.info("Check Service Listener")
+        time.sleep(p2pconsts.DEFAULT_SERVICE_WAITING_TIME)
+        try:
+            dnssd_events = ad_serviceReceiver.ed.pop_all(p2pconsts.DNSSD_EVENT)
+            dnssd_txrecord_events = ad_serviceReceiver.ed.pop_all(
+                p2pconsts.DNSSD_TXRECORD_EVENT)
+            dns_service = WifiP2PEnums.WifiP2pDnsSdServiceResponse()
+            for dnssd_event in dnssd_events:
+                if dnssd_event['data'][
+                        'SourceDeviceAddress'] == ad_serviceProvider.deviceAddress:
+                    dns_service.InstanceName = dnssd_event['data'][
+                        p2pconsts.DNSSD_EVENT_INSTANCENAME_KEY]
+                    dns_service.RegistrationType = dnssd_event['data'][
+                        p2pconsts.DNSSD_EVENT_REGISTRATIONTYPE_KEY]
+                    dns_service.FullDomainName = ""
+                    dns_service.TxtRecordMap = ""
+                    serviceData[dns_service.toString()] = 1
+            for dnssd_txrecord_event in dnssd_txrecord_events:
+                if dnssd_txrecord_event['data'][
+                        'SourceDeviceAddress'] == ad_serviceProvider.deviceAddress:
+                    dns_service.InstanceName = ""
+                    dns_service.RegistrationType = ""
+                    dns_service.FullDomainName = dnssd_txrecord_event['data'][
+                        p2pconsts.DNSSD_TXRECORD_EVENT_FULLDOMAINNAME_KEY]
+                    dns_service.TxtRecordMap = dnssd_txrecord_event['data'][
+                        p2pconsts.DNSSD_TXRECORD_EVENT_TXRECORDMAP_KEY]
+                    serviceData[dns_service.toString()] = 1
+            ad_serviceReceiver.log.info("serviceData %s" % serviceData)
+            if len(serviceData) == 0:
+                ad_serviceReceiver.droid.wifiP2pRemoveServiceRequest(
+                    service_id)
+                return -1
+        except queue.Empty as error:
+            ad_serviceReceiver.log.info("dnssd event is empty", )
+    elif (serviceType ==
+          WifiP2PEnums.WifiP2pServiceInfo.WIFI_P2P_SERVICE_TYPE_UPNP):
+        ad_serviceReceiver.log.info(
+            "Request upnp service in %s with Query String %s " %
+            (ad_serviceReceiver.name, queryString1))
+        ad_serviceReceiver.log.info("expectData %s" % expectData)
+        if queryString1 != None:
+            service_id = ad_serviceReceiver.droid.wifiP2pAddUpnpServiceRequest(
+                queryString1)
+        else:
+            service_id = ad_serviceReceiver.droid.wifiP2pAddServiceRequest(
+                WifiP2PEnums.WifiP2pServiceInfo.WIFI_P2P_SERVICE_TYPE_UPNP)
+        ad_serviceReceiver.droid.wifiP2pSetUpnpResponseListeners()
+        ad_serviceReceiver.droid.wifiP2pDiscoverServices()
+        ad_serviceReceiver.log.info("Check Service Listener")
+        time.sleep(p2pconsts.DEFAULT_SERVICE_WAITING_TIME)
+        try:
+            upnp_events = ad_serviceReceiver.ed.pop_all(p2pconsts.UPNP_EVENT)
+            for upnp_event in upnp_events:
+                if upnp_event['data']['Device'][
+                        'Address'] == ad_serviceProvider.deviceAddress:
+                    for service in upnp_event['data'][
+                            p2pconsts.UPNP_EVENT_SERVICELIST_KEY]:
+                        serviceData[service] = 1
+            ad_serviceReceiver.log.info("serviceData %s" % serviceData)
+            if len(serviceData) == 0:
+                ad_serviceReceiver.droid.wifiP2pRemoveServiceRequest(
+                    service_id)
+                return -1
+        except queue.Empty as error:
+            ad_serviceReceiver.log.info("p2p upnp event is empty", )
+
+    ad_serviceReceiver.log.info("Check ServiceList")
+    asserts.assert_true(checkServiceQueryResult(serviceData, expectData),
+                        "ServiceList not same as Expect")
+    # After service checked, remove the service_id
+    ad_serviceReceiver.droid.wifiP2pRemoveServiceRequest(service_id)
+    return 0
+
+
+def requestServiceAndCheckResultWithRetry(ad_serviceProvider,
+                                          ad_serviceReceiver,
+                                          serviceType,
+                                          queryString1,
+                                          queryString2,
+                                          retryCount=3):
+    """ allow failures for requestServiceAndCheckResult. Service
+        discovery might fail unexpectedly because the request packet might not be
+        recevied by the service responder due to p2p state switch.
+
+    Args:
+        ad_serviceProvider: The android device which provide p2p local service
+        ad_serviceReceiver: The android device which query p2p local service
+        serviceType: P2p local service type, Upnp or Bonjour
+        queryString1: Query String, NonNull
+        queryString2: Query String, used for Bonjour, Nullable
+        retryCount: maximum retry count, default is 3
+    """
+    ret = 0
+    while retryCount > 0:
+        ret = requestServiceAndCheckResult(ad_serviceProvider,
+                                           ad_serviceReceiver, serviceType,
+                                           queryString1, queryString2)
+        if (ret == 0):
+            break
+        retryCount -= 1
+
+    asserts.assert_equal(0, ret, "cannot find any services with retries.")
+
+
+def checkServiceQueryResult(serviceList, expectServiceList):
+    """Check serviceList same as expectServiceList or not
+
+    Args:
+        serviceList: ServiceList which get from query result
+        expectServiceList: ServiceList which hardcode in genExpectTestData
+    Return:
+        True: serviceList  same as expectServiceList
+        False:Exist discrepancy between serviceList and expectServiceList
+    """
+    tempServiceList = serviceList.copy()
+    tempExpectServiceList = expectServiceList.copy()
+    for service in serviceList.keys():
+        if service in expectServiceList:
+            del tempServiceList[service]
+            del tempExpectServiceList[service]
+    return len(tempExpectServiceList) == 0 and len(tempServiceList) == 0
+
+
+def genTestData(serviceCategory):
+    """Based on serviceCategory to generator Test Data
+
+    Args:
+        serviceCategory: P2p local service type, Upnp or Bonjour
+    Return:
+        TestData
+    """
+    testData = []
+    if serviceCategory == p2pconsts.P2P_LOCAL_SERVICE_UPNP:
+        testData.append(p2pconsts.UpnpTestData.uuid)
+        testData.append(p2pconsts.UpnpTestData.serviceType)
+        testData.append([
+            p2pconsts.UpnpTestData.AVTransport,
+            p2pconsts.UpnpTestData.ConnectionManager
+        ])
+    elif serviceCategory == p2pconsts.P2P_LOCAL_SERVICE_IPP:
+        testData.append(p2pconsts.IppTestData.ippInstanceName)
+        testData.append(p2pconsts.IppTestData.ippRegistrationType)
+        testData.append(p2pconsts.IppTestData.ipp_txtRecord)
+    elif serviceCategory == p2pconsts.P2P_LOCAL_SERVICE_AFP:
+        testData.append(p2pconsts.AfpTestData.afpInstanceName)
+        testData.append(p2pconsts.AfpTestData.afpRegistrationType)
+        testData.append(p2pconsts.AfpTestData.afp_txtRecord)
+
+    return testData
+
+
+def genExpectTestData(serviceType, queryString1, queryString2):
+    """Based on serviceCategory to generator expect serviceList
+
+    Args:
+        serviceType: P2p local service type, Upnp or Bonjour
+        queryString1: Query String, NonNull
+        queryString2: Query String, used for Bonjour, Nullable
+    Return:
+        expectServiceList
+    """
+    expectServiceList = {}
+    if (serviceType ==
+            WifiP2PEnums.WifiP2pServiceInfo.WIFI_P2P_SERVICE_TYPE_BONJOUR):
+        ipp_service = WifiP2PEnums.WifiP2pDnsSdServiceResponse()
+        afp_service = WifiP2PEnums.WifiP2pDnsSdServiceResponse()
+        if queryString1 == p2pconsts.IppTestData.ippRegistrationType:
+            if queryString2 == p2pconsts.IppTestData.ippInstanceName:
+                ipp_service.InstanceName = ""
+                ipp_service.RegistrationType = ""
+                ipp_service.FullDomainName = p2pconsts.IppTestData.ippDomainName
+                ipp_service.TxtRecordMap = p2pconsts.IppTestData.ipp_txtRecord
+                expectServiceList[ipp_service.toString()] = 1
+                return expectServiceList
+            ipp_service.InstanceName = p2pconsts.IppTestData.ippInstanceName
+            ipp_service.RegistrationType = (
+                p2pconsts.IppTestData.ippRegistrationType + ".local.")
+            ipp_service.FullDomainName = ""
+            ipp_service.TxtRecordMap = ""
+            expectServiceList[ipp_service.toString()] = 1
+            return expectServiceList
+        elif queryString1 == p2pconsts.AfpTestData.afpRegistrationType:
+            if queryString2 == p2pconsts.AfpTestData.afpInstanceName:
+                afp_service.InstanceName = ""
+                afp_service.RegistrationType = ""
+                afp_service.FullDomainName = p2pconsts.AfpTestData.afpDomainName
+                afp_service.TxtRecordMap = p2pconsts.AfpTestData.afp_txtRecord
+                expectServiceList[afp_service.toString()] = 1
+                return expectServiceList
+        ipp_service.InstanceName = p2pconsts.IppTestData.ippInstanceName
+        ipp_service.RegistrationType = (
+            p2pconsts.IppTestData.ippRegistrationType + ".local.")
+        ipp_service.FullDomainName = ""
+        ipp_service.TxtRecordMap = ""
+        expectServiceList[ipp_service.toString()] = 1
+
+        ipp_service.InstanceName = ""
+        ipp_service.RegistrationType = ""
+        ipp_service.FullDomainName = p2pconsts.IppTestData.ippDomainName
+        ipp_service.TxtRecordMap = p2pconsts.IppTestData.ipp_txtRecord
+        expectServiceList[ipp_service.toString()] = 1
+
+        afp_service.InstanceName = p2pconsts.AfpTestData.afpInstanceName
+        afp_service.RegistrationType = (
+            p2pconsts.AfpTestData.afpRegistrationType + ".local.")
+        afp_service.FullDomainName = ""
+        afp_service.TxtRecordMap = ""
+        expectServiceList[afp_service.toString()] = 1
+
+        afp_service.InstanceName = ""
+        afp_service.RegistrationType = ""
+        afp_service.FullDomainName = p2pconsts.AfpTestData.afpDomainName
+        afp_service.TxtRecordMap = p2pconsts.AfpTestData.afp_txtRecord
+        expectServiceList[afp_service.toString()] = 1
+
+        return expectServiceList
+    elif serviceType == WifiP2PEnums.WifiP2pServiceInfo.WIFI_P2P_SERVICE_TYPE_UPNP:
+        upnp_service = "uuid:" + p2pconsts.UpnpTestData.uuid + "::" + (
+            p2pconsts.UpnpTestData.rootdevice)
+        expectServiceList[upnp_service] = 1
+        if queryString1 != "upnp:rootdevice":
+            upnp_service = "uuid:" + p2pconsts.UpnpTestData.uuid + (
+                "::" + p2pconsts.UpnpTestData.AVTransport)
+            expectServiceList[upnp_service] = 1
+            upnp_service = "uuid:" + p2pconsts.UpnpTestData.uuid + (
+                "::" + p2pconsts.UpnpTestData.ConnectionManager)
+            expectServiceList[upnp_service] = 1
+            upnp_service = "uuid:" + p2pconsts.UpnpTestData.uuid + (
+                "::" + p2pconsts.UpnpTestData.serviceType)
+            expectServiceList[upnp_service] = 1
+            upnp_service = "uuid:" + p2pconsts.UpnpTestData.uuid
+            expectServiceList[upnp_service] = 1
+
+    return expectServiceList
+
+
+def p2p_create_group(ad):
+    """Create a group as Group Owner
+
+    Args:
+        ad: The android device
+    """
+    ad.droid.wifiP2pCreateGroup()
+    ad.ed.pop_event(p2pconsts.CREATE_GROUP_SUCCESS_EVENT,
+                    p2pconsts.DEFAULT_TIMEOUT)
+    time.sleep(p2pconsts.DEFAULT_SLEEPTIME)
+
+
+def p2p_create_group_with_config(ad, network_name, passphrase, band):
+    """Create a group as Group Owner
+
+    Args:
+        ad: The android device
+    """
+    wifi_p2p_config = {
+        WifiP2PEnums.WifiP2pConfig.NETWORK_NAME: network_name,
+        WifiP2PEnums.WifiP2pConfig.PASSPHRASE: passphrase,
+        WifiP2PEnums.WifiP2pConfig.GROUP_BAND: band,
+        WifiP2PEnums.WifiP2pConfig.WPSINFO_KEY: {
+            WifiP2PEnums.WpsInfo.WPS_SETUP_KEY:
+            WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC
+        }
+    }
+    ad.droid.wifiP2pCreateGroupWithConfig(wifi_p2p_config)
+    ad.ed.pop_event(p2pconsts.CREATE_GROUP_SUCCESS_EVENT,
+                    p2pconsts.DEFAULT_TIMEOUT)
+    time.sleep(p2pconsts.DEFAULT_SLEEPTIME)
+
+
+def wifi_p2p_set_channels_for_current_group(ad, listening_chan,
+                                            operating_chan):
+    """Sets the listening channel and operating channel of the current group
+       created with initialize.
+
+    Args:
+        ad: The android device
+        listening_chan: Integer, the listening channel
+        operating_chan: Integer, the operating channel
+    """
+    ad.droid.wifiP2pSetChannelsForCurrentGroup(listening_chan, operating_chan)
+    ad.ed.pop_event(p2pconsts.SET_CHANNEL_SUCCESS_EVENT,
+                    p2pconsts.DEFAULT_TIMEOUT)
+
+
+class WifiP2PEnums():
+    class WifiP2pConfig():
+        DEVICEADDRESS_KEY = "deviceAddress"
+        WPSINFO_KEY = "wpsInfo"
+        GO_INTENT_KEY = "groupOwnerIntent"
+        NETID_KEY = "netId"
+        NETWORK_NAME = "networkName"
+        PASSPHRASE = "passphrase"
+        GROUP_BAND = "groupOwnerBand"
+
+    class WpsInfo():
+        WPS_SETUP_KEY = "setup"
+        BSSID_KEY = "BSSID"
+        WPS_PIN_KEY = "pin"
+        #TODO: remove it from wifi_test_utils.py
+        WIFI_WPS_INFO_PBC = 0
+        WIFI_WPS_INFO_DISPLAY = 1
+        WIFI_WPS_INFO_KEYPAD = 2
+        WIFI_WPS_INFO_LABEL = 3
+        WIFI_WPS_INFO_INVALID = 4
+
+    class WifiP2pServiceInfo():
+        #TODO: remove it from wifi_test_utils.py
+        # Macros for wifi p2p.
+        WIFI_P2P_SERVICE_TYPE_ALL = 0
+        WIFI_P2P_SERVICE_TYPE_BONJOUR = 1
+        WIFI_P2P_SERVICE_TYPE_UPNP = 2
+        WIFI_P2P_SERVICE_TYPE_VENDOR_SPECIFIC = 255
+
+    class WifiP2pDnsSdServiceResponse():
+        def __init__(self):
+            pass
+
+        InstanceName = ""
+        RegistrationType = ""
+        FullDomainName = ""
+        TxtRecordMap = {}
+
+        def toString(self):
+            return self.InstanceName + self.RegistrationType + (
+                self.FullDomainName + str(self.TxtRecordMap))
diff --git a/acts_tests/acts_contrib/test_utils/wifi/rpm_controller_utils.py b/acts_tests/acts_contrib/test_utils/wifi/rpm_controller_utils.py
new file mode 100644
index 0000000..6aa8e3e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/rpm_controller_utils.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 Google, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT 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.attenuator_lib._tnhelper import _ascii_string
+
+import logging
+import telnetlib
+
+ID = '.A'
+LOGIN_PWD = 'admn'
+ON = 'On'
+OFF = 'Off'
+PASSWORD = 'Password: '
+PORT = 23
+RPM_PROMPT = 'Switched CDU: '
+SEPARATOR = '\n'
+TIMEOUT = 3
+USERNAME = 'Username: '
+
+
+class RpmControllerError(Exception):
+    """Error related to RPM switch."""
+
+class RpmController(object):
+    """Class representing telnet to RPM switch.
+
+    Each object represents a telnet connection to the RPM switch's IP.
+
+    Attributes:
+        tn: represents a connection to RPM switch.
+        host: IP address of the RPM controller.
+    """
+    def __init__(self, host):
+        """Initializes the RPM controller object.
+
+        Establishes a telnet connection and login to the switch.
+        """
+        self.host = host
+        logging.info('RPM IP: %s' % self.host)
+
+        self.tn = telnetlib.Telnet(self.host)
+        self.tn.open(self.host, PORT, TIMEOUT)
+        self.run(USERNAME, LOGIN_PWD)
+        result = self.run(PASSWORD, LOGIN_PWD)
+        if RPM_PROMPT not in result:
+            raise RpmControllerError('Failed to login to rpm controller %s'
+                                     % self.host)
+
+    def run(self, prompt, cmd_str):
+        """Method to run commands on the RPM.
+
+        This method simply runs a command and returns output in decoded format.
+        The calling methods should take care of parsing the expected result
+        from this output.
+
+        Args:
+            prompt: Expected prompt before running a command.
+            cmd_str: Command to run on RPM.
+
+        Returns:
+            Decoded text returned by the command.
+        """
+        cmd_str = '%s%s' % (cmd_str, SEPARATOR)
+        res = self.tn.read_until(_ascii_string(prompt), TIMEOUT)
+
+        self.tn.write(_ascii_string(cmd_str))
+        idx, val, txt = self.tn.expect(
+            [_ascii_string('\S+%s' % SEPARATOR)], TIMEOUT)
+
+        return txt.decode()
+
+    def set_rpm_port_state(self, rpm_port, state):
+        """Method to turn on/off rpm port.
+
+        Args:
+            rpm_port: port number of the switch to turn on.
+            state: 'on' or 'off'
+
+        Returns:
+            True: if the state is set to the expected value
+        """
+        port = '%s%s' % (ID, rpm_port)
+        logging.info('Turning %s port: %s' % (state, port))
+        self.run(RPM_PROMPT, '%s %s' % (state.lower(), port))
+        result = self.run(RPM_PROMPT, 'status %s' % port)
+        if port not in result:
+            raise RpmControllerError('Port %s doesn\'t exist' % port)
+        return state in result
+
+    def turn_on(self, rpm_port):
+        """Method to turn on a port on the RPM switch.
+
+        Args:
+            rpm_port: port number of the switch to turn on.
+
+        Returns:
+            True if the port is turned on.
+            False if not turned on.
+        """
+        return self.set_rpm_port_state(rpm_port, ON)
+
+    def turn_off(self, rpm_port):
+        """Method to turn off a port on the RPM switch.
+
+        Args:
+            rpm_port: port number of the switch to turn off.
+
+        Returns:
+            True if the port is turned off.
+            False if not turned off.
+        """
+        return self.set_rpm_port_state(rpm_port, OFF)
+
+    def __del__(self):
+        """Close the telnet connection. """
+        self.tn.close()
+
+
+def create_telnet_session(ip):
+    """Returns telnet connection object to RPM's IP."""
+    return RpmController(ip)
+
+def turn_on_ap(pcap, ssid, rpm_port, rpm_ip=None, rpm=None):
+    """Turn on the AP.
+
+    This method turns on the RPM port the AP is connected to,
+    verify the SSID of the AP is found in the scan result through the
+    packet capturer.
+
+    Either IP addr of the RPM switch or the existing telnet connection
+    to the RPM is required. Multiple APs might be connected to the same RPM
+    switch. Instead of connecting/terminating telnet for each AP, the test
+    can maintain a single telnet connection for all the APs.
+
+    Args:
+        pcap: packet capture object.
+        ssid: SSID of the wifi network.
+        rpm_port: Port number on the RPM switch the AP is connected to.
+        rpm_ip: IP address of the RPM switch.
+        rpm: telnet connection object to the RPM switch.
+    """
+    if not rpm and not rpm_ip:
+        logging.error("Failed to turn on AP. Need telnet object or RPM IP")
+        return False
+    elif not rpm:
+        rpm = create_telnet_session(rpm_ip)
+
+    return rpm.turn_on(rpm_port) and pcap.start_scan_and_find_network(ssid)
+
+def turn_off_ap(rpm_port, rpm_ip=None, rpm=None):
+    """ Turn off AP.
+
+    This method turns off the RPM port the AP is connected to.
+
+    Either IP addr of the RPM switch or the existing telnet connection
+    to the RPM is required.
+
+    Args:
+        rpm_port: Port number on the RPM switch the AP is connected to.
+        rpm_ip: IP address of the RPM switch.
+        rpm: telnet connection object to the RPM switch.
+    """
+    if not rpm and not rpm_ip:
+        logging.error("Failed to turn off AP. Need telnet object or RPM IP")
+        return False
+    elif not rpm:
+        rpm = create_telnet_session(rpm_ip)
+
+    return rpm.turn_off(rpm_port)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/rtt/RttBaseTest.py b/acts_tests/acts_contrib/test_utils/wifi/rtt/RttBaseTest.py
new file mode 100644
index 0000000..f2cc393
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/rtt/RttBaseTest.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - 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 asserts
+from acts import utils
+from acts.base_test import BaseTestClass
+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
+
+
+class RttBaseTest(BaseTestClass):
+    def setup_test(self):
+        required_params = ("lci_reference", "lcr_reference",
+                           "rtt_reference_distance_mm",
+                           "stress_test_min_iteration_count",
+                           "stress_test_target_run_time_sec")
+        self.unpack_userparams(required_params)
+
+        # can be moved to JSON config file
+        self.rtt_reference_distance_margin_mm = 2000
+        self.rtt_max_failure_rate_two_sided_rtt_percentage = 20
+        self.rtt_max_failure_rate_one_sided_rtt_percentage = 50
+        self.rtt_max_margin_exceeded_rate_two_sided_rtt_percentage = 10
+        self.rtt_max_margin_exceeded_rate_one_sided_rtt_percentage = 50
+        self.rtt_min_expected_rssi_dbm = -100
+
+        for ad in self.android_devices:
+            utils.set_location_service(ad, True)
+            asserts.skip_if(
+                not ad.droid.doesDeviceSupportWifiRttFeature(),
+                "Device under test does not support Wi-Fi RTT - skipping test")
+            wutils.wifi_toggle_state(ad, True)
+            rtt_avail = ad.droid.wifiIsRttAvailable()
+            if not rtt_avail:
+                self.log.info('RTT not available. Waiting ...')
+                rutils.wait_for_event(ad, rconsts.BROADCAST_WIFI_RTT_AVAILABLE)
+            ad.ed.clear_all_events()
+            rutils.config_privilege_override(ad, False)
+            wutils.set_wifi_country_code(ad, wutils.WifiEnums.CountryCode.US)
+            ad.rtt_capabilities = rutils.get_rtt_capabilities(ad)
+
+    def teardown_test(self):
+        for ad in self.android_devices:
+            if not ad.droid.doesDeviceSupportWifiRttFeature():
+                return
+
+            # clean-up queue from the System Service UID
+            ad.droid.wifiRttCancelRanging([1000])
+
+    def on_fail(self, test_name, begin_time):
+        for ad in self.android_devices:
+            ad.take_bug_report(test_name, begin_time)
+            ad.cat_adb_log(test_name, begin_time)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/rtt/__init__.py b/acts_tests/acts_contrib/test_utils/wifi/rtt/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/rtt/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/wifi/rtt/rtt_const.py b/acts_tests/acts_contrib/test_utils/wifi/rtt/rtt_const.py
new file mode 100644
index 0000000..34e6701
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/rtt/rtt_const.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - 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.
+
+######################################################
+# Broadcast events
+######################################################
+BROADCAST_WIFI_RTT_AVAILABLE = "WifiRttAvailable"
+BROADCAST_WIFI_RTT_NOT_AVAILABLE = "WifiRttNotAvailable"
+
+######################################################
+# RangingResultCallback events
+######################################################
+EVENT_CB_RANGING_ON_FAIL = "WifiRttRangingFailure"
+EVENT_CB_RANGING_ON_RESULT = "WifiRttRangingResults"
+
+EVENT_CB_RANGING_KEY_RESULTS = "Results"
+
+EVENT_CB_RANGING_KEY_STATUS = "status"
+EVENT_CB_RANGING_KEY_DISTANCE_MM = "distanceMm"
+EVENT_CB_RANGING_KEY_DISTANCE_STD_DEV_MM = "distanceStdDevMm"
+EVENT_CB_RANGING_KEY_RSSI = "rssi"
+EVENT_CB_RANGING_KEY_NUM_ATTEMPTED_MEASUREMENTS = "numAttemptedMeasurements"
+EVENT_CB_RANGING_KEY_NUM_SUCCESSFUL_MEASUREMENTS = "numSuccessfulMeasurements"
+EVENT_CB_RANGING_KEY_LCI = "lci"
+EVENT_CB_RANGING_KEY_LCR = "lcr"
+EVENT_CB_RANGING_KEY_TIMESTAMP = "timestamp"
+EVENT_CB_RANGING_KEY_MAC = "mac"
+EVENT_CB_RANGING_KEY_PEER_ID = "peerId"
+EVENT_CB_RANGING_KEY_MAC_AS_STRING = "macAsString"
+
+EVENT_CB_RANGING_STATUS_SUCCESS = 0
+EVENT_CB_RANGING_STATUS_FAIL = 1
+EVENT_CB_RANGING_STATUS_RESPONDER_DOES_NOT_SUPPORT_IEEE80211MC = 2
+
+######################################################
+# status codes
+######################################################
+
+RANGING_FAIL_CODE_GENERIC = 1
+RANGING_FAIL_CODE_RTT_NOT_AVAILABLE = 2
+
+######################################################
+# ScanResults keys
+######################################################
+
+SCAN_RESULT_KEY_RTT_RESPONDER = "is80211McRTTResponder"
+
+######################################################
+# Capabilities keys
+######################################################
+
+CAP_RTT_ONE_SIDED_SUPPORTED = "rttOneSidedSupported"
+CAP_FTM_SUPPORTED = "rttFtmSupported"
+CAP_LCI_SUPPORTED = "lciSupported"
+CAP_LCR_SUPPORTED = "lcrSupported"
+CAP_RESPONDER_SUPPORTED = "responderSupported"
+CAP_MC_VERSION = "mcVersion"
diff --git a/acts_tests/acts_contrib/test_utils/wifi/rtt/rtt_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/rtt/rtt_test_utils.py
new file mode 100644
index 0000000..eac7378
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/rtt/rtt_test_utils.py
@@ -0,0 +1,501 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2017 - 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 queue
+import statistics
+import time
+
+from acts import asserts
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.rtt import rtt_const as rconsts
+
+# arbitrary timeout for events
+EVENT_TIMEOUT = 15
+
+
+def decorate_event(event_name, id):
+    return '%s_%d' % (event_name, id)
+
+
+def wait_for_event(ad, event_name, timeout=EVENT_TIMEOUT):
+    """Wait for the specified event or timeout.
+
+  Args:
+    ad: The android device
+    event_name: The event to wait on
+    timeout: Number of seconds to wait
+  Returns:
+    The event (if available)
+  """
+    prefix = ''
+    if hasattr(ad, 'pretty_name'):
+        prefix = '[%s] ' % ad.pretty_name
+    try:
+        event = ad.ed.pop_event(event_name, timeout)
+        ad.log.info('%s%s: %s', prefix, event_name, event['data'])
+        return event
+    except queue.Empty:
+        ad.log.info('%sTimed out while waiting for %s', prefix, event_name)
+        asserts.fail(event_name)
+
+
+def fail_on_event(ad, event_name, timeout=EVENT_TIMEOUT):
+    """Wait for a timeout period and looks for the specified event - fails if it
+  is observed.
+
+  Args:
+    ad: The android device
+    event_name: The event to wait for (and fail on its appearance)
+  """
+    prefix = ''
+    if hasattr(ad, 'pretty_name'):
+        prefix = '[%s] ' % ad.pretty_name
+    try:
+        event = ad.ed.pop_event(event_name, timeout)
+        ad.log.info('%sReceived unwanted %s: %s', prefix, event_name,
+                    event['data'])
+        asserts.fail(event_name, extras=event)
+    except queue.Empty:
+        ad.log.info('%s%s not seen (as expected)', prefix, event_name)
+        return
+
+
+def get_rtt_capabilities(ad):
+    """Get the Wi-Fi RTT capabilities from the specified device. The
+  capabilities are a dictionary keyed by rtt_const.CAP_* keys.
+
+  Args:
+    ad: the Android device
+  Returns: the capability dictionary.
+  """
+    return json.loads(ad.adb.shell('cmd wifirtt get_capabilities'))
+
+
+def config_privilege_override(dut, override_to_no_privilege):
+    """Configure the device to override the permission check and to disallow any
+  privileged RTT operations, e.g. disallow one-sided RTT to Responders (APs)
+  which do not support IEEE 802.11mc.
+
+  Args:
+    dut: Device to configure.
+    override_to_no_privilege: True to indicate no privileged ops, False for
+                              default (which will allow privileged ops).
+  """
+    dut.adb.shell("cmd wifirtt set override_assume_no_privilege %d" %
+                  (1 if override_to_no_privilege else 0))
+
+
+def get_rtt_constrained_results(scanned_networks, support_rtt):
+    """Filter the input list and only return those networks which either support
+  or do not support RTT (IEEE 802.11mc.)
+
+  Args:
+    scanned_networks: A list of networks from scan results.
+      support_rtt: True - only return those APs which support RTT, False - only
+                   return those APs which do not support RTT.
+
+  Returns: a sub-set of the scanned_networks per support_rtt constraint.
+  """
+    matching_networks = []
+    for network in scanned_networks:
+        if support_rtt:
+            if (rconsts.SCAN_RESULT_KEY_RTT_RESPONDER in network
+                    and network[rconsts.SCAN_RESULT_KEY_RTT_RESPONDER]):
+                matching_networks.append(network)
+        else:
+            if (rconsts.SCAN_RESULT_KEY_RTT_RESPONDER not in network
+                    or not network[rconsts.SCAN_RESULT_KEY_RTT_RESPONDER]):
+                matching_networks.append(network)
+
+    return matching_networks
+
+
+def scan_networks(dut, max_tries=3):
+    """Perform a scan and return scan results.
+
+  Args:
+    dut: Device under test.
+    max_retries: Retry scan to ensure network is found
+
+  Returns: an array of scan results.
+  """
+    scan_results = []
+    for num_tries in range(max_tries):
+        wutils.start_wifi_connection_scan(dut)
+        scan_results = dut.droid.wifiGetScanResults()
+        if scan_results:
+            break
+    return scan_results
+
+
+def scan_with_rtt_support_constraint(dut, support_rtt, repeat=0):
+    """Perform a scan and return scan results of APs: only those that support or
+  do not support RTT (IEEE 802.11mc) - per the support_rtt parameter.
+
+  Args:
+    dut: Device under test.
+    support_rtt: True - only return those APs which support RTT, False - only
+                 return those APs which do not support RTT.
+    repeat: Re-scan this many times to find an RTT supporting network.
+
+  Returns: an array of scan results.
+  """
+    for i in range(repeat + 1):
+        scan_results = scan_networks(dut)
+        aps = get_rtt_constrained_results(scan_results, support_rtt)
+        if len(aps) != 0:
+            return aps
+
+    return []
+
+
+def select_best_scan_results(scans, select_count, lowest_rssi=-80):
+    """Select the strongest 'select_count' scans in the input list based on
+  highest RSSI. Exclude all very weak signals, even if results in a shorter
+  list.
+
+  Args:
+    scans: List of scan results.
+    select_count: An integer specifying how many scans to return at most.
+    lowest_rssi: The lowest RSSI to accept into the output.
+  Returns: a list of the strongest 'select_count' scan results from the scans
+           list.
+  """
+
+    def takeRssi(element):
+        return element['level']
+
+    result = []
+    scans.sort(key=takeRssi, reverse=True)
+    for scan in scans:
+        if len(result) == select_count:
+            break
+        if scan['level'] < lowest_rssi:
+            break  # rest are lower since we're sorted
+        result.append(scan)
+
+    return result
+
+
+def validate_ap_result(scan_result, range_result):
+    """Validate the range results:
+  - Successful if AP (per scan result) support 802.11mc (allowed to fail
+    otherwise)
+  - MAC of result matches the BSSID
+
+  Args:
+    scan_result: Scan result for the AP
+    range_result: Range result returned by the RTT API
+  """
+    asserts.assert_equal(
+        scan_result[wutils.WifiEnums.BSSID_KEY],
+        range_result[rconsts.EVENT_CB_RANGING_KEY_MAC_AS_STRING_BSSID],
+        'MAC/BSSID mismatch')
+    if (rconsts.SCAN_RESULT_KEY_RTT_RESPONDER in scan_result
+            and scan_result[rconsts.SCAN_RESULT_KEY_RTT_RESPONDER]):
+        asserts.assert_true(
+            range_result[rconsts.EVENT_CB_RANGING_KEY_STATUS] ==
+            rconsts.EVENT_CB_RANGING_STATUS_SUCCESS,
+            'Ranging failed for an AP which supports 802.11mc!')
+
+
+def validate_ap_results(scan_results, range_results):
+    """Validate an array of ranging results against the scan results used to
+  trigger the range. The assumption is that the results are returned in the
+  same order as the request (which were the scan results).
+
+  Args:
+    scan_results: Scans results used to trigger the range request
+    range_results: Range results returned by the RTT API
+  """
+    asserts.assert_equal(
+        len(scan_results), len(range_results),
+        'Mismatch in length of scan results and range results')
+
+    # sort first based on BSSID/MAC
+    scan_results.sort(key=lambda x: x[wutils.WifiEnums.BSSID_KEY])
+    range_results.sort(
+        key=lambda x: x[rconsts.EVENT_CB_RANGING_KEY_MAC_AS_STRING_BSSID])
+
+    for i in range(len(scan_results)):
+        validate_ap_result(scan_results[i], range_results[i])
+
+
+def validate_aware_mac_result(range_result, mac, description):
+    """Validate the range result for an Aware peer specified with a MAC address:
+  - Correct MAC address.
+
+  The MAC addresses may contain ":" (which are ignored for the comparison) and
+  may be in any case (which is ignored for the comparison).
+
+  Args:
+    range_result: Range result returned by the RTT API
+    mac: MAC address of the peer
+    description: Additional content to print on failure
+  """
+    mac1 = mac.replace(':', '').lower()
+    mac2 = range_result[rconsts.EVENT_CB_RANGING_KEY_MAC_AS_STRING].replace(
+        ':', '').lower()
+    asserts.assert_equal(mac1, mac2, '%s: MAC mismatch' % description)
+
+
+def validate_aware_peer_id_result(range_result, peer_id, description):
+    """Validate the range result for An Aware peer specified with a Peer ID:
+  - Correct Peer ID
+  - MAC address information not available
+
+  Args:
+    range_result: Range result returned by the RTT API
+    peer_id: Peer ID of the peer
+    description: Additional content to print on failure
+  """
+    asserts.assert_equal(peer_id,
+                         range_result[rconsts.EVENT_CB_RANGING_KEY_PEER_ID],
+                         '%s: Peer Id mismatch' % description)
+    asserts.assert_false(rconsts.EVENT_CB_RANGING_KEY_MAC in range_result,
+                         '%s: MAC Address not empty!' % description)
+
+
+def extract_stats(results,
+                  range_reference_mm,
+                  range_margin_mm,
+                  min_rssi,
+                  reference_lci=[],
+                  reference_lcr=[],
+                  summary_only=False):
+    """Extract statistics from a list of RTT results. Returns a dictionary
+   with results:
+     - num_results (success or fails)
+     - num_success_results
+     - num_no_results (e.g. timeout)
+     - num_failures
+     - num_range_out_of_margin (only for successes)
+     - num_invalid_rssi (only for successes)
+     - distances: extracted list of distances
+     - distance_std_devs: extracted list of distance standard-deviations
+     - rssis: extracted list of RSSI
+     - distance_mean
+     - distance_std_dev (based on distance - ignoring the individual std-devs)
+     - rssi_mean
+     - rssi_std_dev
+     - status_codes
+     - lcis: extracted list of all of the individual LCI
+     - lcrs: extracted list of all of the individual LCR
+     - any_lci_mismatch: True/False - checks if all LCI results are identical to
+                         the reference LCI.
+     - any_lcr_mismatch: True/False - checks if all LCR results are identical to
+                         the reference LCR.
+     - num_attempted_measurements: extracted list of all of the individual
+                                   number of attempted measurements.
+     - num_successful_measurements: extracted list of all of the individual
+                                    number of successful measurements.
+     - invalid_num_attempted: True/False - checks if number of attempted
+                              measurements is non-zero for successful results.
+     - invalid_num_successful: True/False - checks if number of successful
+                               measurements is non-zero for successful results.
+
+  Args:
+    results: List of RTT results.
+    range_reference_mm: Reference value for the distance (in mm)
+    range_margin_mm: Acceptable absolute margin for distance (in mm)
+    min_rssi: Acceptable minimum RSSI value.
+    reference_lci, reference_lcr: Reference values for LCI and LCR.
+    summary_only: Only include summary keys (reduce size).
+
+  Returns: A dictionary of stats.
+  """
+    stats = {}
+    stats['num_results'] = 0
+    stats['num_success_results'] = 0
+    stats['num_no_results'] = 0
+    stats['num_failures'] = 0
+    stats['num_range_out_of_margin'] = 0
+    stats['num_invalid_rssi'] = 0
+    stats['any_lci_mismatch'] = False
+    stats['any_lcr_mismatch'] = False
+    stats['invalid_num_attempted'] = False
+    stats['invalid_num_successful'] = False
+
+    range_max_mm = range_reference_mm + range_margin_mm
+    range_min_mm = range_reference_mm - range_margin_mm
+
+    distances = []
+    distance_std_devs = []
+    rssis = []
+    num_attempted_measurements = []
+    num_successful_measurements = []
+    status_codes = []
+    lcis = []
+    lcrs = []
+
+    for i in range(len(results)):
+        result = results[i]
+
+        if result is None:  # None -> timeout waiting for RTT result
+            stats['num_no_results'] = stats['num_no_results'] + 1
+            continue
+        stats['num_results'] = stats['num_results'] + 1
+
+        status_codes.append(result[rconsts.EVENT_CB_RANGING_KEY_STATUS])
+        if status_codes[-1] != rconsts.EVENT_CB_RANGING_STATUS_SUCCESS:
+            stats['num_failures'] = stats['num_failures'] + 1
+            continue
+        stats['num_success_results'] = stats['num_success_results'] + 1
+
+        distance_mm = result[rconsts.EVENT_CB_RANGING_KEY_DISTANCE_MM]
+        distances.append(distance_mm)
+        if not range_min_mm <= distance_mm <= range_max_mm:
+            stats[
+                'num_range_out_of_margin'] = stats['num_range_out_of_margin'] + 1
+        distance_std_devs.append(
+            result[rconsts.EVENT_CB_RANGING_KEY_DISTANCE_STD_DEV_MM])
+
+        rssi = result[rconsts.EVENT_CB_RANGING_KEY_RSSI]
+        rssis.append(rssi)
+        if not min_rssi <= rssi <= 0:
+            stats['num_invalid_rssi'] = stats['num_invalid_rssi'] + 1
+
+        num_attempted = result[
+            rconsts.EVENT_CB_RANGING_KEY_NUM_ATTEMPTED_MEASUREMENTS]
+        num_attempted_measurements.append(num_attempted)
+        if num_attempted == 0:
+            stats['invalid_num_attempted'] = True
+
+        num_successful = result[
+            rconsts.EVENT_CB_RANGING_KEY_NUM_SUCCESSFUL_MEASUREMENTS]
+        num_successful_measurements.append(num_successful)
+        if num_successful == 0:
+            stats['invalid_num_successful'] = True
+
+        lcis.append(result[rconsts.EVENT_CB_RANGING_KEY_LCI])
+        if (result[rconsts.EVENT_CB_RANGING_KEY_LCI] != reference_lci):
+            stats['any_lci_mismatch'] = True
+        lcrs.append(result[rconsts.EVENT_CB_RANGING_KEY_LCR])
+        if (result[rconsts.EVENT_CB_RANGING_KEY_LCR] != reference_lcr):
+            stats['any_lcr_mismatch'] = True
+
+    if len(distances) > 0:
+        stats['distance_mean'] = statistics.mean(distances)
+    if len(distances) > 1:
+        stats['distance_std_dev'] = statistics.stdev(distances)
+    if len(rssis) > 0:
+        stats['rssi_mean'] = statistics.mean(rssis)
+    if len(rssis) > 1:
+        stats['rssi_std_dev'] = statistics.stdev(rssis)
+    if not summary_only:
+        stats['distances'] = distances
+        stats['distance_std_devs'] = distance_std_devs
+        stats['rssis'] = rssis
+        stats['num_attempted_measurements'] = num_attempted_measurements
+        stats['num_successful_measurements'] = num_successful_measurements
+        stats['status_codes'] = status_codes
+        stats['lcis'] = lcis
+        stats['lcrs'] = lcrs
+
+    return stats
+
+
+def run_ranging(dut,
+                aps,
+                iter_count,
+                time_between_iterations,
+                target_run_time_sec=0):
+    """Executing ranging to the set of APs.
+
+  Will execute a minimum of 'iter_count' iterations. Will continue to run
+  until execution time (just) exceeds 'target_run_time_sec'.
+
+  Args:
+    dut: Device under test
+    aps: A list of APs (Access Points) to range to.
+    iter_count: (Minimum) Number of measurements to perform.
+    time_between_iterations: Number of seconds to wait between iterations.
+    target_run_time_sec: The target run time in seconds.
+
+  Returns: a list of the events containing the RTT results (or None for a
+  failed measurement).
+  """
+    max_peers = dut.droid.wifiRttMaxPeersInRequest()
+
+    asserts.assert_true(len(aps) > 0, "Need at least one AP!")
+    if len(aps) > max_peers:
+        aps = aps[0:max_peers]
+
+    events = {}  # need to keep track per BSSID!
+    for ap in aps:
+        events[ap["BSSID"]] = []
+
+    start_clock = time.time()
+    iterations_done = 0
+    run_time = 0
+    while iterations_done < iter_count or (target_run_time_sec != 0
+                                           and run_time < target_run_time_sec):
+        if iterations_done != 0 and time_between_iterations != 0:
+            time.sleep(time_between_iterations)
+
+        id = dut.droid.wifiRttStartRangingToAccessPoints(aps)
+        try:
+            event = dut.ed.pop_event(
+                decorate_event(rconsts.EVENT_CB_RANGING_ON_RESULT, id),
+                EVENT_TIMEOUT)
+            range_results = event["data"][rconsts.EVENT_CB_RANGING_KEY_RESULTS]
+            asserts.assert_equal(
+                len(aps), len(range_results),
+                'Mismatch in length of scan results and range results')
+            for result in range_results:
+                bssid = result[rconsts.EVENT_CB_RANGING_KEY_MAC_AS_STRING]
+                asserts.assert_true(
+                    bssid in events,
+                    "Result BSSID %s not in requested AP!?" % bssid)
+                asserts.assert_equal(
+                    len(events[bssid]), iterations_done,
+                    "Duplicate results for BSSID %s!?" % bssid)
+                events[bssid].append(result)
+        except queue.Empty:
+            for ap in aps:
+                events[ap["BSSID"]].append(None)
+
+        iterations_done = iterations_done + 1
+        run_time = time.time() - start_clock
+
+    return events
+
+
+def analyze_results(all_aps_events,
+                    rtt_reference_distance_mm,
+                    distance_margin_mm,
+                    min_expected_rssi,
+                    lci_reference,
+                    lcr_reference,
+                    summary_only=False):
+    """Verifies the results of the RTT experiment.
+
+  Args:
+    all_aps_events: Dictionary of APs, each a list of RTT result events.
+    rtt_reference_distance_mm: Expected distance to the AP (source of truth).
+    distance_margin_mm: Accepted error marging in distance measurement.
+    min_expected_rssi: Minimum acceptable RSSI value
+    lci_reference, lcr_reference: Expected LCI/LCR values (arrays of bytes).
+    summary_only: Only include summary keys (reduce size).
+  """
+    all_stats = {}
+    for bssid, events in all_aps_events.items():
+        stats = extract_stats(events, rtt_reference_distance_mm,
+                              distance_margin_mm, min_expected_rssi,
+                              lci_reference, lcr_reference, summary_only)
+        all_stats[bssid] = stats
+    return all_stats
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_constants.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_constants.py
new file mode 100644
index 0000000..3a49905
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_constants.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+# Constants for Wifi related events.
+WIFI_CONNECTED = "WifiNetworkConnected"
+WIFI_DISCONNECTED = "WifiNetworkDisconnected"
+SUPPLICANT_CON_CHANGED = "SupplicantConnectionChanged"
+WIFI_STATE_CHANGED = "WifiStateChanged"
+WIFI_FORGET_NW_SUCCESS = "WifiManagerForgetNetworkOnSuccess"
+WIFI_NETWORK_REQUEST_MATCH_CB_ON_MATCH = "WifiManagerNetworkRequestMatchCallbackOnMatch"
+WIFI_NETWORK_REQUEST_MATCH_CB_ON_CONNECT_SUCCESS = "WifiManagerNetworkRequestMatchCallbackOnUserSelectionConnectSuccess"
+WIFI_NETWORK_REQUEST_MATCH_CB_ON_CONNECT_FAILURE = "WifiManagerNetworkRequestMatchCallbackOnUserSelectionConnectFailure"
+WIFI_NETWORK_CB_ON_AVAILABLE = "WifiManagerNetworkCallbackOnAvailable"
+WIFI_NETWORK_CB_ON_UNAVAILABLE = "WifiManagerNetworkCallbackOnUnavailable"
+WIFI_NETWORK_CB_ON_LOST = "WifiManagerNetworkCallbackOnLost"
+WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "WifiNetworkSuggestionPostConnection"
+
+# These constants will be used by the ACTS wifi tests.
+CONNECT_BY_CONFIG_SUCCESS = 'WifiManagerConnectByConfigOnSuccess'
+CONNECT_BY_NETID_SUCCESS = 'WifiManagerConnectByNetIdOnSuccess'
+
+# Softap related constants
+SOFTAP_CALLBACK_EVENT = "WifiManagerSoftApCallback-"
+# Callback Event for softap state change
+# WifiManagerSoftApCallback-[callbackId]-OnStateChanged
+SOFTAP_STATE_CHANGED = "-OnStateChanged"
+SOFTAP_STATE_CHANGE_CALLBACK_KEY = "State"
+WIFI_AP_DISABLING_STATE = 10
+WIFI_AP_DISABLED_STATE = 11
+WIFI_AP_ENABLING_STATE = 12
+WIFI_AP_ENABLED_STATE = 13
+WIFI_AP_FAILED_STATE = 14
+
+# Callback Event for client number change:
+# WifiManagerSoftApCallback-[callbackId]-OnNumClientsChanged
+SOFTAP_NUMBER_CLIENTS_CHANGED = "-OnNumClientsChanged"
+SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY = "NumClients"
+SOFTAP_CLIENTS_MACS_CALLBACK_KEY = "MacAddresses"
+# Callback Event for softap info change
+SOFTAP_INFO_CHANGED = "-OnInfoChanged"
+SOFTAP_INFO_FREQUENCY_CALLBACK_KEY = "frequency"
+SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY = "bandwidth"
+# Callback Event for softap client blocking
+SOFTAP_BLOCKING_CLIENT_CONNECTING = "-OnBlockedClientConnecting"
+SOFTAP_BLOCKING_CLIENT_REASON_KEY = "BlockedReason"
+SOFTAP_BLOCKING_CLIENT_WIFICLIENT_KEY = "WifiClient"
+SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0
+SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1
+
+# Callback Event for softap capability
+SOFTAP_CAPABILITY_CHANGED = "-OnCapabilityChanged"
+SOFTAP_CAPABILITY_MAX_SUPPORTED_CLIENTS = "maxSupportedClients"
+SOFTAP_CAPABILITY_FEATURE_ACS = "acsOffloadSupported"
+SOFTAP_CAPABILITY_FEATURE_CLIENT_CONTROL = "clientForceDisconnectSupported"
+SOFTAP_CAPABILITY_FEATURE_WPA3_SAE = "wpa3SaeSupported"
+
+DEFAULT_SOFTAP_TIMEOUT_S = 600 # 10 minutes
+
+# AP related constants
+AP_MAIN = "main_AP"
+AP_AUX = "aux_AP"
+SSID = "SSID"
+
+# cnss_diag property related constants
+DEVICES_USING_LEGACY_PROP = ["sailfish", "marlin", "walleye", "taimen", "muskie"]
+CNSS_DIAG_PROP = "persist.vendor.sys.cnss.diag_txt"
+LEGACY_CNSS_DIAG_PROP = "persist.sys.cnss.diag_txt"
+
+# Delay before registering the match callback.
+NETWORK_REQUEST_CB_REGISTER_DELAY_SEC = 2
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_datastore_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_datastore_utils.py
new file mode 100755
index 0000000..a79ff77
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_datastore_utils.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2018 Google, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT 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
+import pprint
+import requests
+import time
+
+from acts import asserts
+from acts import signals
+from acts import utils
+from acts_contrib.test_utils.wifi import wifi_constants
+
+"""This file consists of all the helper methods needed to interact with the
+   Datastore @ https://chaos-188802.appspot.com/ used for Android Interop
+   testing.
+"""
+
+DATASTORE_HOST = "https://chaos-188802.appspot.com"
+
+# The Datastore defines the following paths for operating methods.
+ADD_DEVICE = "devices/new"
+REMOVE_DEVICE = "devices/delete"
+LOCK_DEVICE = "devices/lock"
+UNLOCK_DEVICE = "devices/unlock"
+SHOW_DEVICE = "devices/"
+GET_DEVICES = "devices/"
+
+# HTTP content type. JSON encoded with UTF-8 character encoding.
+HTTP_HEADER = {'content-type': 'application/json'}
+
+def add_device(name, ap_label, lab_label):
+    """Add a device(AP or Packet Capturer) in datastore.
+
+       Args:
+           name: string, hostname of the device.
+           ap_label: string, AP brand name.
+           lab_label: string, lab label for AP.
+       Returns:
+          True if device was added successfully; 0 otherwise.
+    """
+    request = DATASTORE_HOST + '/' + ADD_DEVICE
+    logging.debug("Request = %s" % request)
+    response = requests.post(request,
+                             headers=HTTP_HEADER,
+                             data=json.dumps({"hostname":name,
+                                              "ap_label":ap_label,
+                                              "lab_label":lab_label}))
+    if response.json()['result'] == 'success':
+        logging.info("Added device %s to datastore" % name)
+        return True
+    return False
+
+def remove_device(name):
+    """Delete a device(AP or Packet Capturer) in datastore.
+
+       Args:
+           name: string, hostname of the device to delete.
+       Returns:
+           True if device was deleted successfully; 0 otherwise.
+    """
+    request = DATASTORE_HOST + '/' + REMOVE_DEVICE
+    logging.debug("Request = %s" % request)
+    response = requests.put(request,
+                            headers=HTTP_HEADER,
+                            data=json.dumps({"hostname":name}))
+    result_str = "%s deleted." % name
+    if result_str in response.text:
+        logging.info("Removed device %s from datastore" % name)
+        return True
+    return False
+
+def lock_device(name, admin):
+    """Lock a device(AP or Packet Capturer) in datastore.
+
+       Args:
+           name: string, hostname of the device in datastore.
+           admin: string, unique admin name for locking.
+      Returns:
+          True if operation was successful; 0 otherwise.
+    """
+    request = DATASTORE_HOST + '/' + LOCK_DEVICE
+    logging.debug("Request = %s" % request)
+    response = requests.put(request,
+                            headers=HTTP_HEADER,
+                            data=json.dumps({"hostname":name, "locked_by":admin}))
+    if response.json()['result']:
+        logging.info("Locked device %s in datastore" % name)
+        return True
+    return False
+
+def unlock_device(name):
+    """Un-lock a device(AP or Packet Capturer) in datastore.
+
+       Args:
+           name: string, hostname of the device in datastore.
+      Returns:
+          True if operation was successful; 0 otherwise.
+    """
+    request = DATASTORE_HOST + '/' + UNLOCK_DEVICE
+    logging.debug("Request = %s" % request)
+    response = requests.put(request,
+                            headers=HTTP_HEADER,
+                            data=json.dumps({"hostname":name}))
+    if response.json()['result']:
+        logging.info("Finished un-locking AP %s in datastore" % name)
+        return True
+    return False
+
+def show_device(name):
+    """Show device properties for a given device(AP or Packet Capturer).
+
+       Args:
+           name: string, hostname of the device in datastore to fetch info.
+           Returns: dict of device name:value properties if successful;
+                    None otherwise.
+    """
+    request = DATASTORE_HOST + '/' + SHOW_DEVICE + name
+    logging.debug("Request = %s" % request)
+    response = requests.get(request)
+    if 'error' in response.text:
+        return None
+    return response.json()
+
+def get_devices():
+    """Get a list of all devices in the datastore.
+
+    Returns: dict of all devices' name:value properties if successful;
+             None otherwise.
+    """
+    request = DATASTORE_HOST + '/' + GET_DEVICES
+    logging.debug("Request = %s" % request)
+    response = requests.get(request)
+    if 'error' in response.text:
+        return None
+    return response.json()
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
new file mode 100644
index 0000000..88309ed
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils.py
@@ -0,0 +1,1379 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import bokeh, bokeh.plotting
+import collections
+import hashlib
+import itertools
+import json
+import logging
+import math
+import os
+import re
+import statistics
+import time
+from acts.controllers.android_device import AndroidDevice
+from acts.controllers.utils_lib import ssh
+from acts import utils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from concurrent.futures import ThreadPoolExecutor
+
+SHORT_SLEEP = 1
+MED_SLEEP = 6
+TEST_TIMEOUT = 10
+STATION_DUMP = 'iw wlan0 station dump'
+SCAN = 'wpa_cli scan'
+SCAN_RESULTS = 'wpa_cli scan_results'
+SIGNAL_POLL = 'wpa_cli signal_poll'
+WPA_CLI_STATUS = 'wpa_cli status'
+CONST_3dB = 3.01029995664
+RSSI_ERROR_VAL = float('nan')
+RTT_REGEX = re.compile(r'^\[(?P<timestamp>\S+)\] .*? time=(?P<rtt>\S+)')
+LOSS_REGEX = re.compile(r'(?P<loss>\S+)% packet loss')
+FW_REGEX = re.compile(r'FW:(?P<firmware>\S+) HW:')
+
+
+# Threading decorator
+def nonblocking(f):
+    """Creates a decorator transforming function calls to non-blocking"""
+    def wrap(*args, **kwargs):
+        executor = ThreadPoolExecutor(max_workers=1)
+        thread_future = executor.submit(f, *args, **kwargs)
+        # Ensure resources are freed up when executor ruturns or raises
+        executor.shutdown(wait=False)
+        return thread_future
+
+    return wrap
+
+
+# Link layer stats utilities
+class LinkLayerStats():
+
+    LLSTATS_CMD = 'cat /d/wlan0/ll_stats'
+    PEER_REGEX = 'LL_STATS_PEER_ALL'
+    MCS_REGEX = re.compile(
+        r'preamble: (?P<mode>\S+), nss: (?P<num_streams>\S+), bw: (?P<bw>\S+), '
+        'mcs: (?P<mcs>\S+), bitrate: (?P<rate>\S+), txmpdu: (?P<txmpdu>\S+), '
+        'rxmpdu: (?P<rxmpdu>\S+), mpdu_lost: (?P<mpdu_lost>\S+), '
+        'retries: (?P<retries>\S+), retries_short: (?P<retries_short>\S+), '
+        'retries_long: (?P<retries_long>\S+)')
+    MCS_ID = collections.namedtuple(
+        'mcs_id', ['mode', 'num_streams', 'bandwidth', 'mcs', 'rate'])
+    MODE_MAP = {'0': '11a/g', '1': '11b', '2': '11n', '3': '11ac'}
+    BW_MAP = {'0': 20, '1': 40, '2': 80}
+
+    def __init__(self, dut, llstats_enabled=True):
+        self.dut = dut
+        self.llstats_enabled = llstats_enabled
+        self.llstats_cumulative = self._empty_llstats()
+        self.llstats_incremental = self._empty_llstats()
+
+    def update_stats(self):
+        if self.llstats_enabled:
+            try:
+                llstats_output = self.dut.adb.shell(self.LLSTATS_CMD,
+                                                    timeout=0.1)
+            except:
+                llstats_output = ''
+        else:
+            llstats_output = ''
+        self._update_stats(llstats_output)
+
+    def reset_stats(self):
+        self.llstats_cumulative = self._empty_llstats()
+        self.llstats_incremental = self._empty_llstats()
+
+    def _empty_llstats(self):
+        return collections.OrderedDict(mcs_stats=collections.OrderedDict(),
+                                       summary=collections.OrderedDict())
+
+    def _empty_mcs_stat(self):
+        return collections.OrderedDict(txmpdu=0,
+                                       rxmpdu=0,
+                                       mpdu_lost=0,
+                                       retries=0,
+                                       retries_short=0,
+                                       retries_long=0)
+
+    def _mcs_id_to_string(self, mcs_id):
+        mcs_string = '{} {}MHz Nss{} MCS{} {}Mbps'.format(
+            mcs_id.mode, mcs_id.bandwidth, mcs_id.num_streams, mcs_id.mcs,
+            mcs_id.rate)
+        return mcs_string
+
+    def _parse_mcs_stats(self, llstats_output):
+        llstats_dict = {}
+        # Look for per-peer stats
+        match = re.search(self.PEER_REGEX, llstats_output)
+        if not match:
+            self.reset_stats()
+            return collections.OrderedDict()
+        # Find and process all matches for per stream stats
+        match_iter = re.finditer(self.MCS_REGEX, llstats_output)
+        for match in match_iter:
+            current_mcs = self.MCS_ID(self.MODE_MAP[match.group('mode')],
+                                      int(match.group('num_streams')) + 1,
+                                      self.BW_MAP[match.group('bw')],
+                                      int(match.group('mcs')),
+                                      int(match.group('rate'), 16) / 1000)
+            current_stats = collections.OrderedDict(
+                txmpdu=int(match.group('txmpdu')),
+                rxmpdu=int(match.group('rxmpdu')),
+                mpdu_lost=int(match.group('mpdu_lost')),
+                retries=int(match.group('retries')),
+                retries_short=int(match.group('retries_short')),
+                retries_long=int(match.group('retries_long')))
+            llstats_dict[self._mcs_id_to_string(current_mcs)] = current_stats
+        return llstats_dict
+
+    def _diff_mcs_stats(self, new_stats, old_stats):
+        stats_diff = collections.OrderedDict()
+        for stat_key in new_stats.keys():
+            stats_diff[stat_key] = new_stats[stat_key] - old_stats[stat_key]
+        return stats_diff
+
+    def _generate_stats_summary(self, llstats_dict):
+        llstats_summary = collections.OrderedDict(common_tx_mcs=None,
+                                                  common_tx_mcs_count=0,
+                                                  common_tx_mcs_freq=0,
+                                                  common_rx_mcs=None,
+                                                  common_rx_mcs_count=0,
+                                                  common_rx_mcs_freq=0)
+        txmpdu_count = 0
+        rxmpdu_count = 0
+        for mcs_id, mcs_stats in llstats_dict['mcs_stats'].items():
+            if mcs_stats['txmpdu'] > llstats_summary['common_tx_mcs_count']:
+                llstats_summary['common_tx_mcs'] = mcs_id
+                llstats_summary['common_tx_mcs_count'] = mcs_stats['txmpdu']
+            if mcs_stats['rxmpdu'] > llstats_summary['common_rx_mcs_count']:
+                llstats_summary['common_rx_mcs'] = mcs_id
+                llstats_summary['common_rx_mcs_count'] = mcs_stats['rxmpdu']
+            txmpdu_count += mcs_stats['txmpdu']
+            rxmpdu_count += mcs_stats['rxmpdu']
+        if txmpdu_count:
+            llstats_summary['common_tx_mcs_freq'] = (
+                llstats_summary['common_tx_mcs_count'] / txmpdu_count)
+        if rxmpdu_count:
+            llstats_summary['common_rx_mcs_freq'] = (
+                llstats_summary['common_rx_mcs_count'] / rxmpdu_count)
+        return llstats_summary
+
+    def _update_stats(self, llstats_output):
+        # Parse stats
+        new_llstats = self._empty_llstats()
+        new_llstats['mcs_stats'] = self._parse_mcs_stats(llstats_output)
+        # Save old stats and set new cumulative stats
+        old_llstats = self.llstats_cumulative.copy()
+        self.llstats_cumulative = new_llstats.copy()
+        # Compute difference between new and old stats
+        self.llstats_incremental = self._empty_llstats()
+        for mcs_id, new_mcs_stats in new_llstats['mcs_stats'].items():
+            old_mcs_stats = old_llstats['mcs_stats'].get(
+                mcs_id, self._empty_mcs_stat())
+            self.llstats_incremental['mcs_stats'][
+                mcs_id] = self._diff_mcs_stats(new_mcs_stats, old_mcs_stats)
+        # Generate llstats summary
+        self.llstats_incremental['summary'] = self._generate_stats_summary(
+            self.llstats_incremental)
+        self.llstats_cumulative['summary'] = self._generate_stats_summary(
+            self.llstats_cumulative)
+
+
+# JSON serializer
+def serialize_dict(input_dict):
+    """Function to serialize dicts to enable JSON output"""
+    output_dict = collections.OrderedDict()
+    for key, value in input_dict.items():
+        output_dict[_serialize_value(key)] = _serialize_value(value)
+    return output_dict
+
+
+def _serialize_value(value):
+    """Function to recursively serialize dict entries to enable JSON output"""
+    if isinstance(value, tuple):
+        return str(value)
+    if isinstance(value, list):
+        return [_serialize_value(x) for x in value]
+    elif isinstance(value, dict):
+        return serialize_dict(value)
+    else:
+        return value
+
+
+# Plotting Utilities
+class BokehFigure():
+    """Class enabling  simplified Bokeh plotting."""
+
+    COLORS = [
+        'black',
+        'blue',
+        'blueviolet',
+        'brown',
+        'burlywood',
+        'cadetblue',
+        'cornflowerblue',
+        'crimson',
+        'cyan',
+        'darkblue',
+        'darkgreen',
+        'darkmagenta',
+        'darkorange',
+        'darkred',
+        'deepskyblue',
+        'goldenrod',
+        'green',
+        'grey',
+        'indigo',
+        'navy',
+        'olive',
+        'orange',
+        'red',
+        'salmon',
+        'teal',
+        'yellow',
+    ]
+    MARKERS = [
+        'asterisk', 'circle', 'circle_cross', 'circle_x', 'cross', 'diamond',
+        'diamond_cross', 'hex', 'inverted_triangle', 'square', 'square_x',
+        'square_cross', 'triangle', 'x'
+    ]
+
+    TOOLS = ('box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save')
+    TOOLTIPS = [
+        ('index', '$index'),
+        ('(x,y)', '($x, $y)'),
+        ('info', '@hover_text'),
+    ]
+
+    def __init__(self,
+                 title=None,
+                 x_label=None,
+                 primary_y_label=None,
+                 secondary_y_label=None,
+                 height=700,
+                 width=1100,
+                 title_size='15pt',
+                 axis_label_size='12pt',
+                 json_file=None):
+        if json_file:
+            self.load_from_json(json_file)
+        else:
+            self.figure_data = []
+            self.fig_property = {
+                'title': title,
+                'x_label': x_label,
+                'primary_y_label': primary_y_label,
+                'secondary_y_label': secondary_y_label,
+                'num_lines': 0,
+                'height': height,
+                'width': width,
+                'title_size': title_size,
+                'axis_label_size': axis_label_size
+            }
+
+    def init_plot(self):
+        self.plot = bokeh.plotting.figure(
+            sizing_mode='scale_both',
+            plot_width=self.fig_property['width'],
+            plot_height=self.fig_property['height'],
+            title=self.fig_property['title'],
+            tools=self.TOOLS,
+            output_backend='webgl')
+        self.plot.hover.tooltips = self.TOOLTIPS
+        self.plot.add_tools(
+            bokeh.models.tools.WheelZoomTool(dimensions='width'))
+        self.plot.add_tools(
+            bokeh.models.tools.WheelZoomTool(dimensions='height'))
+
+    def _filter_line(self, x_data, y_data, hover_text=None):
+        """Function to remove NaN points from bokeh plots."""
+        x_data_filtered = []
+        y_data_filtered = []
+        hover_text_filtered = []
+        for x, y, hover in itertools.zip_longest(x_data, y_data, hover_text):
+            if not math.isnan(y):
+                x_data_filtered.append(x)
+                y_data_filtered.append(y)
+                hover_text_filtered.append(hover)
+        return x_data_filtered, y_data_filtered, hover_text_filtered
+
+    def add_line(self,
+                 x_data,
+                 y_data,
+                 legend,
+                 hover_text=None,
+                 color=None,
+                 width=3,
+                 style='solid',
+                 marker=None,
+                 marker_size=10,
+                 shaded_region=None,
+                 y_axis='default'):
+        """Function to add line to existing BokehFigure.
+
+        Args:
+            x_data: list containing x-axis values for line
+            y_data: list containing y_axis values for line
+            legend: string containing line title
+            hover_text: text to display when hovering over lines
+            color: string describing line color
+            width: integer line width
+            style: string describing line style, e.g, solid or dashed
+            marker: string specifying line marker, e.g., cross
+            shaded region: data describing shaded region to plot
+            y_axis: identifier for y-axis to plot line against
+        """
+        if y_axis not in ['default', 'secondary']:
+            raise ValueError('y_axis must be default or secondary')
+        if color == None:
+            color = self.COLORS[self.fig_property['num_lines'] %
+                                len(self.COLORS)]
+        if style == 'dashed':
+            style = [5, 5]
+        if not hover_text:
+            hover_text = ['y={}'.format(y) for y in y_data]
+        x_data_filter, y_data_filter, hover_text_filter = self._filter_line(
+            x_data, y_data, hover_text)
+        self.figure_data.append({
+            'x_data': x_data_filter,
+            'y_data': y_data_filter,
+            'legend': legend,
+            'hover_text': hover_text_filter,
+            'color': color,
+            'width': width,
+            'style': style,
+            'marker': marker,
+            'marker_size': marker_size,
+            'shaded_region': shaded_region,
+            'y_axis': y_axis
+        })
+        self.fig_property['num_lines'] += 1
+
+    def add_scatter(self,
+                    x_data,
+                    y_data,
+                    legend,
+                    hover_text=None,
+                    color=None,
+                    marker=None,
+                    marker_size=10,
+                    y_axis='default'):
+        """Function to add line to existing BokehFigure.
+
+        Args:
+            x_data: list containing x-axis values for line
+            y_data: list containing y_axis values for line
+            legend: string containing line title
+            hover_text: text to display when hovering over lines
+            color: string describing line color
+            marker: string specifying marker, e.g., cross
+            y_axis: identifier for y-axis to plot line against
+        """
+        if y_axis not in ['default', 'secondary']:
+            raise ValueError('y_axis must be default or secondary')
+        if color == None:
+            color = self.COLORS[self.fig_property['num_lines'] %
+                                len(self.COLORS)]
+        if marker == None:
+            marker = self.MARKERS[self.fig_property['num_lines'] %
+                                  len(self.MARKERS)]
+        if not hover_text:
+            hover_text = ['y={}'.format(y) for y in y_data]
+        self.figure_data.append({
+            'x_data': x_data,
+            'y_data': y_data,
+            'legend': legend,
+            'hover_text': hover_text,
+            'color': color,
+            'width': 0,
+            'style': 'solid',
+            'marker': marker,
+            'marker_size': marker_size,
+            'shaded_region': None,
+            'y_axis': y_axis
+        })
+        self.fig_property['num_lines'] += 1
+
+    def generate_figure(self, output_file=None, save_json=True):
+        """Function to generate and save BokehFigure.
+
+        Args:
+            output_file: string specifying output file path
+        """
+        self.init_plot()
+        two_axes = False
+        for line in self.figure_data:
+            source = bokeh.models.ColumnDataSource(
+                data=dict(x=line['x_data'],
+                          y=line['y_data'],
+                          hover_text=line['hover_text']))
+            if line['width'] > 0:
+                self.plot.line(x='x',
+                               y='y',
+                               legend_label=line['legend'],
+                               line_width=line['width'],
+                               color=line['color'],
+                               line_dash=line['style'],
+                               name=line['y_axis'],
+                               y_range_name=line['y_axis'],
+                               source=source)
+            if line['shaded_region']:
+                band_x = line['shaded_region']['x_vector']
+                band_x.extend(line['shaded_region']['x_vector'][::-1])
+                band_y = line['shaded_region']['lower_limit']
+                band_y.extend(line['shaded_region']['upper_limit'][::-1])
+                self.plot.patch(band_x,
+                                band_y,
+                                color='#7570B3',
+                                line_alpha=0.1,
+                                fill_alpha=0.1)
+            if line['marker'] in self.MARKERS:
+                marker_func = getattr(self.plot, line['marker'])
+                marker_func(x='x',
+                            y='y',
+                            size=line['marker_size'],
+                            legend_label=line['legend'],
+                            line_color=line['color'],
+                            fill_color=line['color'],
+                            name=line['y_axis'],
+                            y_range_name=line['y_axis'],
+                            source=source)
+            if line['y_axis'] == 'secondary':
+                two_axes = True
+
+        #x-axis formatting
+        self.plot.xaxis.axis_label = self.fig_property['x_label']
+        self.plot.x_range.range_padding = 0
+        self.plot.xaxis[0].axis_label_text_font_size = self.fig_property[
+            'axis_label_size']
+        #y-axis formatting
+        self.plot.yaxis[0].axis_label = self.fig_property['primary_y_label']
+        self.plot.yaxis[0].axis_label_text_font_size = self.fig_property[
+            'axis_label_size']
+        self.plot.y_range = bokeh.models.DataRange1d(names=['default'])
+        if two_axes and 'secondary' not in self.plot.extra_y_ranges:
+            self.plot.extra_y_ranges = {
+                'secondary': bokeh.models.DataRange1d(names=['secondary'])
+            }
+            self.plot.add_layout(
+                bokeh.models.LinearAxis(
+                    y_range_name='secondary',
+                    axis_label=self.fig_property['secondary_y_label'],
+                    axis_label_text_font_size=self.
+                    fig_property['axis_label_size']), 'right')
+        # plot formatting
+        self.plot.legend.location = 'top_right'
+        self.plot.legend.click_policy = 'hide'
+        self.plot.title.text_font_size = self.fig_property['title_size']
+
+        if output_file is not None:
+            self.save_figure(output_file, save_json)
+        return self.plot
+
+    def load_from_json(self, file_path):
+        with open(file_path, 'r') as json_file:
+            fig_dict = json.load(json_file)
+        self.fig_property = fig_dict['fig_property']
+        self.figure_data = fig_dict['figure_data']
+
+    def _save_figure_json(self, output_file):
+        """Function to save a json format of a figure"""
+        figure_dict = collections.OrderedDict(fig_property=self.fig_property,
+                                              figure_data=self.figure_data)
+        output_file = output_file.replace('.html', '_plot_data.json')
+        with open(output_file, 'w') as outfile:
+            json.dump(figure_dict, outfile, indent=4)
+
+    def save_figure(self, output_file, save_json=True):
+        """Function to save BokehFigure.
+
+        Args:
+            output_file: string specifying output file path
+            save_json: flag controlling json outputs
+        """
+        bokeh.plotting.output_file(output_file)
+        bokeh.plotting.save(self.plot)
+        if save_json:
+            self._save_figure_json(output_file)
+
+    @staticmethod
+    def save_figures(figure_array, output_file_path, save_json=True):
+        """Function to save list of BokehFigures in one file.
+
+        Args:
+            figure_array: list of BokehFigure object to be plotted
+            output_file: string specifying output file path
+        """
+        for idx, figure in enumerate(figure_array):
+            figure.generate_figure()
+            if save_json:
+                json_file_path = output_file_path.replace(
+                    '.html', '{}-plot_data.json'.format(idx))
+                figure._save_figure_json(json_file_path)
+        plot_array = [figure.plot for figure in figure_array]
+        all_plots = bokeh.layouts.column(children=plot_array,
+                                         sizing_mode='scale_width')
+        bokeh.plotting.output_file(output_file_path)
+        bokeh.plotting.save(all_plots)
+
+
+# Ping utilities
+class PingResult(object):
+    """An object that contains the results of running ping command.
+
+    Attributes:
+        connected: True if a connection was made. False otherwise.
+        packet_loss_percentage: The total percentage of packets lost.
+        transmission_times: The list of PingTransmissionTimes containing the
+            timestamps gathered for transmitted packets.
+        rtts: An list-like object enumerating all round-trip-times of
+            transmitted packets.
+        timestamps: A list-like object enumerating the beginning timestamps of
+            each packet transmission.
+        ping_interarrivals: A list-like object enumerating the amount of time
+            between the beginning of each subsequent transmission.
+    """
+    def __init__(self, ping_output):
+        self.packet_loss_percentage = 100
+        self.transmission_times = []
+
+        self.rtts = _ListWrap(self.transmission_times, lambda entry: entry.rtt)
+        self.timestamps = _ListWrap(self.transmission_times,
+                                    lambda entry: entry.timestamp)
+        self.ping_interarrivals = _PingInterarrivals(self.transmission_times)
+
+        self.start_time = 0
+        for line in ping_output:
+            if 'loss' in line:
+                match = re.search(LOSS_REGEX, line)
+                self.packet_loss_percentage = float(match.group('loss'))
+            if 'time=' in line:
+                match = re.search(RTT_REGEX, line)
+                if self.start_time == 0:
+                    self.start_time = float(match.group('timestamp'))
+                self.transmission_times.append(
+                    PingTransmissionTimes(
+                        float(match.group('timestamp')) - self.start_time,
+                        float(match.group('rtt'))))
+        self.connected = len(
+            ping_output) > 1 and self.packet_loss_percentage < 100
+
+    def __getitem__(self, item):
+        if item == 'rtt':
+            return self.rtts
+        if item == 'connected':
+            return self.connected
+        if item == 'packet_loss_percentage':
+            return self.packet_loss_percentage
+        raise ValueError('Invalid key. Please use an attribute instead.')
+
+    def as_dict(self):
+        return {
+            'connected': 1 if self.connected else 0,
+            'rtt': list(self.rtts),
+            'time_stamp': list(self.timestamps),
+            'ping_interarrivals': list(self.ping_interarrivals),
+            'packet_loss_percentage': self.packet_loss_percentage
+        }
+
+
+class PingTransmissionTimes(object):
+    """A class that holds the timestamps for a packet sent via the ping command.
+
+    Attributes:
+        rtt: The round trip time for the packet sent.
+        timestamp: The timestamp the packet started its trip.
+    """
+    def __init__(self, timestamp, rtt):
+        self.rtt = rtt
+        self.timestamp = timestamp
+
+
+class _ListWrap(object):
+    """A convenient helper class for treating list iterators as native lists."""
+    def __init__(self, wrapped_list, func):
+        self.__wrapped_list = wrapped_list
+        self.__func = func
+
+    def __getitem__(self, key):
+        return self.__func(self.__wrapped_list[key])
+
+    def __iter__(self):
+        for item in self.__wrapped_list:
+            yield self.__func(item)
+
+    def __len__(self):
+        return len(self.__wrapped_list)
+
+
+class _PingInterarrivals(object):
+    """A helper class for treating ping interarrivals as a native list."""
+    def __init__(self, ping_entries):
+        self.__ping_entries = ping_entries
+
+    def __getitem__(self, key):
+        return (self.__ping_entries[key + 1].timestamp -
+                self.__ping_entries[key].timestamp)
+
+    def __iter__(self):
+        for index in range(len(self.__ping_entries) - 1):
+            yield self[index]
+
+    def __len__(self):
+        return max(0, len(self.__ping_entries) - 1)
+
+
+def get_ping_stats(src_device, dest_address, ping_duration, ping_interval,
+                   ping_size):
+    """Run ping to or from the DUT.
+
+    The function computes either pings the DUT or pings a remote ip from
+    DUT.
+
+    Args:
+        src_device: object representing device to ping from
+        dest_address: ip address to ping
+        ping_duration: timeout to set on the the ping process (in seconds)
+        ping_interval: time between pings (in seconds)
+        ping_size: size of ping packet payload
+    Returns:
+        ping_result: dict containing ping results and other meta data
+    """
+    ping_count = int(ping_duration / ping_interval)
+    ping_deadline = int(ping_count * ping_interval) + 1
+    ping_cmd = 'ping -c {} -w {} -i {} -s {} -D'.format(
+        ping_count,
+        ping_deadline,
+        ping_interval,
+        ping_size,
+    )
+    if isinstance(src_device, AndroidDevice):
+        ping_cmd = '{} {}'.format(ping_cmd, dest_address)
+        ping_output = src_device.adb.shell(ping_cmd,
+                                           timeout=ping_deadline + SHORT_SLEEP,
+                                           ignore_status=True)
+    elif isinstance(src_device, ssh.connection.SshConnection):
+        ping_cmd = 'sudo {} {}'.format(ping_cmd, dest_address)
+        ping_output = src_device.run(ping_cmd,
+                                     timeout=ping_deadline + SHORT_SLEEP,
+                                     ignore_status=True).stdout
+    else:
+        raise TypeError('Unable to ping using src_device of type %s.' %
+                        type(src_device))
+    return PingResult(ping_output.splitlines())
+
+
+@nonblocking
+def get_ping_stats_nb(src_device, dest_address, ping_duration, ping_interval,
+                      ping_size):
+    return get_ping_stats(src_device, dest_address, ping_duration,
+                          ping_interval, ping_size)
+
+
+# Iperf utilities
+@nonblocking
+def start_iperf_client_nb(iperf_client, iperf_server_address, iperf_args, tag,
+                          timeout):
+    return iperf_client.start(iperf_server_address, iperf_args, tag, timeout)
+
+
+def get_iperf_arg_string(duration,
+                         reverse_direction,
+                         interval=1,
+                         traffic_type='TCP',
+                         socket_size=None,
+                         num_processes=1,
+                         udp_throughput='1000M',
+                         ipv6=False):
+    """Function to format iperf client arguments.
+
+    This function takes in iperf client parameters and returns a properly
+    formatter iperf arg string to be used in throughput tests.
+
+    Args:
+        duration: iperf duration in seconds
+        reverse_direction: boolean controlling the -R flag for iperf clients
+        interval: iperf print interval
+        traffic_type: string specifying TCP or UDP traffic
+        socket_size: string specifying TCP window or socket buffer, e.g., 2M
+        num_processes: int specifying number of iperf processes
+        udp_throughput: string specifying TX throughput in UDP tests, e.g. 100M
+        ipv6: boolean controlling the use of IP V6
+    Returns:
+        iperf_args: string of formatted iperf args
+    """
+    iperf_args = '-i {} -t {} -J '.format(interval, duration)
+    if ipv6:
+        iperf_args = iperf_args + '-6 '
+    if traffic_type.upper() == 'UDP':
+        iperf_args = iperf_args + '-u -b {} -l 1400 -P {} '.format(
+            udp_throughput, num_processes)
+    elif traffic_type.upper() == 'TCP':
+        iperf_args = iperf_args + '-P {} '.format(num_processes)
+    if socket_size:
+        iperf_args = iperf_args + '-w {} '.format(socket_size)
+    if reverse_direction:
+        iperf_args = iperf_args + ' -R'
+    return iperf_args
+
+
+# Rssi Utilities
+def empty_rssi_result():
+    return collections.OrderedDict([('data', []), ('mean', None),
+                                    ('stdev', None)])
+
+
+def get_connected_rssi(dut,
+                       num_measurements=1,
+                       polling_frequency=SHORT_SLEEP,
+                       first_measurement_delay=0,
+                       disconnect_warning=True,
+                       ignore_samples=0,
+                       interface=None):
+    """Gets all RSSI values reported for the connected access point/BSSID.
+
+    Args:
+        dut: android device object from which to get RSSI
+        num_measurements: number of scans done, and RSSIs collected
+        polling_frequency: time to wait between RSSI measurements
+        disconnect_warning: boolean controlling disconnection logging messages
+        ignore_samples: number of leading samples to ignore
+    Returns:
+        connected_rssi: dict containing the measurements results for
+        all reported RSSI values (signal_poll, per chain, etc.) and their
+        statistics
+    """
+    # yapf: disable
+    connected_rssi = collections.OrderedDict(
+        [('time_stamp', []),
+         ('bssid', []), ('ssid', []), ('frequency', []),
+         ('signal_poll_rssi', empty_rssi_result()),
+         ('signal_poll_avg_rssi', empty_rssi_result()),
+         ('chain_0_rssi', empty_rssi_result()),
+         ('chain_1_rssi', empty_rssi_result())])
+    # yapf: enable
+    previous_bssid = 'disconnected'
+    t0 = time.time()
+    time.sleep(first_measurement_delay)
+    for idx in range(num_measurements):
+        measurement_start_time = time.time()
+        connected_rssi['time_stamp'].append(measurement_start_time - t0)
+        # Get signal poll RSSI
+        if interface is None:
+            status_output = dut.adb.shell(WPA_CLI_STATUS)
+        else:
+            status_output = dut.adb.shell(
+                'wpa_cli -i {} status'.format(interface))
+        match = re.search('bssid=.*', status_output)
+        if match:
+            current_bssid = match.group(0).split('=')[1]
+            connected_rssi['bssid'].append(current_bssid)
+        else:
+            current_bssid = 'disconnected'
+            connected_rssi['bssid'].append(current_bssid)
+            if disconnect_warning and previous_bssid != 'disconnected':
+                logging.warning('WIFI DISCONNECT DETECTED!')
+        previous_bssid = current_bssid
+        match = re.search('\s+ssid=.*', status_output)
+        if match:
+            ssid = match.group(0).split('=')[1]
+            connected_rssi['ssid'].append(ssid)
+        else:
+            connected_rssi['ssid'].append('disconnected')
+        if interface is None:
+            signal_poll_output = dut.adb.shell(SIGNAL_POLL)
+        else:
+            signal_poll_output = dut.adb.shell(
+                'wpa_cli -i {} signal_poll'.format(interface))
+        match = re.search('FREQUENCY=.*', signal_poll_output)
+        if match:
+            frequency = int(match.group(0).split('=')[1])
+            connected_rssi['frequency'].append(frequency)
+        else:
+            connected_rssi['frequency'].append(RSSI_ERROR_VAL)
+        match = re.search('RSSI=.*', signal_poll_output)
+        if match:
+            temp_rssi = int(match.group(0).split('=')[1])
+            if temp_rssi == -9999 or temp_rssi == 0:
+                connected_rssi['signal_poll_rssi']['data'].append(
+                    RSSI_ERROR_VAL)
+            else:
+                connected_rssi['signal_poll_rssi']['data'].append(temp_rssi)
+        else:
+            connected_rssi['signal_poll_rssi']['data'].append(RSSI_ERROR_VAL)
+        match = re.search('AVG_RSSI=.*', signal_poll_output)
+        if match:
+            connected_rssi['signal_poll_avg_rssi']['data'].append(
+                int(match.group(0).split('=')[1]))
+        else:
+            connected_rssi['signal_poll_avg_rssi']['data'].append(
+                RSSI_ERROR_VAL)
+
+        # Get per chain RSSI
+        if interface is None:
+            per_chain_rssi = dut.adb.shell(STATION_DUMP)
+        else:
+            per_chain_rssi = ''
+        match = re.search('.*signal avg:.*', per_chain_rssi)
+        if match:
+            per_chain_rssi = per_chain_rssi[per_chain_rssi.find('[') +
+                                            1:per_chain_rssi.find(']')]
+            per_chain_rssi = per_chain_rssi.split(', ')
+            connected_rssi['chain_0_rssi']['data'].append(
+                int(per_chain_rssi[0]))
+            connected_rssi['chain_1_rssi']['data'].append(
+                int(per_chain_rssi[1]))
+        else:
+            connected_rssi['chain_0_rssi']['data'].append(RSSI_ERROR_VAL)
+            connected_rssi['chain_1_rssi']['data'].append(RSSI_ERROR_VAL)
+        measurement_elapsed_time = time.time() - measurement_start_time
+        time.sleep(max(0, polling_frequency - measurement_elapsed_time))
+
+    # Compute mean RSSIs. Only average valid readings.
+    # Output RSSI_ERROR_VAL if no valid connected readings found.
+    for key, val in connected_rssi.copy().items():
+        if 'data' not in val:
+            continue
+        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
+        if len(filtered_rssi_values) > ignore_samples:
+            filtered_rssi_values = filtered_rssi_values[ignore_samples:]
+        if filtered_rssi_values:
+            connected_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
+            if len(filtered_rssi_values) > 1:
+                connected_rssi[key]['stdev'] = statistics.stdev(
+                    filtered_rssi_values)
+            else:
+                connected_rssi[key]['stdev'] = 0
+        else:
+            connected_rssi[key]['mean'] = RSSI_ERROR_VAL
+            connected_rssi[key]['stdev'] = RSSI_ERROR_VAL
+    return connected_rssi
+
+
+@nonblocking
+def get_connected_rssi_nb(dut,
+                          num_measurements=1,
+                          polling_frequency=SHORT_SLEEP,
+                          first_measurement_delay=0,
+                          disconnect_warning=True,
+                          ignore_samples=0,
+                          interface=None):
+    return get_connected_rssi(dut, num_measurements, polling_frequency,
+                              first_measurement_delay, disconnect_warning,
+                              ignore_samples, interface)
+
+
+def get_scan_rssi(dut, tracked_bssids, num_measurements=1):
+    """Gets scan RSSI for specified BSSIDs.
+
+    Args:
+        dut: android device object from which to get RSSI
+        tracked_bssids: array of BSSIDs to gather RSSI data for
+        num_measurements: number of scans done, and RSSIs collected
+    Returns:
+        scan_rssi: dict containing the measurement results as well as the
+        statistics of the scan RSSI for all BSSIDs in tracked_bssids
+    """
+    scan_rssi = collections.OrderedDict()
+    for bssid in tracked_bssids:
+        scan_rssi[bssid] = empty_rssi_result()
+    for idx in range(num_measurements):
+        scan_output = dut.adb.shell(SCAN)
+        time.sleep(MED_SLEEP)
+        scan_output = dut.adb.shell(SCAN_RESULTS)
+        for bssid in tracked_bssids:
+            bssid_result = re.search(bssid + '.*',
+                                     scan_output,
+                                     flags=re.IGNORECASE)
+            if bssid_result:
+                bssid_result = bssid_result.group(0).split('\t')
+                scan_rssi[bssid]['data'].append(int(bssid_result[2]))
+            else:
+                scan_rssi[bssid]['data'].append(RSSI_ERROR_VAL)
+    # Compute mean RSSIs. Only average valid readings.
+    # Output RSSI_ERROR_VAL if no readings found.
+    for key, val in scan_rssi.items():
+        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
+        if filtered_rssi_values:
+            scan_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
+            if len(filtered_rssi_values) > 1:
+                scan_rssi[key]['stdev'] = statistics.stdev(
+                    filtered_rssi_values)
+            else:
+                scan_rssi[key]['stdev'] = 0
+        else:
+            scan_rssi[key]['mean'] = RSSI_ERROR_VAL
+            scan_rssi[key]['stdev'] = RSSI_ERROR_VAL
+    return scan_rssi
+
+
+@nonblocking
+def get_scan_rssi_nb(dut, tracked_bssids, num_measurements=1):
+    return get_scan_rssi(dut, tracked_bssids, num_measurements)
+
+
+# Attenuator Utilities
+def atten_by_label(atten_list, path_label, atten_level):
+    """Attenuate signals according to their path label.
+
+    Args:
+        atten_list: list of attenuators to iterate over
+        path_label: path label on which to set desired attenuation
+        atten_level: attenuation desired on path
+    """
+    for atten in atten_list:
+        if path_label in atten.path:
+            atten.set_atten(atten_level)
+
+
+def get_atten_for_target_rssi(target_rssi, attenuators, dut, ping_server):
+    """Function to estimate attenuation to hit a target RSSI.
+
+    This function estimates a constant attenuation setting on all atennuation
+    ports to hit a target RSSI. The estimate is not meant to be exact or
+    guaranteed.
+
+    Args:
+        target_rssi: rssi of interest
+        attenuators: list of attenuator ports
+        dut: android device object assumed connected to a wifi network.
+        ping_server: ssh connection object to ping server
+    Returns:
+        target_atten: attenuation setting to achieve target_rssi
+    """
+    logging.info('Searching attenuation for RSSI = {}dB'.format(target_rssi))
+    # Set attenuator to 0 dB
+    for atten in attenuators:
+        atten.set_atten(0, strict=False)
+    # Start ping traffic
+    dut_ip = dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+    # Measure starting RSSI
+    ping_future = get_ping_stats_nb(src_device=ping_server,
+                                    dest_address=dut_ip,
+                                    ping_duration=1.5,
+                                    ping_interval=0.02,
+                                    ping_size=64)
+    current_rssi = get_connected_rssi(dut,
+                                      num_measurements=4,
+                                      polling_frequency=0.25,
+                                      first_measurement_delay=0.5,
+                                      disconnect_warning=1,
+                                      ignore_samples=1)
+    current_rssi = current_rssi['signal_poll_rssi']['mean']
+    ping_future.result()
+    target_atten = 0
+    logging.debug("RSSI @ {0:.2f}dB attenuation = {1:.2f}".format(
+        target_atten, current_rssi))
+    within_range = 0
+    for idx in range(20):
+        atten_delta = max(min(current_rssi - target_rssi, 20), -20)
+        target_atten = int((target_atten + atten_delta) * 4) / 4
+        if target_atten < 0:
+            return 0
+        if target_atten > attenuators[0].get_max_atten():
+            return attenuators[0].get_max_atten()
+        for atten in attenuators:
+            atten.set_atten(target_atten, strict=False)
+        ping_future = get_ping_stats_nb(src_device=ping_server,
+                                        dest_address=dut_ip,
+                                        ping_duration=1.5,
+                                        ping_interval=0.02,
+                                        ping_size=64)
+        current_rssi = get_connected_rssi(dut,
+                                          num_measurements=4,
+                                          polling_frequency=0.25,
+                                          first_measurement_delay=0.5,
+                                          disconnect_warning=1,
+                                          ignore_samples=1)
+        current_rssi = current_rssi['signal_poll_rssi']['mean']
+        ping_future.result()
+        logging.info("RSSI @ {0:.2f}dB attenuation = {1:.2f}".format(
+            target_atten, current_rssi))
+        if abs(current_rssi - target_rssi) < 1:
+            if within_range:
+                logging.info(
+                    'Reached RSSI: {0:.2f}. Target RSSI: {1:.2f}.'
+                    'Attenuation: {2:.2f}, Iterations = {3:.2f}'.format(
+                        current_rssi, target_rssi, target_atten, idx))
+                return target_atten
+            else:
+                within_range = True
+        else:
+            within_range = False
+    return target_atten
+
+
+def get_current_atten_dut_chain_map(attenuators, dut, ping_server):
+    """Function to detect mapping between attenuator ports and DUT chains.
+
+    This function detects the mapping between attenuator ports and DUT chains
+    in cases where DUT chains are connected to only one attenuator port. The
+    function assumes the DUT is already connected to a wifi network. The
+    function starts by measuring per chain RSSI at 0 attenuation, then
+    attenuates one port at a time looking for the chain that reports a lower
+    RSSI.
+
+    Args:
+        attenuators: list of attenuator ports
+        dut: android device object assumed connected to a wifi network.
+        ping_server: ssh connection object to ping server
+    Returns:
+        chain_map: list of dut chains, one entry per attenuator port
+    """
+    # Set attenuator to 0 dB
+    for atten in attenuators:
+        atten.set_atten(0, strict=False)
+    # Start ping traffic
+    dut_ip = dut.droid.connectivityGetIPv4Addresses('wlan0')[0]
+    ping_future = get_ping_stats_nb(ping_server, dut_ip, 11, 0.02, 64)
+    # Measure starting RSSI
+    base_rssi = get_connected_rssi(dut, 4, 0.25, 1)
+    chain0_base_rssi = base_rssi['chain_0_rssi']['mean']
+    chain1_base_rssi = base_rssi['chain_1_rssi']['mean']
+    if chain0_base_rssi < -70 or chain1_base_rssi < -70:
+        logging.warning('RSSI might be too low to get reliable chain map.')
+    # Compile chain map by attenuating one path at a time and seeing which
+    # chain's RSSI degrades
+    chain_map = []
+    for test_atten in attenuators:
+        # Set one attenuator to 30 dB down
+        test_atten.set_atten(30, strict=False)
+        # Get new RSSI
+        test_rssi = get_connected_rssi(dut, 4, 0.25, 1)
+        # Assign attenuator to path that has lower RSSI
+        if chain0_base_rssi > -70 and chain0_base_rssi - test_rssi[
+                'chain_0_rssi']['mean'] > 10:
+            chain_map.append('DUT-Chain-0')
+        elif chain1_base_rssi > -70 and chain1_base_rssi - test_rssi[
+                'chain_1_rssi']['mean'] > 10:
+            chain_map.append('DUT-Chain-1')
+        else:
+            chain_map.append(None)
+        # Reset attenuator to 0
+        test_atten.set_atten(0, strict=False)
+    ping_future.result()
+    logging.debug('Chain Map: {}'.format(chain_map))
+    return chain_map
+
+
+def get_full_rf_connection_map(attenuators, dut, ping_server, networks):
+    """Function to detect per-network connections between attenuator and DUT.
+
+    This function detects the mapping between attenuator ports and DUT chains
+    on all networks in its arguments. The function connects the DUT to each
+    network then calls get_current_atten_dut_chain_map to get the connection
+    map on the current network. The function outputs the results in two formats
+    to enable easy access when users are interested in indexing by network or
+    attenuator port.
+
+    Args:
+        attenuators: list of attenuator ports
+        dut: android device object assumed connected to a wifi network.
+        ping_server: ssh connection object to ping server
+        networks: dict of network IDs and configs
+    Returns:
+        rf_map_by_network: dict of RF connections indexed by network.
+        rf_map_by_atten: list of RF connections indexed by attenuator
+    """
+    for atten in attenuators:
+        atten.set_atten(0, strict=False)
+
+    rf_map_by_network = collections.OrderedDict()
+    rf_map_by_atten = [[] for atten in attenuators]
+    for net_id, net_config in networks.items():
+        wutils.reset_wifi(dut)
+        wutils.wifi_connect(dut,
+                            net_config,
+                            num_of_tries=1,
+                            assert_on_fail=False,
+                            check_connectivity=False)
+        rf_map_by_network[net_id] = get_current_atten_dut_chain_map(
+            attenuators, dut, ping_server)
+        for idx, chain in enumerate(rf_map_by_network[net_id]):
+            if chain:
+                rf_map_by_atten[idx].append({
+                    "network": net_id,
+                    "dut_chain": chain
+                })
+    logging.debug("RF Map (by Network): {}".format(rf_map_by_network))
+    logging.debug("RF Map (by Atten): {}".format(rf_map_by_atten))
+
+    return rf_map_by_network, rf_map_by_atten
+
+
+# Miscellaneous Wifi Utilities
+def extract_sub_dict(full_dict, fields):
+    sub_dict = collections.OrderedDict(
+        (field, full_dict[field]) for field in fields)
+    return sub_dict
+
+
+def validate_network(dut, ssid):
+    """Check that DUT has a valid internet connection through expected SSID
+
+    Args:
+        dut: android device of interest
+        ssid: expected ssid
+    """
+    current_network = dut.droid.wifiGetConnectionInfo()
+    try:
+        connected = wutils.validate_connection(dut) is not None
+    except:
+        connected = False
+    if connected and current_network['SSID'] == ssid:
+        return True
+    else:
+        return False
+
+
+def get_server_address(ssh_connection, dut_ip, subnet_mask):
+    """Get server address on a specific subnet,
+
+    This function retrieves the LAN IP of a remote machine used in testing,
+    i.e., it returns the server's IP belonging to the same LAN as the DUT.
+
+    Args:
+        ssh_connection: object representing server for which we want an ip
+        dut_ip: string in ip address format, i.e., xxx.xxx.xxx.xxx, specifying
+        the DUT LAN IP we wish to connect to
+        subnet_mask: string representing subnet mask
+    """
+    subnet_mask = subnet_mask.split('.')
+    dut_subnet = [
+        int(dut) & int(subnet)
+        for dut, subnet in zip(dut_ip.split('.'), subnet_mask)
+    ]
+    ifconfig_out = ssh_connection.run('ifconfig').stdout
+    ip_list = re.findall('inet (?:addr:)?(\d+.\d+.\d+.\d+)', ifconfig_out)
+    for current_ip in ip_list:
+        current_subnet = [
+            int(ip) & int(subnet)
+            for ip, subnet in zip(current_ip.split('.'), subnet_mask)
+        ]
+        if current_subnet == dut_subnet:
+            return current_ip
+    logging.error('No IP address found in requested subnet')
+
+
+def get_dut_temperature(dut):
+    """Function to get dut temperature.
+
+    The function fetches and returns the reading from the temperature sensor
+    used for skin temperature and thermal throttling.
+
+    Args:
+        dut: AndroidDevice of interest
+    Returns:
+        temperature: device temperature. 0 if temperature could not be read
+    """
+    candidate_zones = [
+        'skin-therm', 'sdm-therm-monitor', 'sdm-therm-adc', 'back_therm'
+    ]
+    for zone in candidate_zones:
+        try:
+            temperature = int(
+                dut.adb.shell(
+                    'cat /sys/class/thermal/tz-by-name/{}/temp'.format(zone)))
+            break
+        except ValueError:
+            temperature = 0
+    if temperature == 0:
+        logging.debug('Could not check DUT temperature.')
+    elif temperature > 100:
+        temperature = temperature / 1000
+    return temperature
+
+
+def wait_for_dut_cooldown(dut, target_temp=50, timeout=300):
+    """Function to wait for a DUT to cool down.
+
+    Args:
+        dut: AndroidDevice of interest
+        target_temp: target cooldown temperature
+        timeout: maxt time to wait for cooldown
+    """
+    start_time = time.time()
+    while time.time() - start_time < timeout:
+        temperature = get_dut_temperature(dut)
+        if temperature < target_temp:
+            break
+        time.sleep(SHORT_SLEEP)
+    elapsed_time = time.time() - start_time
+    logging.debug("DUT Final Temperature: {}C. Cooldown duration: {}".format(
+        temperature, elapsed_time))
+
+
+def health_check(dut, batt_thresh=5, temp_threshold=53, cooldown=1):
+    """Function to check health status of a DUT.
+
+    The function checks both battery levels and temperature to avoid DUT
+    powering off during the test.
+
+    Args:
+        dut: AndroidDevice of interest
+        batt_thresh: battery level threshold
+        temp_threshold: temperature threshold
+        cooldown: flag to wait for DUT to cool down when overheating
+    Returns:
+        health_check: boolean confirming device is healthy
+    """
+    health_check = True
+    battery_level = utils.get_battery_level(dut)
+    if battery_level < batt_thresh:
+        logging.warning("Battery level low ({}%)".format(battery_level))
+        health_check = False
+    else:
+        logging.debug("Battery level = {}%".format(battery_level))
+
+    temperature = get_dut_temperature(dut)
+    if temperature > temp_threshold:
+        if cooldown:
+            logging.warning(
+                "Waiting for DUT to cooldown. ({} C)".format(temperature))
+            wait_for_dut_cooldown(dut, target_temp=temp_threshold - 5)
+        else:
+            logging.warning("DUT Overheating ({} C)".format(temperature))
+            health_check = False
+    else:
+        logging.debug("DUT Temperature = {} C".format(temperature))
+    return health_check
+
+
+def get_sw_signature(dut):
+    """Function that checks the signature for wifi firmware and config files.
+
+    Returns:
+        bdf_signature: signature consisting of last three digits of bdf cksums
+        fw_signature: floating point firmware version, i.e., major.minor
+    """
+    bdf_output = dut.adb.shell('cksum /vendor/firmware/bdwlan*')
+    logging.debug('BDF Checksum output: {}'.format(bdf_output))
+    bdf_signature = sum(
+        [int(line.split(' ')[0]) for line in bdf_output.splitlines()]) % 1000
+
+    fw_output = dut.adb.shell('halutil -logger -get fw')
+    logging.debug('Firmware version output: {}'.format(fw_output))
+    fw_version = re.search(FW_REGEX, fw_output).group('firmware')
+    fw_signature = fw_version.split('.')[-3:-1]
+    fw_signature = float('.'.join(fw_signature))
+    serial_hash = int(hashlib.md5(dut.serial.encode()).hexdigest(), 16) % 1000
+    return {
+        'bdf_signature': bdf_signature,
+        'fw_signature': fw_signature,
+        'serial_hash': serial_hash
+    }
+
+
+def push_bdf(dut, bdf_file):
+    """Function to push Wifi BDF files
+
+    This function checks for existing wifi bdf files and over writes them all,
+    for simplicity, with the bdf file provided in the arguments. The dut is
+    rebooted for the bdf file to take effect
+
+    Args:
+        dut: dut to push bdf file to
+        bdf_file: path to bdf_file to push
+    """
+    bdf_files_list = dut.adb.shell('ls /vendor/firmware/bdwlan*').splitlines()
+    for dst_file in bdf_files_list:
+        dut.push_system_file(bdf_file, dst_file)
+    dut.reboot()
+
+
+def push_firmware(dut, wlanmdsp_file, datamsc_file):
+    """Function to push Wifi firmware files
+
+    Args:
+        dut: dut to push bdf file to
+        wlanmdsp_file: path to wlanmdsp.mbn file
+        datamsc_file: path to Data.msc file
+    """
+    dut.push_system_file(wlanmdsp_file, '/vendor/firmware/wlanmdsp.mbn')
+    dut.push_system_file(datamsc_file, '/vendor/firmware/Data.msc')
+    dut.reboot()
+
+
+def _set_ini_fields(ini_file_path, ini_field_dict):
+    template_regex = r'^{}=[0-9,.x-]+'
+    with open(ini_file_path, 'r') as f:
+        ini_lines = f.read().splitlines()
+        for idx, line in enumerate(ini_lines):
+            for field_name, field_value in ini_field_dict.items():
+                line_regex = re.compile(template_regex.format(field_name))
+                if re.match(line_regex, line):
+                    ini_lines[idx] = "{}={}".format(field_name, field_value)
+                    print(ini_lines[idx])
+    with open(ini_file_path, 'w') as f:
+        f.write("\n".join(ini_lines) + "\n")
+
+
+def _edit_dut_ini(dut, ini_fields):
+    """Function to edit Wifi ini files."""
+    dut_ini_path = '/vendor/firmware/wlan/qca_cld/WCNSS_qcom_cfg.ini'
+    local_ini_path = os.path.expanduser('~/WCNSS_qcom_cfg.ini')
+    dut.pull_files(dut_ini_path, local_ini_path)
+
+    _set_ini_fields(local_ini_path, ini_fields)
+
+    dut.push_system_file(local_ini_path, dut_ini_path)
+    dut.reboot()
+
+
+def set_ini_single_chain_mode(dut, chain):
+    ini_fields = {
+        'gEnable2x2': 0,
+        'gSetTxChainmask1x1': chain + 1,
+        'gSetRxChainmask1x1': chain + 1,
+        'gDualMacFeatureDisable': 1,
+        'gDot11Mode': 0
+    }
+    _edit_dut_ini(dut, ini_fields)
+
+
+def set_ini_two_chain_mode(dut):
+    ini_fields = {
+        'gEnable2x2': 2,
+        'gSetTxChainmask1x1': 1,
+        'gSetRxChainmask1x1': 1,
+        'gDualMacFeatureDisable': 6,
+        'gDot11Mode': 0
+    }
+    _edit_dut_ini(dut, ini_fields)
+
+
+def set_ini_tx_mode(dut, mode):
+    TX_MODE_DICT = {
+        "Auto": 0,
+        "11n": 4,
+        "11ac": 9,
+        "11abg": 1,
+        "11b": 2,
+        "11g": 3,
+        "11g only": 5,
+        "11n only": 6,
+        "11b only": 7,
+        "11ac only": 8
+    }
+
+    ini_fields = {
+        'gEnable2x2': 2,
+        'gSetTxChainmask1x1': 1,
+        'gSetRxChainmask1x1': 1,
+        'gDualMacFeatureDisable': 6,
+        'gDot11Mode': TX_MODE_DICT[mode]
+    }
+    _edit_dut_ini(dut, ini_fields)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py
new file mode 100644
index 0000000..b1565d2
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_power_test_utils.py
@@ -0,0 +1,321 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 Google, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import time
+from acts import utils
+from acts.libs.proc import job
+from acts.controllers.ap_lib import bridge_interface as bi
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts.controllers.ap_lib import hostapd_security
+from acts.controllers.ap_lib import hostapd_ap_preset
+
+# http://www.secdev.org/projects/scapy/
+# On ubuntu, sudo pip3 install scapy
+import scapy.all as scapy
+
+GET_FROM_PHONE = 'get_from_dut'
+GET_FROM_AP = 'get_from_ap'
+ENABLED_MODULATED_DTIM = 'gEnableModulatedDTIM='
+MAX_MODULATED_DTIM = 'gMaxLIModulatedDTIM='
+
+
+def change_dtim(ad, gEnableModulatedDTIM, gMaxLIModulatedDTIM=10):
+    """Function to change the DTIM setting in the phone.
+
+    Args:
+        ad: the target android device, AndroidDevice object
+        gEnableModulatedDTIM: Modulated DTIM, int
+        gMaxLIModulatedDTIM: Maximum modulated DTIM, int
+    """
+    # First trying to find the ini file with DTIM settings
+    ini_file_phone = ad.adb.shell('ls /vendor/firmware/wlan/*/*.ini')
+    ini_file_local = ini_file_phone.split('/')[-1]
+
+    # Pull the file and change the DTIM to desired value
+    ad.adb.pull('{} {}'.format(ini_file_phone, ini_file_local))
+
+    with open(ini_file_local, 'r') as fin:
+        for line in fin:
+            if ENABLED_MODULATED_DTIM in line:
+                gE_old = line.strip('\n')
+                gEDTIM_old = line.strip(ENABLED_MODULATED_DTIM).strip('\n')
+            if MAX_MODULATED_DTIM in line:
+                gM_old = line.strip('\n')
+                gMDTIM_old = line.strip(MAX_MODULATED_DTIM).strip('\n')
+    fin.close()
+    if int(gEDTIM_old) == gEnableModulatedDTIM and int(
+            gMDTIM_old) == gMaxLIModulatedDTIM:
+        ad.log.info('Current DTIM is already the desired value,'
+                    'no need to reset it')
+        return 0
+
+    gE_new = ENABLED_MODULATED_DTIM + str(gEnableModulatedDTIM)
+    gM_new = MAX_MODULATED_DTIM + str(gMaxLIModulatedDTIM)
+
+    sed_gE = 'sed -i \'s/{}/{}/g\' {}'.format(gE_old, gE_new, ini_file_local)
+    sed_gM = 'sed -i \'s/{}/{}/g\' {}'.format(gM_old, gM_new, ini_file_local)
+    job.run(sed_gE)
+    job.run(sed_gM)
+
+    # Push the file to the phone
+    push_file_to_phone(ad, ini_file_local, ini_file_phone)
+    ad.log.info('DTIM changes checked in and rebooting...')
+    ad.reboot()
+    # Wait for auto-wifi feature to start
+    time.sleep(20)
+    ad.adb.shell('dumpsys battery set level 100')
+    ad.log.info('DTIM updated and device back from reboot')
+    return 1
+
+
+def push_file_to_phone(ad, file_local, file_phone):
+    """Function to push local file to android phone.
+
+    Args:
+        ad: the target android device
+        file_local: the locla file to push
+        file_phone: the file/directory on the phone to be pushed
+    """
+    ad.adb.root()
+    cmd_out = ad.adb.remount()
+    if 'Permission denied' in cmd_out:
+        ad.log.info('Need to disable verity first and reboot')
+        ad.adb.disable_verity()
+        time.sleep(1)
+        ad.reboot()
+        ad.log.info('Verity disabled and device back from reboot')
+        ad.adb.root()
+        ad.adb.remount()
+    time.sleep(1)
+    ad.adb.push('{} {}'.format(file_local, file_phone))
+
+
+def ap_setup(ap, network, bandwidth=80):
+    """Set up the whirlwind AP with provided network info.
+
+    Args:
+        ap: access_point object of the AP
+        network: dict with information of the network, including ssid, password
+                 bssid, channel etc.
+        bandwidth: the operation bandwidth for the AP, default 80MHz
+    Returns:
+        brconfigs: the bridge interface configs
+    """
+    log = logging.getLogger()
+    bss_settings = []
+    ssid = network[wutils.WifiEnums.SSID_KEY]
+    if "password" in network.keys():
+        password = network["password"]
+        security = hostapd_security.Security(
+            security_mode="wpa", password=password)
+    else:
+        security = hostapd_security.Security(security_mode=None, password=None)
+    channel = network["channel"]
+    config = hostapd_ap_preset.create_ap_preset(
+        channel=channel,
+        ssid=ssid,
+        security=security,
+        bss_settings=bss_settings,
+        vht_bandwidth=bandwidth,
+        profile_name='whirlwind',
+        iface_wlan_2g=ap.wlan_2g,
+        iface_wlan_5g=ap.wlan_5g)
+    config_bridge = ap.generate_bridge_configs(channel)
+    brconfigs = bi.BridgeInterfaceConfigs(config_bridge[0], config_bridge[1],
+                                          config_bridge[2])
+    ap.bridge.startup(brconfigs)
+    ap.start_ap(config)
+    log.info("AP started on channel {} with SSID {}".format(channel, ssid))
+    return brconfigs
+
+
+def run_iperf_client_nonblocking(ad, server_host, extra_args=""):
+    """Start iperf client on the device with nohup.
+
+    Return status as true if iperf client start successfully.
+    And data flow information as results.
+
+    Args:
+        ad: the android device under test
+        server_host: Address of the iperf server.
+        extra_args: A string representing extra arguments for iperf client,
+            e.g. "-i 1 -t 30".
+
+    """
+    log = logging.getLogger()
+    ad.adb.shell_nb("nohup >/dev/null 2>&1 sh -c 'iperf3 -c {} {} &'".format(
+        server_host, extra_args))
+    log.info("IPerf client started")
+
+
+def get_wifi_rssi(ad):
+    """Get the RSSI of the device.
+
+    Args:
+        ad: the android device under test
+    Returns:
+        RSSI: the rssi level of the device
+    """
+    RSSI = ad.droid.wifiGetConnectionInfo()['rssi']
+    return RSSI
+
+
+def get_phone_ip(ad):
+    """Get the WiFi IP address of the phone.
+
+    Args:
+        ad: the android device under test
+    Returns:
+        IP: IP address of the phone for WiFi, as a string
+    """
+    IP = ad.droid.connectivityGetIPv4Addresses('wlan0')[0]
+
+    return IP
+
+
+def get_phone_mac(ad):
+    """Get the WiFi MAC address of the phone.
+
+    Args:
+        ad: the android device under test
+    Returns:
+        mac: MAC address of the phone for WiFi, as a string
+    """
+    mac = ad.droid.wifiGetConnectionInfo()["mac_address"]
+
+    return mac
+
+
+def get_phone_ipv6(ad):
+    """Get the WiFi IPV6 address of the phone.
+
+    Args:
+        ad: the android device under test
+    Returns:
+        IPv6: IPv6 address of the phone for WiFi, as a string
+    """
+    IPv6 = ad.droid.connectivityGetLinkLocalIpv6Address('wlan0')[:-6]
+
+    return IPv6
+
+
+def wait_for_dhcp(interface_name):
+    """Wait the DHCP address assigned to desired interface.
+
+    Getting DHCP address takes time and the wait time isn't constant. Utilizing
+    utils.timeout to keep trying until success
+
+    Args:
+        interface_name: desired interface name
+    Returns:
+        ip: ip address of the desired interface name
+    Raise:
+        TimeoutError: After timeout, if no DHCP assigned, raise
+    """
+    log = logging.getLogger()
+    reset_host_interface(interface_name)
+    start_time = time.time()
+    time_limit_seconds = 60
+    ip = '0.0.0.0'
+    while start_time + time_limit_seconds > time.time():
+        ip = scapy.get_if_addr(interface_name)
+        if ip == '0.0.0.0':
+            time.sleep(1)
+        else:
+            log.info(
+                'DHCP address assigned to %s as %s' % (interface_name, ip))
+            return ip
+    raise TimeoutError('Timed out while getting if_addr after %s seconds.' %
+                       time_limit_seconds)
+
+
+def reset_host_interface(intferface_name):
+    """Reset the host interface.
+
+    Args:
+        intferface_name: the desired interface to reset
+    """
+    log = logging.getLogger()
+    intf_down_cmd = 'ifconfig %s down' % intferface_name
+    intf_up_cmd = 'ifconfig %s up' % intferface_name
+    try:
+        job.run(intf_down_cmd)
+        time.sleep(10)
+        job.run(intf_up_cmd)
+        log.info('{} has been reset'.format(intferface_name))
+    except job.Error:
+        raise Exception('No such interface')
+
+
+def bringdown_host_interface(intferface_name):
+    """Reset the host interface.
+
+    Args:
+        intferface_name: the desired interface to reset
+    """
+    log = logging.getLogger()
+    intf_down_cmd = 'ifconfig %s down' % intferface_name
+    try:
+        job.run(intf_down_cmd)
+        time.sleep(2)
+        log.info('{} has been brought down'.format(intferface_name))
+    except job.Error:
+        raise Exception('No such interface')
+
+
+def create_pkt_config(test_class):
+    """Creates the config for generating multicast packets
+
+    Args:
+        test_class: object with all networking paramters
+
+    Returns:
+        Dictionary with the multicast packet config
+    """
+    addr_type = (scapy.IPV6_ADDR_LINKLOCAL
+                 if test_class.ipv6_src_type == 'LINK_LOCAL' else
+                 scapy.IPV6_ADDR_GLOBAL)
+
+    mac_dst = test_class.mac_dst
+    if GET_FROM_PHONE in test_class.mac_dst:
+        mac_dst = get_phone_mac(test_class.dut)
+
+    ipv4_dst = test_class.ipv4_dst
+    if GET_FROM_PHONE in test_class.ipv4_dst:
+        ipv4_dst = get_phone_ip(test_class.dut)
+
+    ipv6_dst = test_class.ipv6_dst
+    if GET_FROM_PHONE in test_class.ipv6_dst:
+        ipv6_dst = get_phone_ipv6(test_class.dut)
+
+    ipv4_gw = test_class.ipv4_gwt
+    if GET_FROM_AP in test_class.ipv4_gwt:
+        ipv4_gw = test_class.access_point.ssh_settings.hostname
+
+    pkt_gen_config = {
+        'interf': test_class.pkt_sender.interface,
+        'subnet_mask': test_class.sub_mask,
+        'src_mac': test_class.mac_src,
+        'dst_mac': mac_dst,
+        'src_ipv4': test_class.ipv4_src,
+        'dst_ipv4': ipv4_dst,
+        'src_ipv6': test_class.ipv6_src,
+        'src_ipv6_type': addr_type,
+        'dst_ipv6': ipv6_dst,
+        'gw_ipv4': ipv4_gw
+    }
+    return pkt_gen_config
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap.py
new file mode 100644
index 0000000..adbceed
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap.py
@@ -0,0 +1,1433 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import fcntl
+import os
+import selenium
+import splinter
+import time
+from acts import logger
+from acts.controllers import access_point
+from acts.controllers.ap_lib import bridge_interface
+from acts.controllers.ap_lib import hostapd_security
+from acts.controllers.ap_lib import hostapd_ap_preset
+
+BROWSER_WAIT_SHORT = 1
+BROWSER_WAIT_MED = 3
+BROWSER_WAIT_LONG = 30
+BROWSER_WAIT_EXTRA_LONG = 60
+
+
+def create(configs):
+    """Factory method for retail AP class.
+
+    Args:
+        configs: list of dicts containing ap settings. ap settings must contain
+        the following: brand, model, ip_address, username and password
+    """
+    SUPPORTED_APS = {
+        ("Netgear", "R7000"): "NetgearR7000AP",
+        ("Netgear", "R7000NA"): "NetgearR7000NAAP",
+        ("Netgear", "R7500"): "NetgearR7500AP",
+        ("Netgear", "R7800"): "NetgearR7800AP",
+        ("Netgear", "R8000"): "NetgearR8000AP",
+        ("Netgear", "R8500"): "NetgearR8500AP",
+        ("Netgear", "RAX"): "NetgearRAXAP",
+        ("Google", "Wifi"): "GoogleWifiAP"
+    }
+    objs = []
+    for config in configs:
+        try:
+            ap_class_name = SUPPORTED_APS[(config["brand"], config["model"])]
+            ap_class = globals()[ap_class_name]
+        except KeyError:
+            raise KeyError("Invalid retail AP brand and model combination.")
+        objs.append(ap_class(config))
+    return objs
+
+
+def detroy(objs):
+    for obj in objs:
+        obj.teardown()
+
+
+class BlockingBrowser(splinter.driver.webdriver.chrome.WebDriver):
+    """Class that implements a blocking browser session on top of selenium.
+
+    The class inherits from and builds upon splinter/selenium's webdriver class
+    and makes sure that only one such webdriver is active on a machine at any
+    single time. The class ensures single session operation using a lock file.
+    The class is to be used within context managers (e.g. with statements) to
+    ensure locks are always properly released.
+    """
+    def __init__(self, headless, timeout):
+        """Constructor for BlockingBrowser class.
+
+        Args:
+            headless: boolean to control visible/headless browser operation
+            timeout: maximum time allowed to launch browser
+        """
+        self.log = logger.create_tagged_trace_logger("ChromeDriver")
+        self.chrome_options = splinter.driver.webdriver.chrome.Options()
+        self.chrome_options.add_argument("--no-proxy-server")
+        self.chrome_options.add_argument("--no-sandbox")
+        self.chrome_options.add_argument("--allow-running-insecure-content")
+        self.chrome_options.add_argument("--ignore-certificate-errors")
+        self.chrome_capabilities = selenium.webdriver.common.desired_capabilities.DesiredCapabilities.CHROME.copy(
+        )
+        self.chrome_capabilities["acceptSslCerts"] = True
+        self.chrome_capabilities["acceptInsecureCerts"] = True
+        if headless:
+            self.chrome_options.add_argument("--headless")
+            self.chrome_options.add_argument("--disable-gpu")
+        self.lock_file_path = "/usr/local/bin/chromedriver"
+        self.timeout = timeout
+
+    def __enter__(self):
+        """Entry context manager for BlockingBrowser.
+
+        The enter context manager for BlockingBrowser attempts to lock the
+        browser file. If successful, it launches and returns a chromedriver
+        session. If an exception occurs while starting the browser, the lock
+        file is released.
+        """
+        self.lock_file = open(self.lock_file_path, "r")
+        start_time = time.time()
+        while time.time() < start_time + self.timeout:
+            try:
+                fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
+            except BlockingIOError:
+                time.sleep(BROWSER_WAIT_SHORT)
+                continue
+            try:
+                self.driver = selenium.webdriver.Chrome(
+                    options=self.chrome_options,
+                    desired_capabilities=self.chrome_capabilities)
+                self.element_class = splinter.driver.webdriver.WebDriverElement
+                self._cookie_manager = splinter.driver.webdriver.cookie_manager.CookieManager(
+                    self.driver)
+                super(splinter.driver.webdriver.chrome.WebDriver,
+                      self).__init__(2)
+                return super(BlockingBrowser, self).__enter__()
+            except:
+                fcntl.flock(self.lock_file, fcntl.LOCK_UN)
+                self.lock_file.close()
+                raise RuntimeError("Error starting browser. "
+                                   "Releasing lock file.")
+        raise TimeoutError("Could not start chrome browser in time.")
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        """Exit context manager for BlockingBrowser.
+
+        The exit context manager simply calls the parent class exit and
+        releases the lock file.
+        """
+        try:
+            super(BlockingBrowser, self).__exit__(exc_type, exc_value,
+                                                  traceback)
+        except:
+            raise RuntimeError("Failed to quit browser. Releasing lock file.")
+        finally:
+            fcntl.flock(self.lock_file, fcntl.LOCK_UN)
+            self.lock_file.close()
+
+    def restart(self):
+        """Method to restart browser session without releasing lock file."""
+        self.quit()
+        self.__enter__()
+
+    def visit_persistent(self,
+                         url,
+                         page_load_timeout,
+                         num_tries,
+                         backup_url="about:blank",
+                         check_for_element=None):
+        """Method to visit webpages and retry upon failure.
+
+        The function visits a web page and checks the the resulting URL matches
+        the intended URL, i.e. no redirects have happened
+
+        Args:
+            url: the intended url
+            page_load_timeout: timeout for page visits
+            num_tries: number of tries before url is declared unreachable
+            backup_url: url to visit if first url is not reachable. This can be
+            used to simply refresh the browser and try again or to re-login to
+            the AP
+            check_for_element: element id to check for existence on page
+        """
+        self.driver.set_page_load_timeout(page_load_timeout)
+        for idx in range(num_tries):
+            try:
+                self.visit(url)
+            except:
+                self.restart()
+
+            page_reached = self.url.split("/")[-1] == url.split("/")[-1]
+            if check_for_element:
+                time.sleep(BROWSER_WAIT_MED)
+                element = self.find_by_id(check_for_element)
+                if not element:
+                    page_reached = 0
+            if page_reached:
+                break
+            else:
+                try:
+                    self.visit(backup_url)
+                except:
+                    self.restart()
+
+            if idx == num_tries - 1:
+                self.log.error("URL unreachable. Current URL: {}".format(
+                    self.url))
+                raise RuntimeError("URL unreachable.")
+
+
+class WifiRetailAP(object):
+    """Base class implementation for retail ap.
+
+    Base class provides functions whose implementation is shared by all aps.
+    If some functions such as set_power not supported by ap, checks will raise
+    exceptions.
+    """
+    def __init__(self, ap_settings):
+        self.ap_settings = ap_settings.copy()
+        self.log = logger.create_tagged_trace_logger("AccessPoint|{}".format(
+            self._get_control_ip_address()))
+        # Lock AP
+        if self.ap_settings.get('lock_ap', 0):
+            self.lock_timeout = self.ap_settings.get('lock_timeout', 3600)
+            self._lock_ap()
+
+    def teardown(self):
+        """Function to perform destroy operations."""
+        self._unlock_ap()
+
+    def reset(self):
+        """Function that resets AP.
+
+        Function implementation is AP dependent and intended to perform any
+        necessary reset operations as part of controller destroy.
+        """
+        pass
+
+    def read_ap_settings(self):
+        """Function that reads current ap settings.
+
+        Function implementation is AP dependent and thus base class raises exception
+        if function not implemented in child class.
+        """
+        raise NotImplementedError
+
+    def validate_ap_settings(self):
+        """Function to validate ap settings.
+
+        This function compares the actual ap settings read from the web GUI
+        with the assumed settings saved in the AP object. When called after AP
+        configuration, this method helps ensure that our configuration was
+        successful.
+        Note: Calling this function updates the stored ap_settings
+
+        Raises:
+            ValueError: If read AP settings do not match stored settings.
+        """
+        assumed_ap_settings = self.ap_settings.copy()
+        actual_ap_settings = self.read_ap_settings()
+        if assumed_ap_settings != actual_ap_settings:
+            self.log.warning(
+                "Discrepancy in AP settings. Some settings may have been overwritten."
+            )
+
+    def configure_ap(self, **config_flags):
+        """Function that configures ap based on values of ap_settings.
+
+        Function implementation is AP dependent and thus base class raises exception
+        if function not implemented in child class.
+
+        Args:
+            config_flags: optional configuration flags
+        """
+        raise NotImplementedError
+
+    def set_region(self, region):
+        """Function that sets AP region.
+
+        This function sets the region for the AP. Note that this may overwrite
+        channel and bandwidth settings in cases where the new region does not
+        support the current wireless configuration.
+
+        Args:
+            region: string indicating AP region
+        """
+        self.log.warning("Updating region may overwrite wireless settings.")
+        setting_to_update = {"region": region}
+        self.update_ap_settings(setting_to_update)
+
+    def set_radio_on_off(self, network, status):
+        """Function that turns the radio on or off.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            status: boolean indicating on or off (0: off, 1: on)
+        """
+        setting_to_update = {"status_{}".format(network): int(status)}
+        self.update_ap_settings(setting_to_update)
+
+    def set_ssid(self, network, ssid):
+        """Function that sets network SSID.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            ssid: string containing ssid
+        """
+        setting_to_update = {"ssid_{}".format(network): str(ssid)}
+        self.update_ap_settings(setting_to_update)
+
+    def set_channel(self, network, channel):
+        """Function that sets network channel.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            channel: string or int containing channel
+        """
+        setting_to_update = {"channel_{}".format(network): str(channel)}
+        self.update_ap_settings(setting_to_update)
+
+    def set_bandwidth(self, network, bandwidth):
+        """Function that sets network bandwidth/mode.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            bandwidth: string containing mode, e.g. 11g, VHT20, VHT40, VHT80.
+        """
+        setting_to_update = {"bandwidth_{}".format(network): str(bandwidth)}
+        self.update_ap_settings(setting_to_update)
+
+    def set_power(self, network, power):
+        """Function that sets network transmit power.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            power: string containing power level, e.g., 25%, 100%
+        """
+        setting_to_update = {"power_{}".format(network): str(power)}
+        self.update_ap_settings(setting_to_update)
+
+    def set_security(self, network, security_type, *password):
+        """Function that sets network security setting and password.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            security: string containing security setting, e.g., WPA2-PSK
+            password: optional argument containing password
+        """
+        if (len(password) == 1) and (type(password[0]) == str):
+            setting_to_update = {
+                "security_type_{}".format(network): str(security_type),
+                "password_{}".format(network): str(password[0])
+            }
+        else:
+            setting_to_update = {
+                "security_type_{}".format(network): str(security_type)
+            }
+        self.update_ap_settings(setting_to_update)
+
+    def set_rate(self):
+        """Function that configures rate used by AP.
+
+        Function implementation is not supported by most APs and thus base
+        class raises exception if function not implemented in child class.
+        """
+        raise NotImplementedError
+
+    def update_ap_settings(self, dict_settings={}, **named_settings):
+        """Function to update settings of existing AP.
+
+        Function copies arguments into ap_settings and calls configure_retail_ap
+        to apply them.
+
+        Args:
+            *dict_settings accepts single dictionary of settings to update
+            **named_settings accepts named settings to update
+            Note: dict and named_settings cannot contain the same settings.
+        """
+        settings_to_update = dict(dict_settings, **named_settings)
+        if len(settings_to_update) != len(dict_settings) + len(named_settings):
+            raise KeyError("The following keys were passed twice: {}".format(
+                (set(dict_settings.keys()).intersection(
+                    set(named_settings.keys())))))
+        if not set(settings_to_update.keys()).issubset(
+                set(self.ap_settings.keys())):
+            raise KeyError(
+                "The following settings are invalid for this AP: {}".format(
+                    set(settings_to_update.keys()).difference(
+                        set(self.ap_settings.keys()))))
+
+        updates_requested = False
+        status_toggle_flag = False
+        for setting, value in settings_to_update.items():
+            if self.ap_settings[setting] != value:
+                self.ap_settings[setting] = value
+                if "status" in setting:
+                    status_toggle_flag = True
+                updates_requested = True
+
+        if updates_requested:
+            self.configure_ap(status_toggled=status_toggle_flag)
+
+    def band_lookup_by_channel(self, channel):
+        """Function that gives band name by channel number.
+
+        Args:
+            channel: channel number to lookup
+        Returns:
+            band: name of band which this channel belongs to on this ap
+        """
+        for key, value in self.channel_band_map.items():
+            if channel in value:
+                return key
+        raise ValueError("Invalid channel passed in argument.")
+
+    def _get_control_ip_address(self):
+        """Function to get AP's Control Interface IP address."""
+        if "ssh_config" in self.ap_settings.keys():
+            return self.ap_settings["ssh_config"]["host"]
+        else:
+            return self.ap_settings["ip_address"]
+
+    def _lock_ap(self):
+        """Function to lock the ap while tests are running."""
+        self.lock_file_path = "/tmp/{}_{}_{}.lock".format(
+            self.ap_settings['brand'], self.ap_settings['model'],
+            self._get_control_ip_address())
+        if not os.path.exists(self.lock_file_path):
+            with open(self.lock_file_path, 'w'):
+                pass
+        self.lock_file = open(self.lock_file_path, "r")
+        start_time = time.time()
+        self.log.info('Trying to acquire AP lock.')
+        while time.time() < start_time + self.lock_timeout:
+            try:
+                fcntl.flock(self.lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
+            except BlockingIOError:
+                time.sleep(BROWSER_WAIT_SHORT)
+                continue
+            self.log.info('AP lock acquired.')
+            return
+        raise RuntimeError("Could not lock AP in time.")
+
+    def _unlock_ap(self):
+        """Function to unlock the AP when tests are done."""
+        self.log.info('Releasing AP lock.')
+        if hasattr(self, "lock_file"):
+            fcntl.flock(self.lock_file, fcntl.LOCK_UN)
+            self.lock_file.close()
+
+
+class NetgearR7000AP(WifiRetailAP):
+    """Class that implements Netgear R7000 AP."""
+    def __init__(self, ap_settings):
+        super().__init__(ap_settings)
+        self.init_gui_data()
+        # Read and update AP settings
+        self.read_ap_settings()
+        if not set(ap_settings.items()).issubset(self.ap_settings.items()):
+            self.update_ap_settings(ap_settings)
+
+    def init_gui_data(self):
+        """Function to initialize data used while interacting with web GUI"""
+        self.config_page = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/WLG_wireless_dual_band_r10.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.config_page_nologin = (
+            "{protocol}://{ip_address}:{port}/"
+            "WLG_wireless_dual_band_r10.htm").format(
+                protocol=self.ap_settings["protocol"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.config_page_advanced = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/WLG_adv_dual_band2.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.networks = ["2G", "5G_1"]
+        self.channel_band_map = {
+            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+            "5G_1": [
+                36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120,
+                124, 128, 132, 136, 140, 149, 153, 157, 161, 165
+            ]
+        }
+        self.region_map = {
+            "1": "Africa",
+            "2": "Asia",
+            "3": "Australia",
+            "4": "Canada",
+            "5": "Europe",
+            "6": "Israel",
+            "7": "Japan",
+            "8": "Korea",
+            "9": "Mexico",
+            "10": "South America",
+            "11": "United States",
+            "12": "Middle East(Algeria/Syria/Yemen)",
+            "14": "Russia",
+            "16": "China",
+            "17": "India",
+            "18": "Malaysia",
+            "19": "Middle East(Iran/Labanon/Qatar)",
+            "20": "Middle East(Turkey/Egypt/Tunisia/Kuwait)",
+            "21": "Middle East(Saudi Arabia)",
+            "22": "Middle East(United Arab Emirates)",
+            "23": "Singapore",
+            "24": "Taiwan"
+        }
+        self.config_page_fields = {
+            "region": "WRegion",
+            ("2G", "status"): "enable_ap",
+            ("5G_1", "status"): "enable_ap_an",
+            ("2G", "ssid"): "ssid",
+            ("5G_1", "ssid"): "ssid_an",
+            ("2G", "channel"): "w_channel",
+            ("5G_1", "channel"): "w_channel_an",
+            ("2G", "bandwidth"): "opmode",
+            ("5G_1", "bandwidth"): "opmode_an",
+            ("2G", "power"): "enable_tpc",
+            ("5G_1", "power"): "enable_tpc_an",
+            ("2G", "security_type"): "security_type",
+            ("5G_1", "security_type"): "security_type_an",
+            ("2G", "password"): "passphrase",
+            ("5G_1", "password"): "passphrase_an"
+        }
+        self.bw_mode_values = {
+            "g and b": "11g",
+            "145Mbps": "VHT20",
+            "300Mbps": "VHT40",
+            "HT80": "VHT80"
+        }
+        self.power_mode_values = {
+            "1": "100%",
+            "2": "75%",
+            "3": "50%",
+            "4": "25%"
+        }
+        self.bw_mode_text = {
+            "11g": "Up to 54 Mbps",
+            "VHT20": "Up to 289 Mbps",
+            "VHT40": "Up to 600 Mbps",
+            "VHT80": "Up to 1300 Mbps"
+        }
+
+    def read_ap_settings(self):
+        """Function to read ap settings."""
+        with BlockingBrowser(self.ap_settings["headless_browser"],
+                             900) as browser:
+            # Visit URL
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+
+            for key, value in self.config_page_fields.items():
+                if "status" in key:
+                    browser.visit_persistent(self.config_page_advanced,
+                                             BROWSER_WAIT_MED, 10)
+                    config_item = browser.find_by_name(value)
+                    self.ap_settings["{}_{}".format(key[1], key[0])] = int(
+                        config_item.first.checked)
+                    browser.visit_persistent(self.config_page,
+                                             BROWSER_WAIT_MED, 10)
+                else:
+                    config_item = browser.find_by_name(value)
+                    if "bandwidth" in key:
+                        self.ap_settings["{}_{}".format(
+                            key[1], key[0])] = self.bw_mode_values[
+                                config_item.first.value]
+                    elif "power" in key:
+                        self.ap_settings["{}_{}".format(
+                            key[1], key[0])] = self.power_mode_values[
+                                config_item.first.value]
+                    elif "region" in key:
+                        self.ap_settings["region"] = self.region_map[
+                            config_item.first.value]
+                    elif "security_type" in key:
+                        for item in config_item:
+                            if item.checked:
+                                self.ap_settings["{}_{}".format(
+                                    key[1], key[0])] = item.value
+                    else:
+                        config_item = browser.find_by_name(value)
+                        self.ap_settings["{}_{}".format(
+                            key[1], key[0])] = config_item.first.value
+        return self.ap_settings.copy()
+
+    def configure_ap(self, **config_flags):
+        """Function to configure ap wireless settings."""
+        # Turn radios on or off
+        if config_flags["status_toggled"]:
+            self.configure_radio_on_off()
+        # Configure radios
+        with BlockingBrowser(self.ap_settings["headless_browser"],
+                             900) as browser:
+            # Visit URL
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+            browser.visit_persistent(self.config_page_nologin,
+                                     BROWSER_WAIT_MED, 10, self.config_page)
+
+            # Update region, and power/bandwidth for each network
+            config_item = browser.find_by_name(
+                self.config_page_fields["region"]).first
+            config_item.select_by_text(self.ap_settings["region"])
+            for key, value in self.config_page_fields.items():
+                if "power" in key:
+                    config_item = browser.find_by_name(value).first
+                    config_item.select_by_text(self.ap_settings["{}_{}".format(
+                        key[1], key[0])])
+                elif "bandwidth" in key:
+                    config_item = browser.find_by_name(value).first
+                    try:
+                        config_item.select_by_text(
+                            self.bw_mode_text[self.ap_settings["{}_{}".format(
+                                key[1], key[0])]])
+                    except AttributeError:
+                        self.log.warning(
+                            "Cannot select bandwidth. Keeping AP default.")
+
+            # Update security settings (passwords updated only if applicable)
+            for key, value in self.config_page_fields.items():
+                if "security_type" in key:
+                    browser.choose(
+                        value, self.ap_settings["{}_{}".format(key[1],
+                                                               key[0])])
+                    if self.ap_settings["{}_{}".format(key[1],
+                                                       key[0])] == "WPA2-PSK":
+                        config_item = browser.find_by_name(
+                            self.config_page_fields[(key[0],
+                                                     "password")]).first
+                        config_item.fill(self.ap_settings["{}_{}".format(
+                            "password", key[0])])
+
+            # Update SSID and channel for each network
+            # NOTE: Update ordering done as such as workaround for R8000
+            # wherein channel and SSID get overwritten when some other
+            # variables are changed. However, region does have to be set before
+            # channel in all cases.
+            for key, value in self.config_page_fields.items():
+                if "ssid" in key:
+                    config_item = browser.find_by_name(value).first
+                    config_item.fill(self.ap_settings["{}_{}".format(
+                        key[1], key[0])])
+                elif "channel" in key:
+                    config_item = browser.find_by_name(value).first
+                    try:
+                        config_item.select(self.ap_settings["{}_{}".format(
+                            key[1], key[0])])
+                        time.sleep(BROWSER_WAIT_SHORT)
+                    except AttributeError:
+                        self.log.warning(
+                            "Cannot select channel. Keeping AP default.")
+                    try:
+                        alert = browser.get_alert()
+                        alert.accept()
+                    except:
+                        pass
+
+            time.sleep(BROWSER_WAIT_SHORT)
+            browser.find_by_name("Apply").first.click()
+            time.sleep(BROWSER_WAIT_SHORT)
+            try:
+                alert = browser.get_alert()
+                alert.accept()
+                time.sleep(BROWSER_WAIT_SHORT)
+            except:
+                time.sleep(BROWSER_WAIT_SHORT)
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
+                                     10)
+
+    def configure_radio_on_off(self):
+        """Helper configuration function to turn radios on/off."""
+        with BlockingBrowser(self.ap_settings["headless_browser"],
+                             900) as browser:
+            # Visit URL
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+            browser.visit_persistent(self.config_page_advanced,
+                                     BROWSER_WAIT_MED, 10)
+
+            # Turn radios on or off
+            for key, value in self.config_page_fields.items():
+                if "status" in key:
+                    config_item = browser.find_by_name(value).first
+                    if self.ap_settings["{}_{}".format(key[1], key[0])]:
+                        config_item.check()
+                    else:
+                        config_item.uncheck()
+
+            time.sleep(BROWSER_WAIT_SHORT)
+            browser.find_by_name("Apply").first.click()
+            time.sleep(BROWSER_WAIT_EXTRA_LONG)
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
+                                     10)
+
+
+class NetgearR7000NAAP(NetgearR7000AP):
+    """Class that implements Netgear R7000 NA AP."""
+    def init_gui_data(self):
+        """Function to initialize data used while interacting with web GUI"""
+        super.init_gui_data()
+        self.region_map["11"] = "North America"
+
+
+class NetgearR7500AP(WifiRetailAP):
+    """Class that implements Netgear R7500 AP."""
+    def __init__(self, ap_settings):
+        super().__init__(ap_settings)
+        self.init_gui_data()
+        # Read and update AP settings
+        self.read_ap_settings()
+        if not set(ap_settings.items()).issubset(self.ap_settings.items()):
+            self.update_ap_settings(ap_settings)
+
+    def init_gui_data(self):
+        """Function to initialize data used while interacting with web GUI"""
+        self.config_page = ("{protocol}://{username}:{password}@"
+                            "{ip_address}:{port}/index.htm").format(
+                                protocol=self.ap_settings["protocol"],
+                                username=self.ap_settings["admin_username"],
+                                password=self.ap_settings["admin_password"],
+                                ip_address=self.ap_settings["ip_address"],
+                                port=self.ap_settings["port"])
+        self.config_page_advanced = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/adv_index.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.networks = ["2G", "5G_1"]
+        self.channel_band_map = {
+            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+            "5G_1": [
+                36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120,
+                124, 128, 132, 136, 140, 149, 153, 157, 161, 165
+            ]
+        }
+        self.config_page_fields = {
+            "region": "WRegion",
+            ("2G", "status"): "enable_ap",
+            ("5G_1", "status"): "enable_ap_an",
+            ("2G", "ssid"): "ssid",
+            ("5G_1", "ssid"): "ssid_an",
+            ("2G", "channel"): "w_channel",
+            ("5G_1", "channel"): "w_channel_an",
+            ("2G", "bandwidth"): "opmode",
+            ("5G_1", "bandwidth"): "opmode_an",
+            ("2G", "security_type"): "security_type",
+            ("5G_1", "security_type"): "security_type_an",
+            ("2G", "password"): "passphrase",
+            ("5G_1", "password"): "passphrase_an"
+        }
+        self.region_map = {
+            "0": "Africa",
+            "1": "Asia",
+            "2": "Australia",
+            "3": "Canada",
+            "4": "Europe",
+            "5": "Israel",
+            "6": "Japan",
+            "7": "Korea",
+            "8": "Mexico",
+            "9": "South America",
+            "10": "United States",
+            "11": "China",
+            "12": "India",
+            "13": "Malaysia",
+            "14": "Middle East(Algeria/Syria/Yemen)",
+            "15": "Middle East(Iran/Labanon/Qatar)",
+            "16": "Middle East(Turkey/Egypt/Tunisia/Kuwait)",
+            "17": "Middle East(Saudi Arabia)",
+            "18": "Middle East(United Arab Emirates)",
+            "19": "Russia",
+            "20": "Singapore",
+            "21": "Taiwan"
+        }
+        self.bw_mode_text_2g = {
+            "11g": "Up to 54 Mbps",
+            "VHT20": "Up to 289 Mbps",
+            "VHT40": "Up to 600 Mbps"
+        }
+        self.bw_mode_text_5g = {
+            "VHT20": "Up to 347 Mbps",
+            "VHT40": "Up to 800 Mbps",
+            "VHT80": "Up to 1733 Mbps"
+        }
+        self.bw_mode_values = {
+            "1": "11g",
+            "2": "VHT20",
+            "3": "VHT40",
+            "7": "VHT20",
+            "8": "VHT40",
+            "9": "VHT80"
+        }
+
+    def read_ap_settings(self):
+        """Function to read ap wireless settings."""
+        # Get radio status (on/off)
+        self.read_radio_on_off()
+        # Get radio configuration. Note that if both radios are off, the below
+        # code will result in an error
+        with BlockingBrowser(self.ap_settings["headless_browser"],
+                             900) as browser:
+            browser.visit_persistent(self.config_page,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element="wireless")
+            wireless_button = browser.find_by_id("wireless").first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe("formframe") as iframe:
+                for key, value in self.config_page_fields.items():
+                    if "bandwidth" in key:
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings["{}_{}".format(
+                            key[1],
+                            key[0])] = self.bw_mode_values[config_item.value]
+                    elif "region" in key:
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings["region"] = self.region_map[
+                            config_item.value]
+                    elif "password" in key:
+                        try:
+                            config_item = iframe.find_by_name(value).first
+                            self.ap_settings["{}_{}".format(
+                                key[1], key[0])] = config_item.value
+                            self.ap_settings["{}_{}".format(
+                                "security_type", key[0])] = "WPA2-PSK"
+                        except:
+                            self.ap_settings["{}_{}".format(
+                                key[1], key[0])] = "defaultpassword"
+                            self.ap_settings["{}_{}".format(
+                                "security_type", key[0])] = "Disable"
+                    elif ("channel" in key) or ("ssid" in key):
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings["{}_{}".format(
+                            key[1], key[0])] = config_item.value
+                    else:
+                        pass
+        return self.ap_settings.copy()
+
+    def configure_ap(self, **config_flags):
+        """Function to configure ap wireless settings."""
+        # Turn radios on or off
+        if config_flags["status_toggled"]:
+            self.configure_radio_on_off()
+        # Configure radios
+        with BlockingBrowser(self.ap_settings["headless_browser"],
+                             900) as browser:
+            browser.visit_persistent(self.config_page,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element="wireless")
+            wireless_button = browser.find_by_id("wireless").first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe("formframe") as iframe:
+                # Update AP region. Must be done before channel setting
+                config_item = iframe.find_by_name(
+                    self.config_page_fields["region"]).first
+                config_item.select_by_text(self.ap_settings["region"])
+                # Update wireless settings for each network
+                for key, value in self.config_page_fields.items():
+                    if "ssid" in key:
+                        config_item = iframe.find_by_name(value).first
+                        config_item.fill(self.ap_settings["{}_{}".format(
+                            key[1], key[0])])
+                    elif "channel" in key:
+                        channel_string = "0" * (int(self.ap_settings[
+                            "{}_{}".format(key[1], key[0])]) < 10) + str(
+                                self.ap_settings["{}_{}".format(
+                                    key[1], key[0])]) + "(DFS)" * (48 < int(
+                                        self.ap_settings["{}_{}".format(
+                                            key[1], key[0])]) < 149)
+                        config_item = iframe.find_by_name(value).first
+                        try:
+                            config_item.select_by_text(channel_string)
+                        except AttributeError:
+                            self.log.warning(
+                                "Cannot select channel. Keeping AP default.")
+                    elif key == ("2G", "bandwidth"):
+                        config_item = iframe.find_by_name(value).first
+                        try:
+                            config_item.select_by_text(
+                                str(self.bw_mode_text_2g[self.ap_settings[
+                                    "{}_{}".format(key[1], key[0])]]))
+                        except AttributeError:
+                            self.log.warning(
+                                "Cannot select bandwidth. Keeping AP default.")
+                    elif key == ("5G_1", "bandwidth"):
+                        config_item = iframe.find_by_name(value).first
+                        try:
+                            config_item.select_by_text(
+                                str(self.bw_mode_text_5g[self.ap_settings[
+                                    "{}_{}".format(key[1], key[0])]]))
+                        except AttributeError:
+                            self.log.warning(
+                                "Cannot select bandwidth. Keeping AP default.")
+                # Update passwords for WPA2-PSK protected networks
+                # (Must be done after security type is selected)
+                for key, value in self.config_page_fields.items():
+                    if "security_type" in key:
+                        iframe.choose(
+                            value,
+                            self.ap_settings["{}_{}".format(key[1], key[0])])
+                        if self.ap_settings["{}_{}".format(
+                                key[1], key[0])] == "WPA2-PSK":
+                            config_item = iframe.find_by_name(
+                                self.config_page_fields[(key[0],
+                                                         "password")]).first
+                            config_item.fill(self.ap_settings["{}_{}".format(
+                                "password", key[0])])
+
+                apply_button = iframe.find_by_name("Apply")
+                apply_button[0].click()
+                time.sleep(BROWSER_WAIT_SHORT)
+                try:
+                    alert = browser.get_alert()
+                    alert.accept()
+                except:
+                    pass
+                time.sleep(BROWSER_WAIT_SHORT)
+                try:
+                    alert = browser.get_alert()
+                    alert.accept()
+                except:
+                    pass
+                time.sleep(BROWSER_WAIT_SHORT)
+            time.sleep(BROWSER_WAIT_EXTRA_LONG)
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_EXTRA_LONG,
+                                     10)
+
+    def configure_radio_on_off(self):
+        """Helper configuration function to turn radios on/off."""
+        with BlockingBrowser(self.ap_settings["headless_browser"],
+                             900) as browser:
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+            browser.visit_persistent(self.config_page_advanced,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element="advanced_bt")
+            advanced_button = browser.find_by_id("advanced_bt").first
+            advanced_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+            wireless_button = browser.find_by_id("wladv").first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe("formframe") as iframe:
+                # Turn radios on or off
+                for key, value in self.config_page_fields.items():
+                    if "status" in key:
+                        config_item = iframe.find_by_name(value).first
+                        if self.ap_settings["{}_{}".format(key[1], key[0])]:
+                            config_item.check()
+                        else:
+                            config_item.uncheck()
+
+                time.sleep(BROWSER_WAIT_SHORT)
+                browser.find_by_name("Apply").first.click()
+                time.sleep(BROWSER_WAIT_EXTRA_LONG)
+                browser.visit_persistent(self.config_page,
+                                         BROWSER_WAIT_EXTRA_LONG, 10)
+
+    def read_radio_on_off(self):
+        """Helper configuration function to read radio status."""
+        with BlockingBrowser(self.ap_settings["headless_browser"],
+                             900) as browser:
+            browser.visit_persistent(self.config_page, BROWSER_WAIT_MED, 10)
+            browser.visit_persistent(self.config_page_advanced,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element="advanced_bt")
+            advanced_button = browser.find_by_id("advanced_bt").first
+            advanced_button.click()
+            time.sleep(BROWSER_WAIT_SHORT)
+            wireless_button = browser.find_by_id("wladv").first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe("formframe") as iframe:
+                # Turn radios on or off
+                for key, value in self.config_page_fields.items():
+                    if "status" in key:
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings["{}_{}".format(key[1], key[0])] = int(
+                            config_item.checked)
+
+
+class NetgearR7800AP(NetgearR7500AP):
+    """Class that implements Netgear R7800 AP.
+
+    Since most of the class' implementation is shared with the R7500, this
+    class inherits from NetgearR7500AP and simply redefines config parameters
+    """
+    def __init__(self, ap_settings):
+        super().__init__(ap_settings)
+        self.init_gui_data()
+        # Overwrite minor differences from R7500 AP
+        self.bw_mode_text_2g["VHT20"] = "Up to 347 Mbps"
+        # Read and update AP settings
+        self.read_ap_settings()
+        if not set(ap_settings.items()).issubset(self.ap_settings.items()):
+            self.update_ap_settings(ap_settings)
+
+
+class NetgearR8000AP(NetgearR7000AP):
+    """Class that implements Netgear R8000 AP.
+
+    Since most of the class' implementation is shared with the R7000, this
+    class inherits from NetgearR7000AP and simply redefines config parameters
+    """
+    def init_gui_data(self):
+        super().init_gui_data()
+        # Overwrite minor differences from R7000 AP
+        self.config_page = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/WLG_wireless_dual_band_r8000.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.config_page_nologin = (
+            "{protocol}://{ip_address}:{port}/"
+            "WLG_wireless_dual_band_r8000.htm").format(
+                protocol=self.ap_settings["protocol"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.config_page_advanced = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/WLG_adv_dual_band2_r8000.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.networks = ["2G", "5G_1", "5G_2"]
+        self.channel_band_map = {
+            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+            "5G_1": [36, 40, 44, 48],
+            "5G_2": [149, 153, 157, 161, 165]
+        }
+        self.config_page_fields = {
+            "region": "WRegion",
+            ("2G", "status"): "enable_ap",
+            ("5G_1", "status"): "enable_ap_an",
+            ("5G_2", "status"): "enable_ap_an_2",
+            ("2G", "ssid"): "ssid",
+            ("5G_1", "ssid"): "ssid_an",
+            ("5G_2", "ssid"): "ssid_an_2",
+            ("2G", "channel"): "w_channel",
+            ("5G_1", "channel"): "w_channel_an",
+            ("5G_2", "channel"): "w_channel_an_2",
+            ("2G", "bandwidth"): "opmode",
+            ("5G_1", "bandwidth"): "opmode_an",
+            ("5G_2", "bandwidth"): "opmode_an_2",
+            ("2G", "security_type"): "security_type",
+            ("5G_1", "security_type"): "security_type_an",
+            ("5G_2", "security_type"): "security_type_an_2",
+            ("2G", "password"): "passphrase",
+            ("5G_1", "password"): "passphrase_an",
+            ("5G_2", "password"): "passphrase_an_2"
+        }
+
+
+class NetgearR8500AP(NetgearR7000AP):
+    """Class that implements Netgear R8500 AP.
+
+    Since most of the class' implementation is shared with the R7000, this
+    class inherits from NetgearR7000AP and simply redefines config parameters
+    """
+    def __init__(self, ap_settings):
+        super().__init__(ap_settings)
+        self.init_gui_data()
+        # Overwrite minor differences from R8000 AP
+        self.config_page = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/WLG_wireless_tri_band.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.config_page_nologin = (
+            "{protocol}://{ip_address}:{port}/"
+            "WLG_wireless_tri_band.htm").format(
+                protocol=self.ap_settings["protocol"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.config_page_advanced = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/WLG_adv_tri_band2.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.networks = ["2G", "5G_1", "5G_2"]
+        self.channel_band_map = {
+            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+            "5G_1": [36, 40, 44, 48],
+            "5G_2": [149, 153, 157, 161, 165]
+        }
+        self.config_page_fields = {
+            "region": "WRegion",
+            ("2G", "status"): "enable_ap",
+            ("5G_1", "status"): "enable_ap_an",
+            ("5G_2", "status"): "enable_ap_an_2",
+            ("2G", "ssid"): "ssid",
+            ("5G_1", "ssid"): "ssid_an",
+            ("5G_2", "ssid"): "ssid_an_2",
+            ("2G", "channel"): "w_channel",
+            ("5G_1", "channel"): "w_channel_an",
+            ("5G_2", "channel"): "w_channel_an_2",
+            ("2G", "bandwidth"): "opmode",
+            ("5G_1", "bandwidth"): "opmode_an",
+            ("5G_2", "bandwidth"): "opmode_an_2",
+            ("2G", "security_type"): "security_type",
+            ("5G_1", "security_type"): "security_type_an",
+            ("5G_2", "security_type"): "security_type_an_2",
+            ("2G", "password"): "passphrase",
+            ("5G_1", "password"): "passphrase_an",
+            ("5G_2", "password"): "passphrase_an_2"
+        }
+        self.bw_mode_text = {
+            "11g": "Up to 54 Mbps",
+            "VHT20": "Up to 433 Mbps",
+            "VHT40": "Up to 1000 Mbps",
+            "VHT80": "Up to 2165 Mbps"
+        }
+        # Read and update AP settings
+        self.read_ap_settings()
+        if not set(ap_settings.items()).issubset(self.ap_settings.items()):
+            self.update_ap_settings(ap_settings)
+
+
+class NetgearRAXAP(NetgearR7000AP):
+    """Class that implements Netgear RAX AP.
+
+    Since most of the class' implementation is shared with the R7000, this
+    class inherits from NetgearR7000AP and simply redefines config parameters
+    """
+    def init_gui_data(self):
+        super().init_gui_data()
+        # Overwrite minor differences from R7000 AP
+        self.config_page = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/WLG_wireless_dual_band_r10.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.config_page_nologin = (
+            "{protocol}://{ip_address}:{port}/"
+            "WLG_wireless_dual_band_r10.htm").format(
+                protocol=self.ap_settings["protocol"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.config_page_advanced = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/WLG_adv_dual_band2.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.networks = ["2G", "5G_1", "5G_2"]
+        self.channel_band_map = {
+            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+            "5G_1": [
+                36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 120,
+                124, 128, 132, 136, 140, 149, 153, 157, 161, 165
+            ]
+        }
+
+        self.bw_mode_values = {
+            "g and b": "11g",
+            "145Mbps": "VHT20",
+            "300Mbps": "VHT40",
+            "HT80": "VHT80",
+            "HT160": "VHT160"
+        }
+        self.bw_mode_text = {
+            "11g": "Up to 54 Mbps",
+            "VHT20": "Up to 600 Mbps",
+            "VHT40": "Up to 1200 Mbps",
+            "VHT80": "Up to 2400 Mbps",
+            "VHT160": "Up to 4800 Mbps"
+        }
+
+
+class GoogleWifiAP(WifiRetailAP):
+    """ Class that implements Google Wifi AP.
+
+    This class is a work in progress
+    """
+    def __init__(self, ap_settings):
+        super().__init__(ap_settings)
+        # Initialize AP
+        if self.ap_settings["status_2G"] and self.ap_settings["status_5G_1"]:
+            raise ValueError("Error initializing Google Wifi AP. "
+                             "Only one interface can be enabled at a time.")
+        self.channel_band_map = {
+            "2G": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
+            "5G_1": [36, 40, 44, 48, 149, 153, 157, 161, 165]
+        }
+        self.BW_MODE_MAP = {
+            "legacy": 20,
+            "VHT20": 20,
+            "VHT40": 40,
+            "VHT80": 80
+        }
+        self.default_settings = {
+            "region": "United States",
+            "brand": "Google",
+            "model": "Wifi",
+            "hostapd_profile": "whirlwind",
+            "status_2G": 0,
+            "status_5G_1": 0,
+            "ssid_2G": "GoogleWifi_2G",
+            "ssid_5G_1": "GoogleWifi_5G",
+            "channel_2G": 11,
+            "channel_5G_1": 149,
+            "bandwidth_2G": "VHT20",
+            "bandwidth_5G_1": "VHT20",
+            "power_2G": "auto",
+            "power_5G_1": "auto",
+            "mode_2G": None,
+            "num_streams_2G": None,
+            "rate_2G": "auto",
+            "short_gi_2G": 0,
+            "mode_5G_1": None,
+            "num_streams_5G_1": None,
+            "rate_5G_1": "auto",
+            "short_gi_5G_1": 0,
+            "security_type_2G": "Open",
+            "security_type_5G_1": "Open",
+            "subnet_2G": "192.168.1.0/24",
+            "subnet_5G_1": "192.168.9.0/24",
+            "password_2G": "password",
+            "password_5G_1": "password"
+        }
+
+        for setting in self.default_settings.keys():
+            if setting not in self.ap_settings:
+                self.log.debug(
+                    "{0} not found during init. Setting {0} = {1}".format(
+                        setting, self.default_settings[setting]))
+                self.ap_settings[setting] = self.default_settings[setting]
+        init_settings = self.ap_settings.copy()
+        init_settings["ap_subnet"] = {
+            "2g": self.ap_settings["subnet_2G"],
+            "5g": self.ap_settings["subnet_5G_1"]
+        }
+        self.access_point = access_point.AccessPoint(init_settings)
+        self.configure_ap()
+
+    def read_ap_settings(self):
+        """Function that reads current ap settings."""
+        return self.ap_settings.copy()
+
+    def update_ap_settings(self, dict_settings={}, **named_settings):
+        """Function to update settings of existing AP.
+
+        Function copies arguments into ap_settings and calls configure_ap
+        to apply them.
+
+        Args:
+            dict_settings: single dictionary of settings to update
+            **named_settings: named settings to update
+            Note: dict and named_settings cannot contain the same settings.
+        """
+        settings_to_update = dict(dict_settings, **named_settings)
+        if len(settings_to_update) != len(dict_settings) + len(named_settings):
+            raise KeyError("The following keys were passed twice: {}".format(
+                (set(dict_settings.keys()).intersection(
+                    set(named_settings.keys())))))
+        if not set(settings_to_update.keys()).issubset(
+                set(self.ap_settings.keys())):
+            raise KeyError(
+                "The following settings are invalid for this AP: {}".format(
+                    set(settings_to_update.keys()).difference(
+                        set(self.ap_settings.keys()))))
+
+        updating_2G = any(["2G" in x for x in settings_to_update.keys()])
+        updating_5G_1 = any(["5G_1" in x for x in settings_to_update.keys()])
+        if updating_2G and updating_5G_1:
+            raise ValueError(
+                "Error updating Google WiFi AP. "
+                "One interface can be activated and updated at a time")
+        elif updating_2G:
+            # If updating an interface and not explicitly setting its status,
+            # it is assumed that the interface is to be ENABLED and updated
+            if "status_2G" not in settings_to_update:
+                settings_to_update["status_2G"] = 1
+                settings_to_update["status_5G_1"] = 0
+        elif updating_5G_1:
+            if "status_5G_1" not in settings_to_update:
+                settings_to_update["status_2G"] = 0
+                settings_to_update["status_5G_1"] = 1
+
+        updates_requested = False
+        for setting, value in settings_to_update.items():
+            if self.ap_settings[setting] != value:
+                self.ap_settings[setting] = value
+                updates_requested = True
+
+        if updates_requested:
+            self.configure_ap()
+
+    def configure_ap(self):
+        """Function to configure Google Wifi."""
+        self.log.info("Stopping Google Wifi interfaces.")
+        self.access_point.stop_all_aps()
+
+        if self.ap_settings["status_2G"] == 1:
+            network = "2G"
+            self.log.info("Bringing up 2.4 GHz network.")
+        elif self.ap_settings["status_5G_1"] == 1:
+            network = "5G_1"
+            self.log.info("Bringing up 5 GHz network.")
+        else:
+            return
+
+        bss_settings = []
+        ssid = self.ap_settings["ssid_{}".format(network)]
+        security_mode = self.ap_settings["security_type_{}".format(
+            network)].lower()
+        if "wpa" in security_mode:
+            password = self.ap_settings["password_{}".format(network)]
+            security = hostapd_security.Security(security_mode=security_mode,
+                                                 password=password)
+        else:
+            security = hostapd_security.Security(security_mode=None,
+                                                 password=None)
+        channel = int(self.ap_settings["channel_{}".format(network)])
+        bandwidth = self.BW_MODE_MAP[self.ap_settings["bandwidth_{}".format(
+            network)]]
+        config = hostapd_ap_preset.create_ap_preset(
+            channel=channel,
+            ssid=ssid,
+            security=security,
+            bss_settings=bss_settings,
+            vht_bandwidth=bandwidth,
+            profile_name=self.ap_settings["hostapd_profile"],
+            iface_wlan_2g=self.access_point.wlan_2g,
+            iface_wlan_5g=self.access_point.wlan_5g)
+        config_bridge = self.access_point.generate_bridge_configs(channel)
+        brconfigs = bridge_interface.BridgeInterfaceConfigs(
+            config_bridge[0], "lan0", config_bridge[2])
+        self.access_point.bridge.startup(brconfigs)
+        self.access_point.start_ap(config)
+        self.set_power(network, self.ap_settings["power_{}".format(network)])
+        self.set_rate(
+            network,
+            mode=self.ap_settings["mode_{}".format(network)],
+            num_streams=self.ap_settings["num_streams_{}".format(network)],
+            rate=self.ap_settings["rate_{}".format(network)],
+            short_gi=self.ap_settings["short_gi_{}".format(network)])
+        self.log.info("AP started on channel {} with SSID {}".format(
+            channel, ssid))
+
+    def set_power(self, network, power):
+        """Function that sets network transmit power.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            power: power level in dBm
+        """
+        if power == "auto":
+            power_string = "auto"
+        else:
+            if not float(power).is_integer():
+                self.log.info(
+                    "Power in dBm must be an integer. Setting to {}".format(
+                        int(power)))
+            power = int(power)
+            power_string = "fixed {}".format(int(power) * 100)
+
+        if "2G" in network:
+            interface = self.access_point.wlan_2g
+            self.ap_settings["power_2G"] = power
+        elif "5G_1" in network:
+            interface = self.access_point.wlan_5g
+            self.ap_settings["power_5G_1"] = power
+        self.access_point.ssh.run("iw dev {} set txpower {}".format(
+            interface, power_string))
+
+    def set_rate(self,
+                 network,
+                 mode=None,
+                 num_streams=None,
+                 rate='auto',
+                 short_gi=0):
+        """Function that sets rate.
+
+        Args:
+            network: string containing network identifier (2G, 5G_1, 5G_2)
+            mode: string indicating the WiFi standard to use
+            num_streams: number of MIMO streams. used only for VHT
+            rate: data rate of MCS index to use
+            short_gi: boolean controlling the use of short guard interval
+        """
+        if "2G" in network:
+            interface = self.access_point.wlan_2g
+            interface_short = "2.4"
+            self.ap_settings["mode_2G"] = mode
+            self.ap_settings["num_streams_2G"] = num_streams
+            self.ap_settings["rate_2G"] = rate
+            self.ap_settings["short_gi_2G"] = short_gi
+        elif "5G_1" in network:
+            interface = self.access_point.wlan_5g
+            interface_short = "5"
+            self.ap_settings["mode_5G_1"] = mode
+            self.ap_settings["num_streams_5G_1"] = num_streams
+            self.ap_settings["rate_5G_1"] = rate
+            self.ap_settings["short_gi_5G_1"] = short_gi
+
+        if rate == "auto":
+            cmd_string = "iw dev {0} set bitrates".format(interface)
+        elif "legacy" in mode.lower():
+            cmd_string = "iw dev {0} set bitrates legacy-{1} {2} ht-mcs-{1} vht-mcs-{1}".format(
+                interface, interface_short, rate)
+        elif "vht" in mode.lower():
+            cmd_string = "iw dev {0} set bitrates legacy-{1} ht-mcs-{1} vht-mcs-{1} {2}:{3}".format(
+                interface, interface_short, num_streams, rate)
+            if short_gi:
+                cmd_string = cmd_string + " sgi-{}".format(interface_short)
+        elif "ht" in mode.lower():
+            cmd_string = "iw dev {0} set bitrates legacy-{1} ht-mcs-{1} {2} vht-mcs-{1}".format(
+                interface, interface_short, rate)
+            if short_gi:
+                cmd_string = cmd_string + " sgi-{}".format(interface_short)
+        self.access_point.ssh.run(cmd_string)
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py b/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py
new file mode 100755
index 0000000..73f5008
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py
@@ -0,0 +1,2609 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2016 Google, Inc.
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import logging
+import os
+import re
+import shutil
+import time
+
+from collections import namedtuple
+from enum import IntEnum
+from queue import Empty
+
+from acts import asserts
+from acts import context
+from acts import signals
+from acts import utils
+from acts.controllers import attenuator
+from acts.controllers.ap_lib import hostapd_security
+from acts.controllers.ap_lib import hostapd_ap_preset
+from acts.controllers.ap_lib.hostapd_constants import BAND_2G
+from acts.controllers.ap_lib.hostapd_constants import BAND_5G
+from acts_contrib.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.tel import tel_defines
+
+# Default timeout used for reboot, toggle WiFi and Airplane mode,
+# for the system to settle down after the operation.
+DEFAULT_TIMEOUT = 10
+# Number of seconds to wait for events that are supposed to happen quickly.
+# Like onSuccess for start background scan and confirmation on wifi state
+# change.
+SHORT_TIMEOUT = 30
+ROAMING_TIMEOUT = 30
+WIFI_CONNECTION_TIMEOUT_DEFAULT = 30
+# Speed of light in m/s.
+SPEED_OF_LIGHT = 299792458
+
+DEFAULT_PING_ADDR = "https://www.google.com/robots.txt"
+
+ROAMING_ATTN = {
+        "AP1_on_AP2_off": [
+            0,
+            0,
+            95,
+            95
+        ],
+        "AP1_off_AP2_on": [
+            95,
+            95,
+            0,
+            0
+        ],
+        "default": [
+            0,
+            0,
+            0,
+            0
+        ]
+    }
+
+
+class WifiEnums():
+
+    SSID_KEY = "SSID" # Used for Wifi & SoftAp
+    SSID_PATTERN_KEY = "ssidPattern"
+    NETID_KEY = "network_id"
+    BSSID_KEY = "BSSID" # Used for Wifi & SoftAp
+    BSSID_PATTERN_KEY = "bssidPattern"
+    PWD_KEY = "password" # Used for Wifi & SoftAp
+    frequency_key = "frequency"
+    HIDDEN_KEY = "hiddenSSID" # Used for Wifi & SoftAp
+    IS_APP_INTERACTION_REQUIRED = "isAppInteractionRequired"
+    IS_USER_INTERACTION_REQUIRED = "isUserInteractionRequired"
+    IS_SUGGESTION_METERED = "isMetered"
+    PRIORITY = "priority"
+    SECURITY = "security" # Used for Wifi & SoftAp
+
+    # Used for SoftAp
+    AP_BAND_KEY = "apBand"
+    AP_CHANNEL_KEY = "apChannel"
+    AP_MAXCLIENTS_KEY = "MaxNumberOfClients"
+    AP_SHUTDOWNTIMEOUT_KEY = "ShutdownTimeoutMillis"
+    AP_SHUTDOWNTIMEOUTENABLE_KEY = "AutoShutdownEnabled"
+    AP_CLIENTCONTROL_KEY = "ClientControlByUserEnabled"
+    AP_ALLOWEDLIST_KEY = "AllowedClientList"
+    AP_BLOCKEDLIST_KEY = "BlockedClientList"
+
+    WIFI_CONFIG_SOFTAP_BAND_2G = 1
+    WIFI_CONFIG_SOFTAP_BAND_5G = 2
+    WIFI_CONFIG_SOFTAP_BAND_2G_5G = 3
+    WIFI_CONFIG_SOFTAP_BAND_6G = 4
+    WIFI_CONFIG_SOFTAP_BAND_2G_6G = 5
+    WIFI_CONFIG_SOFTAP_BAND_5G_6G = 6
+    WIFI_CONFIG_SOFTAP_BAND_ANY = 7
+
+    # DO NOT USE IT for new test case! Replaced by WIFI_CONFIG_SOFTAP_BAND_
+    WIFI_CONFIG_APBAND_2G = WIFI_CONFIG_SOFTAP_BAND_2G
+    WIFI_CONFIG_APBAND_5G = WIFI_CONFIG_SOFTAP_BAND_5G
+    WIFI_CONFIG_APBAND_AUTO = WIFI_CONFIG_SOFTAP_BAND_2G_5G
+
+    WIFI_CONFIG_APBAND_2G_OLD = 0
+    WIFI_CONFIG_APBAND_5G_OLD = 1
+    WIFI_CONFIG_APBAND_AUTO_OLD = -1
+
+    WIFI_WPS_INFO_PBC = 0
+    WIFI_WPS_INFO_DISPLAY = 1
+    WIFI_WPS_INFO_KEYPAD = 2
+    WIFI_WPS_INFO_LABEL = 3
+    WIFI_WPS_INFO_INVALID = 4
+
+    class SoftApSecurityType():
+        OPEN = "NONE"
+        WPA2 = "WPA2_PSK"
+        WPA3_SAE_TRANSITION = "WPA3_SAE_TRANSITION"
+        WPA3_SAE = "WPA3_SAE"
+
+    class CountryCode():
+        CHINA = "CN"
+        JAPAN = "JP"
+        UK = "GB"
+        US = "US"
+        UNKNOWN = "UNKNOWN"
+
+    # Start of Macros for EAP
+    # EAP types
+    class Eap(IntEnum):
+        NONE = -1
+        PEAP = 0
+        TLS = 1
+        TTLS = 2
+        PWD = 3
+        SIM = 4
+        AKA = 5
+        AKA_PRIME = 6
+        UNAUTH_TLS = 7
+
+    # EAP Phase2 types
+    class EapPhase2(IntEnum):
+        NONE = 0
+        PAP = 1
+        MSCHAP = 2
+        MSCHAPV2 = 3
+        GTC = 4
+
+    class Enterprise:
+        # Enterprise Config Macros
+        EMPTY_VALUE = "NULL"
+        EAP = "eap"
+        PHASE2 = "phase2"
+        IDENTITY = "identity"
+        ANON_IDENTITY = "anonymous_identity"
+        PASSWORD = "password"
+        SUBJECT_MATCH = "subject_match"
+        ALTSUBJECT_MATCH = "altsubject_match"
+        DOM_SUFFIX_MATCH = "domain_suffix_match"
+        CLIENT_CERT = "client_cert"
+        CA_CERT = "ca_cert"
+        ENGINE = "engine"
+        ENGINE_ID = "engine_id"
+        PRIVATE_KEY_ID = "key_id"
+        REALM = "realm"
+        PLMN = "plmn"
+        FQDN = "FQDN"
+        FRIENDLY_NAME = "providerFriendlyName"
+        ROAMING_IDS = "roamingConsortiumIds"
+        OCSP = "ocsp"
+    # End of Macros for EAP
+
+    # Macros for wifi p2p.
+    WIFI_P2P_SERVICE_TYPE_ALL = 0
+    WIFI_P2P_SERVICE_TYPE_BONJOUR = 1
+    WIFI_P2P_SERVICE_TYPE_UPNP = 2
+    WIFI_P2P_SERVICE_TYPE_VENDOR_SPECIFIC = 255
+
+    class ScanResult:
+        CHANNEL_WIDTH_20MHZ = 0
+        CHANNEL_WIDTH_40MHZ = 1
+        CHANNEL_WIDTH_80MHZ = 2
+        CHANNEL_WIDTH_160MHZ = 3
+        CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4
+
+    # Macros for wifi rtt.
+    class RttType(IntEnum):
+        TYPE_ONE_SIDED = 1
+        TYPE_TWO_SIDED = 2
+
+    class RttPeerType(IntEnum):
+        PEER_TYPE_AP = 1
+        PEER_TYPE_STA = 2  # Requires NAN.
+        PEER_P2P_GO = 3
+        PEER_P2P_CLIENT = 4
+        PEER_NAN = 5
+
+    class RttPreamble(IntEnum):
+        PREAMBLE_LEGACY = 0x01
+        PREAMBLE_HT = 0x02
+        PREAMBLE_VHT = 0x04
+
+    class RttBW(IntEnum):
+        BW_5_SUPPORT = 0x01
+        BW_10_SUPPORT = 0x02
+        BW_20_SUPPORT = 0x04
+        BW_40_SUPPORT = 0x08
+        BW_80_SUPPORT = 0x10
+        BW_160_SUPPORT = 0x20
+
+    class Rtt(IntEnum):
+        STATUS_SUCCESS = 0
+        STATUS_FAILURE = 1
+        STATUS_FAIL_NO_RSP = 2
+        STATUS_FAIL_REJECTED = 3
+        STATUS_FAIL_NOT_SCHEDULED_YET = 4
+        STATUS_FAIL_TM_TIMEOUT = 5
+        STATUS_FAIL_AP_ON_DIFF_CHANNEL = 6
+        STATUS_FAIL_NO_CAPABILITY = 7
+        STATUS_ABORTED = 8
+        STATUS_FAIL_INVALID_TS = 9
+        STATUS_FAIL_PROTOCOL = 10
+        STATUS_FAIL_SCHEDULE = 11
+        STATUS_FAIL_BUSY_TRY_LATER = 12
+        STATUS_INVALID_REQ = 13
+        STATUS_NO_WIFI = 14
+        STATUS_FAIL_FTM_PARAM_OVERRIDE = 15
+
+        REASON_UNSPECIFIED = -1
+        REASON_NOT_AVAILABLE = -2
+        REASON_INVALID_LISTENER = -3
+        REASON_INVALID_REQUEST = -4
+
+    class RttParam:
+        device_type = "deviceType"
+        request_type = "requestType"
+        BSSID = "bssid"
+        channel_width = "channelWidth"
+        frequency = "frequency"
+        center_freq0 = "centerFreq0"
+        center_freq1 = "centerFreq1"
+        number_burst = "numberBurst"
+        interval = "interval"
+        num_samples_per_burst = "numSamplesPerBurst"
+        num_retries_per_measurement_frame = "numRetriesPerMeasurementFrame"
+        num_retries_per_FTMR = "numRetriesPerFTMR"
+        lci_request = "LCIRequest"
+        lcr_request = "LCRRequest"
+        burst_timeout = "burstTimeout"
+        preamble = "preamble"
+        bandwidth = "bandwidth"
+        margin = "margin"
+
+    RTT_MARGIN_OF_ERROR = {
+        RttBW.BW_80_SUPPORT: 2,
+        RttBW.BW_40_SUPPORT: 5,
+        RttBW.BW_20_SUPPORT: 5
+    }
+
+    # Macros as specified in the WifiScanner code.
+    WIFI_BAND_UNSPECIFIED = 0  # not specified
+    WIFI_BAND_24_GHZ = 1  # 2.4 GHz band
+    WIFI_BAND_5_GHZ = 2  # 5 GHz band without DFS channels
+    WIFI_BAND_5_GHZ_DFS_ONLY = 4  # 5 GHz band with DFS channels
+    WIFI_BAND_5_GHZ_WITH_DFS = 6  # 5 GHz band with DFS channels
+    WIFI_BAND_BOTH = 3  # both bands without DFS channels
+    WIFI_BAND_BOTH_WITH_DFS = 7  # both bands with DFS channels
+
+    REPORT_EVENT_AFTER_BUFFER_FULL = 0
+    REPORT_EVENT_AFTER_EACH_SCAN = 1
+    REPORT_EVENT_FULL_SCAN_RESULT = 2
+
+    SCAN_TYPE_LOW_LATENCY = 0
+    SCAN_TYPE_LOW_POWER = 1
+    SCAN_TYPE_HIGH_ACCURACY = 2
+
+    # US Wifi frequencies
+    ALL_2G_FREQUENCIES = [2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452,
+                          2457, 2462]
+    DFS_5G_FREQUENCIES = [5260, 5280, 5300, 5320, 5500, 5520, 5540, 5560, 5580,
+                          5600, 5620, 5640, 5660, 5680, 5700, 5720]
+    NONE_DFS_5G_FREQUENCIES = [5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805,
+                               5825]
+    ALL_5G_FREQUENCIES = DFS_5G_FREQUENCIES + NONE_DFS_5G_FREQUENCIES
+
+    band_to_frequencies = {
+        WIFI_BAND_24_GHZ: ALL_2G_FREQUENCIES,
+        WIFI_BAND_5_GHZ: NONE_DFS_5G_FREQUENCIES,
+        WIFI_BAND_5_GHZ_DFS_ONLY: DFS_5G_FREQUENCIES,
+        WIFI_BAND_5_GHZ_WITH_DFS: ALL_5G_FREQUENCIES,
+        WIFI_BAND_BOTH: ALL_2G_FREQUENCIES + NONE_DFS_5G_FREQUENCIES,
+        WIFI_BAND_BOTH_WITH_DFS: ALL_5G_FREQUENCIES + ALL_2G_FREQUENCIES
+    }
+
+    # All Wifi frequencies to channels lookup.
+    freq_to_channel = {
+        2412: 1,
+        2417: 2,
+        2422: 3,
+        2427: 4,
+        2432: 5,
+        2437: 6,
+        2442: 7,
+        2447: 8,
+        2452: 9,
+        2457: 10,
+        2462: 11,
+        2467: 12,
+        2472: 13,
+        2484: 14,
+        4915: 183,
+        4920: 184,
+        4925: 185,
+        4935: 187,
+        4940: 188,
+        4945: 189,
+        4960: 192,
+        4980: 196,
+        5035: 7,
+        5040: 8,
+        5045: 9,
+        5055: 11,
+        5060: 12,
+        5080: 16,
+        5170: 34,
+        5180: 36,
+        5190: 38,
+        5200: 40,
+        5210: 42,
+        5220: 44,
+        5230: 46,
+        5240: 48,
+        5260: 52,
+        5280: 56,
+        5300: 60,
+        5320: 64,
+        5500: 100,
+        5520: 104,
+        5540: 108,
+        5560: 112,
+        5580: 116,
+        5600: 120,
+        5620: 124,
+        5640: 128,
+        5660: 132,
+        5680: 136,
+        5700: 140,
+        5745: 149,
+        5765: 153,
+        5785: 157,
+        5795: 159,
+        5805: 161,
+        5825: 165,
+    }
+
+    # All Wifi channels to frequencies lookup.
+    channel_2G_to_freq = {
+        1: 2412,
+        2: 2417,
+        3: 2422,
+        4: 2427,
+        5: 2432,
+        6: 2437,
+        7: 2442,
+        8: 2447,
+        9: 2452,
+        10: 2457,
+        11: 2462,
+        12: 2467,
+        13: 2472,
+        14: 2484
+    }
+
+    channel_5G_to_freq = {
+        183: 4915,
+        184: 4920,
+        185: 4925,
+        187: 4935,
+        188: 4940,
+        189: 4945,
+        192: 4960,
+        196: 4980,
+        7: 5035,
+        8: 5040,
+        9: 5045,
+        11: 5055,
+        12: 5060,
+        16: 5080,
+        34: 5170,
+        36: 5180,
+        38: 5190,
+        40: 5200,
+        42: 5210,
+        44: 5220,
+        46: 5230,
+        48: 5240,
+        52: 5260,
+        56: 5280,
+        60: 5300,
+        64: 5320,
+        100: 5500,
+        104: 5520,
+        108: 5540,
+        112: 5560,
+        116: 5580,
+        120: 5600,
+        124: 5620,
+        128: 5640,
+        132: 5660,
+        136: 5680,
+        140: 5700,
+        149: 5745,
+        151: 5755,
+        153: 5765,
+        155: 5775,
+        157: 5785,
+        159: 5795,
+        161: 5805,
+        165: 5825
+    }
+
+
+class WifiChannelBase:
+    ALL_2G_FREQUENCIES = []
+    DFS_5G_FREQUENCIES = []
+    NONE_DFS_5G_FREQUENCIES = []
+    ALL_5G_FREQUENCIES = DFS_5G_FREQUENCIES + NONE_DFS_5G_FREQUENCIES
+    MIX_CHANNEL_SCAN = []
+
+    def band_to_freq(self, band):
+        _band_to_frequencies = {
+            WifiEnums.WIFI_BAND_24_GHZ: self.ALL_2G_FREQUENCIES,
+            WifiEnums.WIFI_BAND_5_GHZ: self.NONE_DFS_5G_FREQUENCIES,
+            WifiEnums.WIFI_BAND_5_GHZ_DFS_ONLY: self.DFS_5G_FREQUENCIES,
+            WifiEnums.WIFI_BAND_5_GHZ_WITH_DFS: self.ALL_5G_FREQUENCIES,
+            WifiEnums.WIFI_BAND_BOTH:
+            self.ALL_2G_FREQUENCIES + self.NONE_DFS_5G_FREQUENCIES,
+            WifiEnums.WIFI_BAND_BOTH_WITH_DFS:
+            self.ALL_5G_FREQUENCIES + self.ALL_2G_FREQUENCIES
+        }
+        return _band_to_frequencies[band]
+
+
+class WifiChannelUS(WifiChannelBase):
+    # US Wifi frequencies
+    ALL_2G_FREQUENCIES = [2412, 2417, 2422, 2427, 2432, 2437, 2442, 2447, 2452,
+                          2457, 2462]
+    NONE_DFS_5G_FREQUENCIES = [5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805,
+                               5825]
+    MIX_CHANNEL_SCAN = [2412, 2437, 2462, 5180, 5200, 5280, 5260, 5300, 5500,
+                        5320, 5520, 5560, 5700, 5745, 5805]
+
+    def __init__(self, model=None):
+        self.DFS_5G_FREQUENCIES = [5260, 5280, 5300, 5320, 5500, 5520,
+                                   5540, 5560, 5580, 5600, 5620, 5640,
+                                   5660, 5680, 5700, 5720]
+        self.ALL_5G_FREQUENCIES = self.DFS_5G_FREQUENCIES + self.NONE_DFS_5G_FREQUENCIES
+
+
+class WifiReferenceNetworks:
+    """ Class to parse and return networks of different band and
+        auth type from reference_networks
+    """
+    def __init__(self, obj):
+        self.reference_networks = obj
+        self.WIFI_2G = "2g"
+        self.WIFI_5G = "5g"
+
+        self.secure_networks_2g = []
+        self.secure_networks_5g = []
+        self.open_networks_2g = []
+        self.open_networks_5g = []
+        self._parse_networks()
+
+    def _parse_networks(self):
+        for network in self.reference_networks:
+            for key in network:
+                if key == self.WIFI_2G:
+                    if "password" in network[key]:
+                        self.secure_networks_2g.append(network[key])
+                    else:
+                        self.open_networks_2g.append(network[key])
+                else:
+                    if "password" in network[key]:
+                        self.secure_networks_5g.append(network[key])
+                    else:
+                        self.open_networks_5g.append(network[key])
+
+    def return_2g_secure_networks(self):
+        return self.secure_networks_2g
+
+    def return_5g_secure_networks(self):
+        return self.secure_networks_5g
+
+    def return_2g_open_networks(self):
+        return self.open_networks_2g
+
+    def return_5g_open_networks(self):
+        return self.open_networks_5g
+
+    def return_secure_networks(self):
+        return self.secure_networks_2g + self.secure_networks_5g
+
+    def return_open_networks(self):
+        return self.open_networks_2g + self.open_networks_5g
+
+
+def _assert_on_fail_handler(func, assert_on_fail, *args, **kwargs):
+    """Wrapper function that handles the bahevior of assert_on_fail.
+
+    When assert_on_fail is True, let all test signals through, which can
+    terminate test cases directly. When assert_on_fail is False, the wrapper
+    raises no test signals and reports operation status by returning True or
+    False.
+
+    Args:
+        func: The function to wrap. This function reports operation status by
+              raising test signals.
+        assert_on_fail: A boolean that specifies if the output of the wrapper
+                        is test signal based or return value based.
+        args: Positional args for func.
+        kwargs: Name args for func.
+
+    Returns:
+        If assert_on_fail is True, returns True/False to signal operation
+        status, otherwise return nothing.
+    """
+    try:
+        func(*args, **kwargs)
+        if not assert_on_fail:
+            return True
+    except signals.TestSignal:
+        if assert_on_fail:
+            raise
+        return False
+
+
+def assert_network_in_list(target, network_list):
+    """Makes sure a specified target Wi-Fi network exists in a list of Wi-Fi
+    networks.
+
+    Args:
+        target: A dict representing a Wi-Fi network.
+                E.g. {WifiEnums.SSID_KEY: "SomeNetwork"}
+        network_list: A list of dicts, each representing a Wi-Fi network.
+    """
+    match_results = match_networks(target, network_list)
+    asserts.assert_true(
+        match_results, "Target network %s, does not exist in network list %s" %
+        (target, network_list))
+
+
+def match_networks(target_params, networks):
+    """Finds the WiFi networks that match a given set of parameters in a list
+    of WiFi networks.
+
+    To be considered a match, the network should contain every key-value pair
+    of target_params
+
+    Args:
+        target_params: A dict with 1 or more key-value pairs representing a Wi-Fi network.
+                       E.g { 'SSID': 'wh_ap1_5g', 'BSSID': '30:b5:c2:33:e4:47' }
+        networks: A list of dict objects representing WiFi networks.
+
+    Returns:
+        The networks that match the target parameters.
+    """
+    results = []
+    asserts.assert_true(target_params,
+                        "Expected networks object 'target_params' is empty")
+    for n in networks:
+        add_network = 1
+        for k, v in target_params.items():
+            if k not in n:
+                add_network = 0
+                break
+            if n[k] != v:
+                add_network = 0
+                break
+        if add_network:
+            results.append(n)
+    return results
+
+
+def wait_for_wifi_state(ad, state, assert_on_fail=True):
+    """Waits for the device to transition to the specified wifi state
+
+    Args:
+        ad: An AndroidDevice object.
+        state: Wifi state to wait for.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        If assert_on_fail is False, function returns True if the device transitions
+        to the specified state, False otherwise. If assert_on_fail is True, no return value.
+    """
+    return _assert_on_fail_handler(
+        _wait_for_wifi_state, assert_on_fail, ad, state=state)
+
+
+def _wait_for_wifi_state(ad, state):
+    """Toggles the state of wifi.
+
+    TestFailure signals are raised when something goes wrong.
+
+    Args:
+        ad: An AndroidDevice object.
+        state: Wifi state to wait for.
+    """
+    if state == ad.droid.wifiCheckState():
+        # Check if the state is already achieved, so we don't wait for the
+        # state change event by mistake.
+        return
+    ad.droid.wifiStartTrackingStateChange()
+    fail_msg = "Device did not transition to Wi-Fi state to %s on %s." % (state,
+                                                           ad.serial)
+    try:
+        ad.ed.wait_for_event(wifi_constants.WIFI_STATE_CHANGED,
+                             lambda x: x["data"]["enabled"] == state,
+                             SHORT_TIMEOUT)
+    except Empty:
+        asserts.assert_equal(state, ad.droid.wifiCheckState(), fail_msg)
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
+def wifi_toggle_state(ad, new_state=None, assert_on_fail=True):
+    """Toggles the state of wifi.
+
+    Args:
+        ad: An AndroidDevice object.
+        new_state: Wifi state to set to. If None, opposite of the current state.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        If assert_on_fail is False, function returns True if the toggle was
+        successful, False otherwise. If assert_on_fail is True, no return value.
+    """
+    return _assert_on_fail_handler(
+        _wifi_toggle_state, assert_on_fail, ad, new_state=new_state)
+
+
+def _wifi_toggle_state(ad, new_state=None):
+    """Toggles the state of wifi.
+
+    TestFailure signals are raised when something goes wrong.
+
+    Args:
+        ad: An AndroidDevice object.
+        new_state: The state to set Wi-Fi to. If None, opposite of the current
+                   state will be set.
+    """
+    if new_state is None:
+        new_state = not ad.droid.wifiCheckState()
+    elif new_state == ad.droid.wifiCheckState():
+        # Check if the new_state is already achieved, so we don't wait for the
+        # state change event by mistake.
+        return
+    ad.droid.wifiStartTrackingStateChange()
+    ad.log.info("Setting Wi-Fi state to %s.", new_state)
+    ad.ed.clear_all_events()
+    # Setting wifi state.
+    ad.droid.wifiToggleState(new_state)
+    time.sleep(2)
+    fail_msg = "Failed to set Wi-Fi state to %s on %s." % (new_state,
+                                                           ad.serial)
+    try:
+        ad.ed.wait_for_event(wifi_constants.WIFI_STATE_CHANGED,
+                             lambda x: x["data"]["enabled"] == new_state,
+                             SHORT_TIMEOUT)
+    except Empty:
+        asserts.assert_equal(new_state, ad.droid.wifiCheckState(), fail_msg)
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
+def reset_wifi(ad):
+    """Clears all saved Wi-Fi networks on a device.
+
+    This will turn Wi-Fi on.
+
+    Args:
+        ad: An AndroidDevice object.
+
+    """
+    networks = ad.droid.wifiGetConfiguredNetworks()
+    if not networks:
+        return
+    for n in networks:
+        ad.droid.wifiForgetNetwork(n['networkId'])
+        try:
+            event = ad.ed.pop_event(wifi_constants.WIFI_FORGET_NW_SUCCESS,
+                                    SHORT_TIMEOUT)
+        except Empty:
+            logging.warning("Could not confirm the removal of network %s.", n)
+    # Check again to see if there's any network left.
+    asserts.assert_true(
+        not ad.droid.wifiGetConfiguredNetworks(),
+        "Failed to remove these configured Wi-Fi networks: %s" % networks)
+
+
+def toggle_airplane_mode_on_and_off(ad):
+    """Turn ON and OFF Airplane mode.
+
+    ad: An AndroidDevice object.
+    Returns: Assert if turning on/off Airplane mode fails.
+
+    """
+    ad.log.debug("Toggling Airplane mode ON.")
+    asserts.assert_true(
+        utils.force_airplane_mode(ad, True),
+        "Can not turn on airplane mode on: %s" % ad.serial)
+    time.sleep(DEFAULT_TIMEOUT)
+    ad.log.debug("Toggling Airplane mode OFF.")
+    asserts.assert_true(
+        utils.force_airplane_mode(ad, False),
+        "Can not turn on airplane mode on: %s" % ad.serial)
+    time.sleep(DEFAULT_TIMEOUT)
+
+
+def toggle_wifi_off_and_on(ad):
+    """Turn OFF and ON WiFi.
+
+    ad: An AndroidDevice object.
+    Returns: Assert if turning off/on WiFi fails.
+
+    """
+    ad.log.debug("Toggling wifi OFF.")
+    wifi_toggle_state(ad, False)
+    time.sleep(DEFAULT_TIMEOUT)
+    ad.log.debug("Toggling wifi ON.")
+    wifi_toggle_state(ad, True)
+    time.sleep(DEFAULT_TIMEOUT)
+
+
+def wifi_forget_network(ad, net_ssid):
+    """Remove configured Wifi network on an android device.
+
+    Args:
+        ad: android_device object for forget network.
+        net_ssid: ssid of network to be forget
+
+    """
+    networks = ad.droid.wifiGetConfiguredNetworks()
+    if not networks:
+        return
+    for n in networks:
+        if net_ssid in n[WifiEnums.SSID_KEY]:
+            ad.droid.wifiForgetNetwork(n['networkId'])
+            try:
+                event = ad.ed.pop_event(wifi_constants.WIFI_FORGET_NW_SUCCESS,
+                                        SHORT_TIMEOUT)
+            except Empty:
+                asserts.fail("Failed to remove network %s." % n)
+
+
+def wifi_test_device_init(ad):
+    """Initializes an android device for wifi testing.
+
+    0. Make sure SL4A connection is established on the android device.
+    1. Disable location service's WiFi scan.
+    2. Turn WiFi on.
+    3. Clear all saved networks.
+    4. Set country code to US.
+    5. Enable WiFi verbose logging.
+    6. Sync device time with computer time.
+    7. Turn off cellular data.
+    8. Turn off ambient display.
+    """
+    utils.require_sl4a((ad, ))
+    ad.droid.wifiScannerToggleAlwaysAvailable(False)
+    msg = "Failed to turn off location service's scan."
+    asserts.assert_true(not ad.droid.wifiScannerIsAlwaysAvailable(), msg)
+    wifi_toggle_state(ad, True)
+    reset_wifi(ad)
+    ad.droid.wifiEnableVerboseLogging(1)
+    msg = "Failed to enable WiFi verbose logging."
+    asserts.assert_equal(ad.droid.wifiGetVerboseLoggingLevel(), 1, msg)
+    # We don't verify the following settings since they are not critical.
+    # Set wpa_supplicant log level to EXCESSIVE.
+    output = ad.adb.shell("wpa_cli -i wlan0 -p -g@android:wpa_wlan0 IFNAME="
+                          "wlan0 log_level EXCESSIVE")
+    ad.log.info("wpa_supplicant log change status: %s", output)
+    utils.sync_device_time(ad)
+    ad.droid.telephonyToggleDataConnection(False)
+    set_wifi_country_code(ad, WifiEnums.CountryCode.US)
+    utils.set_ambient_display(ad, False)
+
+def set_wifi_country_code(ad, country_code):
+    """Sets the wifi country code on the device.
+
+    Args:
+        ad: An AndroidDevice object.
+        country_code: 2 letter ISO country code
+
+    Raises:
+        An RpcException if unable to set the country code.
+    """
+    try:
+        ad.adb.shell("cmd wifi force-country-code enabled %s" % country_code)
+    except ad.adb.AdbError as e:
+        ad.droid.wifiSetCountryCode(WifiEnums.CountryCode.US)
+
+
+def start_wifi_connection_scan(ad):
+    """Starts a wifi connection scan and wait for results to become available.
+
+    Args:
+        ad: An AndroidDevice object.
+    """
+    ad.ed.clear_all_events()
+    ad.droid.wifiStartScan()
+    try:
+        ad.ed.pop_event("WifiManagerScanResultsAvailable", 60)
+    except Empty:
+        asserts.fail("Wi-Fi results did not become available within 60s.")
+
+
+def start_wifi_connection_scan_and_return_status(ad):
+    """
+    Starts a wifi connection scan and wait for results to become available
+    or a scan failure to be reported.
+
+    Args:
+        ad: An AndroidDevice object.
+    Returns:
+        True: if scan succeeded & results are available
+        False: if scan failed
+    """
+    ad.ed.clear_all_events()
+    ad.droid.wifiStartScan()
+    try:
+        events = ad.ed.pop_events(
+            "WifiManagerScan(ResultsAvailable|Failure)", 60)
+    except Empty:
+        asserts.fail(
+            "Wi-Fi scan results/failure did not become available within 60s.")
+    # If there are multiple matches, we check for atleast one success.
+    for event in events:
+        if event["name"] == "WifiManagerScanResultsAvailable":
+            return True
+        elif event["name"] == "WifiManagerScanFailure":
+            ad.log.debug("Scan failure received")
+    return False
+
+
+def start_wifi_connection_scan_and_check_for_network(ad, network_ssid,
+                                                     max_tries=3):
+    """
+    Start connectivity scans & checks if the |network_ssid| is seen in
+    scan results. The method performs a max of |max_tries| connectivity scans
+    to find the network.
+
+    Args:
+        ad: An AndroidDevice object.
+        network_ssid: SSID of the network we are looking for.
+        max_tries: Number of scans to try.
+    Returns:
+        True: if network_ssid is found in scan results.
+        False: if network_ssid is not found in scan results.
+    """
+    for num_tries in range(max_tries):
+        if start_wifi_connection_scan_and_return_status(ad):
+            scan_results = ad.droid.wifiGetScanResults()
+            match_results = match_networks(
+                {WifiEnums.SSID_KEY: network_ssid}, scan_results)
+            if len(match_results) > 0:
+                return True
+    return False
+
+
+def start_wifi_connection_scan_and_ensure_network_found(ad, network_ssid,
+                                                        max_tries=3):
+    """
+    Start connectivity scans & ensure the |network_ssid| is seen in
+    scan results. The method performs a max of |max_tries| connectivity scans
+    to find the network.
+    This method asserts on failure!
+
+    Args:
+        ad: An AndroidDevice object.
+        network_ssid: SSID of the network we are looking for.
+        max_tries: Number of scans to try.
+    """
+    ad.log.info("Starting scans to ensure %s is present", network_ssid)
+    assert_msg = "Failed to find " + network_ssid + " in scan results" \
+        " after " + str(max_tries) + " tries"
+    asserts.assert_true(start_wifi_connection_scan_and_check_for_network(
+        ad, network_ssid, max_tries), assert_msg)
+
+
+def start_wifi_connection_scan_and_ensure_network_not_found(ad, network_ssid,
+                                                            max_tries=3):
+    """
+    Start connectivity scans & ensure the |network_ssid| is not seen in
+    scan results. The method performs a max of |max_tries| connectivity scans
+    to find the network.
+    This method asserts on failure!
+
+    Args:
+        ad: An AndroidDevice object.
+        network_ssid: SSID of the network we are looking for.
+        max_tries: Number of scans to try.
+    """
+    ad.log.info("Starting scans to ensure %s is not present", network_ssid)
+    assert_msg = "Found " + network_ssid + " in scan results" \
+        " after " + str(max_tries) + " tries"
+    asserts.assert_false(start_wifi_connection_scan_and_check_for_network(
+        ad, network_ssid, max_tries), assert_msg)
+
+
+def start_wifi_background_scan(ad, scan_setting):
+    """Starts wifi background scan.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        scan_setting: A dict representing the settings of the scan.
+
+    Returns:
+        If scan was started successfully, event data of success event is returned.
+    """
+    idx = ad.droid.wifiScannerStartBackgroundScan(scan_setting)
+    event = ad.ed.pop_event("WifiScannerScan{}onSuccess".format(idx),
+                            SHORT_TIMEOUT)
+    return event['data']
+
+
+def start_wifi_tethering(ad, ssid, password, band=None, hidden=None):
+    """Starts wifi tethering on an android_device.
+
+    Args:
+        ad: android_device to start wifi tethering on.
+        ssid: The SSID the soft AP should broadcast.
+        password: The password the soft AP should use.
+        band: The band the soft AP should be set on. It should be either
+            WifiEnums.WIFI_CONFIG_APBAND_2G or WifiEnums.WIFI_CONFIG_APBAND_5G.
+        hidden: boolean to indicate if the AP needs to be hidden or not.
+
+    Returns:
+        No return value. Error checks in this function will raise test failure signals
+    """
+    config = {WifiEnums.SSID_KEY: ssid}
+    if password:
+        config[WifiEnums.PWD_KEY] = password
+    if band:
+        config[WifiEnums.AP_BAND_KEY] = band
+    if hidden:
+      config[WifiEnums.HIDDEN_KEY] = hidden
+    asserts.assert_true(
+        ad.droid.wifiSetWifiApConfiguration(config),
+        "Failed to update WifiAp Configuration")
+    ad.droid.wifiStartTrackingTetherStateChange()
+    ad.droid.connectivityStartTethering(tel_defines.TETHERING_WIFI, False)
+    try:
+        ad.ed.pop_event("ConnectivityManagerOnTetheringStarted")
+        ad.ed.wait_for_event("TetherStateChanged",
+                             lambda x: x["data"]["ACTIVE_TETHER"], 30)
+        ad.log.debug("Tethering started successfully.")
+    except Empty:
+        msg = "Failed to receive confirmation of wifi tethering starting"
+        asserts.fail(msg)
+    finally:
+        ad.droid.wifiStopTrackingTetherStateChange()
+
+
+def save_wifi_soft_ap_config(ad, wifi_config, band=None, hidden=None,
+                             security=None, password=None,
+                             channel=None, max_clients=None,
+                             shutdown_timeout_enable=None,
+                             shutdown_timeout_millis=None,
+                             client_control_enable=None,
+                             allowedList=None, blockedList=None):
+    """ Save a soft ap configuration and verified
+    Args:
+        ad: android_device to set soft ap configuration.
+        wifi_config: a soft ap configuration object, at least include SSID.
+        band: specifies the band for the soft ap.
+        hidden: specifies the soft ap need to broadcast its SSID or not.
+        security: specifies the security type for the soft ap.
+        password: specifies the password for the soft ap.
+        channel: specifies the channel for the soft ap.
+        max_clients: specifies the maximum connected client number.
+        shutdown_timeout_enable: specifies the auto shut down enable or not.
+        shutdown_timeout_millis: specifies the shut down timeout value.
+        client_control_enable: specifies the client control enable or not.
+        allowedList: specifies allowed clients list.
+        blockedList: specifies blocked clients list.
+    """
+    if security and password:
+       wifi_config[WifiEnums.SECURITY] = security
+       wifi_config[WifiEnums.PWD_KEY] = password
+    if band:
+        wifi_config[WifiEnums.AP_BAND_KEY] = band
+    if hidden:
+        wifi_config[WifiEnums.HIDDEN_KEY] = hidden
+    if channel and band:
+        wifi_config[WifiEnums.AP_BAND_KEY] = band
+        wifi_config[WifiEnums.AP_CHANNEL_KEY] = channel
+    if max_clients:
+        wifi_config[WifiEnums.AP_MAXCLIENTS_KEY] = max_clients
+    if shutdown_timeout_enable:
+        wifi_config[
+            WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY] = shutdown_timeout_enable
+    if shutdown_timeout_millis:
+        wifi_config[
+            WifiEnums.AP_SHUTDOWNTIMEOUT_KEY] = shutdown_timeout_millis
+    if client_control_enable:
+        wifi_config[WifiEnums.AP_CLIENTCONTROL_KEY] = client_control_enable
+    if allowedList:
+        wifi_config[WifiEnums.AP_ALLOWEDLIST_KEY] = allowedList
+    if blockedList:
+        wifi_config[WifiEnums.AP_BLOCKEDLIST_KEY] = blockedList
+
+    if WifiEnums.AP_CHANNEL_KEY in wifi_config and wifi_config[
+        WifiEnums.AP_CHANNEL_KEY] == 0:
+        del wifi_config[WifiEnums.AP_CHANNEL_KEY]
+
+    if WifiEnums.SECURITY in wifi_config and wifi_config[
+        WifiEnums.SECURITY] == WifiEnums.SoftApSecurityType.OPEN:
+        del wifi_config[WifiEnums.SECURITY]
+        del wifi_config[WifiEnums.PWD_KEY]
+
+    asserts.assert_true(ad.droid.wifiSetWifiApConfiguration(wifi_config),
+                        "Failed to set WifiAp Configuration")
+
+    wifi_ap = ad.droid.wifiGetApConfiguration()
+    asserts.assert_true(
+        wifi_ap[WifiEnums.SSID_KEY] == wifi_config[WifiEnums.SSID_KEY],
+        "Hotspot SSID doesn't match")
+    if WifiEnums.SECURITY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.SECURITY] == wifi_config[WifiEnums.SECURITY],
+            "Hotspot Security doesn't match")
+    if WifiEnums.PWD_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.PWD_KEY] == wifi_config[WifiEnums.PWD_KEY],
+            "Hotspot Password doesn't match")
+
+    if WifiEnums.HIDDEN_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.HIDDEN_KEY] == wifi_config[WifiEnums.HIDDEN_KEY],
+            "Hotspot hidden setting doesn't match")
+
+    if WifiEnums.AP_BAND_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_BAND_KEY] == wifi_config[WifiEnums.AP_BAND_KEY],
+            "Hotspot Band doesn't match")
+    if WifiEnums.AP_CHANNEL_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_CHANNEL_KEY] == wifi_config[
+            WifiEnums.AP_CHANNEL_KEY], "Hotspot Channel doesn't match")
+    if WifiEnums.AP_MAXCLIENTS_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_MAXCLIENTS_KEY] == wifi_config[
+            WifiEnums.AP_MAXCLIENTS_KEY], "Hotspot Max Clients doesn't match")
+    if WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY] == wifi_config[
+            WifiEnums.AP_SHUTDOWNTIMEOUTENABLE_KEY],
+            "Hotspot ShutDown feature flag doesn't match")
+    if WifiEnums.AP_SHUTDOWNTIMEOUT_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_SHUTDOWNTIMEOUT_KEY] == wifi_config[
+            WifiEnums.AP_SHUTDOWNTIMEOUT_KEY],
+            "Hotspot ShutDown timeout setting doesn't match")
+    if WifiEnums.AP_CLIENTCONTROL_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_CLIENTCONTROL_KEY] == wifi_config[
+            WifiEnums.AP_CLIENTCONTROL_KEY],
+            "Hotspot Client control flag doesn't match")
+    if WifiEnums.AP_ALLOWEDLIST_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_ALLOWEDLIST_KEY] == wifi_config[
+            WifiEnums.AP_ALLOWEDLIST_KEY],
+            "Hotspot Allowed List doesn't match")
+    if WifiEnums.AP_BLOCKEDLIST_KEY in wifi_config:
+        asserts.assert_true(
+            wifi_ap[WifiEnums.AP_BLOCKEDLIST_KEY] == wifi_config[
+            WifiEnums.AP_BLOCKEDLIST_KEY],
+            "Hotspot Blocked List doesn't match")
+
+def start_wifi_tethering_saved_config(ad):
+    """ Turn on wifi hotspot with a config that is already saved """
+    ad.droid.wifiStartTrackingTetherStateChange()
+    ad.droid.connectivityStartTethering(tel_defines.TETHERING_WIFI, False)
+    try:
+        ad.ed.pop_event("ConnectivityManagerOnTetheringStarted")
+        ad.ed.wait_for_event("TetherStateChanged",
+                             lambda x: x["data"]["ACTIVE_TETHER"], 30)
+    except:
+        asserts.fail("Didn't receive wifi tethering starting confirmation")
+    finally:
+        ad.droid.wifiStopTrackingTetherStateChange()
+
+
+def stop_wifi_tethering(ad):
+    """Stops wifi tethering on an android_device.
+    Args:
+        ad: android_device to stop wifi tethering on.
+    """
+    ad.droid.wifiStartTrackingTetherStateChange()
+    ad.droid.connectivityStopTethering(tel_defines.TETHERING_WIFI)
+    try:
+        ad.ed.pop_event("WifiManagerApDisabled", 30)
+        ad.ed.wait_for_event("TetherStateChanged",
+                             lambda x: not x["data"]["ACTIVE_TETHER"], 30)
+    except Empty:
+        msg = "Failed to receive confirmation of wifi tethering stopping"
+        asserts.fail(msg)
+    finally:
+        ad.droid.wifiStopTrackingTetherStateChange()
+
+
+def toggle_wifi_and_wait_for_reconnection(ad,
+                                          network,
+                                          num_of_tries=1,
+                                          assert_on_fail=True):
+    """Toggle wifi state and then wait for Android device to reconnect to
+    the provided wifi network.
+
+    This expects the device to be already connected to the provided network.
+
+    Logic steps are
+     1. Ensure that we're connected to the network.
+     2. Turn wifi off.
+     3. Wait for 10 seconds.
+     4. Turn wifi on.
+     5. Wait for the "connected" event, then confirm the connected ssid is the
+        one requested.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network: A dictionary representing the network to await connection. The
+                 dictionary must have the key "SSID".
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        If assert_on_fail is False, function returns True if the toggle was
+        successful, False otherwise. If assert_on_fail is True, no return value.
+    """
+    return _assert_on_fail_handler(
+        _toggle_wifi_and_wait_for_reconnection,
+        assert_on_fail,
+        ad,
+        network,
+        num_of_tries=num_of_tries)
+
+
+def _toggle_wifi_and_wait_for_reconnection(ad, network, num_of_tries=3):
+    """Toggle wifi state and then wait for Android device to reconnect to
+    the provided wifi network.
+
+    This expects the device to be already connected to the provided network.
+
+    Logic steps are
+     1. Ensure that we're connected to the network.
+     2. Turn wifi off.
+     3. Wait for 10 seconds.
+     4. Turn wifi on.
+     5. Wait for the "connected" event, then confirm the connected ssid is the
+        one requested.
+
+    This will directly fail a test if anything goes wrong.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network: A dictionary representing the network to await connection. The
+                 dictionary must have the key "SSID".
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+    """
+    expected_ssid = network[WifiEnums.SSID_KEY]
+    # First ensure that we're already connected to the provided network.
+    verify_con = {WifiEnums.SSID_KEY: expected_ssid}
+    verify_wifi_connection_info(ad, verify_con)
+    # Now toggle wifi state and wait for the connection event.
+    wifi_toggle_state(ad, False)
+    time.sleep(10)
+    wifi_toggle_state(ad, True)
+    ad.droid.wifiStartTrackingStateChange()
+    try:
+        connect_result = None
+        for i in range(num_of_tries):
+            try:
+                connect_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED,
+                                                 30)
+                break
+            except Empty:
+                pass
+        asserts.assert_true(connect_result,
+                            "Failed to connect to Wi-Fi network %s on %s" %
+                            (network, ad.serial))
+        logging.debug("Connection result on %s: %s.", ad.serial,
+                      connect_result)
+        actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
+        asserts.assert_equal(actual_ssid, expected_ssid,
+                             "Connected to the wrong network on %s."
+                             "Expected %s, but got %s." %
+                             (ad.serial, expected_ssid, actual_ssid))
+        logging.info("Connected to Wi-Fi network %s on %s", actual_ssid,
+                     ad.serial)
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
+def wait_for_connect(ad, expected_ssid=None, expected_id=None, tries=2,
+                     assert_on_fail=True):
+    """Wait for a connect event.
+
+    This will directly fail a test if anything goes wrong.
+
+    Args:
+        ad: An Android device object.
+        expected_ssid: SSID of the network to connect to.
+        expected_id: Network Id of the network to connect to.
+        tries: An integer that is the number of times to try before failing.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        Returns a value only if assert_on_fail is false.
+        Returns True if the connection was successful, False otherwise.
+    """
+    return _assert_on_fail_handler(
+        _wait_for_connect, assert_on_fail, ad, expected_ssid, expected_id,
+        tries)
+
+
+def _wait_for_connect(ad, expected_ssid=None, expected_id=None, tries=2):
+    """Wait for a connect event.
+
+    Args:
+        ad: An Android device object.
+        expected_ssid: SSID of the network to connect to.
+        expected_id: Network Id of the network to connect to.
+        tries: An integer that is the number of times to try before failing.
+    """
+    ad.droid.wifiStartTrackingStateChange()
+    try:
+        connect_result = _wait_for_connect_event(
+            ad, ssid=expected_ssid, id=expected_id, tries=tries)
+        asserts.assert_true(connect_result,
+                            "Failed to connect to Wi-Fi network %s" %
+                            expected_ssid)
+        ad.log.debug("Wi-Fi connection result: %s.", connect_result)
+        actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
+        if expected_ssid:
+            asserts.assert_equal(actual_ssid, expected_ssid,
+                                 "Connected to the wrong network")
+        actual_id = connect_result['data'][WifiEnums.NETID_KEY]
+        if expected_id:
+            asserts.assert_equal(actual_id, expected_id,
+                                 "Connected to the wrong network")
+        ad.log.info("Connected to Wi-Fi network %s.", actual_ssid)
+    except Empty:
+        asserts.fail("Failed to start connection process to %s" %
+                     expected_ssid)
+    except Exception as error:
+        ad.log.error("Failed to connect to %s with error %s", expected_ssid,
+                     error)
+        raise signals.TestFailure("Failed to connect to %s network" %
+                                  expected_ssid)
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
+def _wait_for_connect_event(ad, ssid=None, id=None, tries=1):
+    """Wait for a connect event on queue and pop when available.
+
+    Args:
+        ad: An Android device object.
+        ssid: SSID of the network to connect to.
+        id: Network Id of the network to connect to.
+        tries: An integer that is the number of times to try before failing.
+
+    Returns:
+        A dict with details of the connection data, which looks like this:
+        {
+         'time': 1485460337798,
+         'name': 'WifiNetworkConnected',
+         'data': {
+                  'rssi': -27,
+                  'is_24ghz': True,
+                  'mac_address': '02:00:00:00:00:00',
+                  'network_id': 1,
+                  'BSSID': '30:b5:c2:33:d3:fc',
+                  'ip_address': 117483712,
+                  'link_speed': 54,
+                  'supplicant_state': 'completed',
+                  'hidden_ssid': False,
+                  'SSID': 'wh_ap1_2g',
+                  'is_5ghz': False}
+        }
+
+    """
+    conn_result = None
+
+    # If ssid and network id is None, just wait for any connect event.
+    if id is None and ssid is None:
+        for i in range(tries):
+            try:
+                conn_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED, 30)
+                break
+            except Empty:
+                pass
+    else:
+    # If ssid or network id is specified, wait for specific connect event.
+        for i in range(tries):
+            try:
+                conn_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED, 30)
+                if id and conn_result['data'][WifiEnums.NETID_KEY] == id:
+                    break
+                elif ssid and conn_result['data'][WifiEnums.SSID_KEY] == ssid:
+                    break
+            except Empty:
+                pass
+
+    return conn_result
+
+
+def wait_for_disconnect(ad, timeout=10):
+    """Wait for a disconnect event within the specified timeout.
+
+    Args:
+        ad: Android device object.
+        timeout: Timeout in seconds.
+
+    """
+    try:
+        ad.droid.wifiStartTrackingStateChange()
+        event = ad.ed.pop_event("WifiNetworkDisconnected", timeout)
+    except Empty:
+        raise signals.TestFailure("Device did not disconnect from the network")
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
+def ensure_no_disconnect(ad, duration=10):
+    """Ensure that there is no disconnect for the specified duration.
+
+    Args:
+        ad: Android device object.
+        duration: Duration in seconds.
+
+    """
+    try:
+        ad.droid.wifiStartTrackingStateChange()
+        event = ad.ed.pop_event("WifiNetworkDisconnected", duration)
+        raise signals.TestFailure("Device disconnected from the network")
+    except Empty:
+        pass
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
+def connect_to_wifi_network(ad, network, assert_on_fail=True,
+        check_connectivity=True, hidden=False):
+    """Connection logic for open and psk wifi networks.
+
+    Args:
+        ad: AndroidDevice to use for connection
+        network: network info of the network to connect to
+        assert_on_fail: If true, errors from wifi_connect will raise
+                        test failure signals.
+        hidden: Is the Wifi network hidden.
+    """
+    if hidden:
+        start_wifi_connection_scan_and_ensure_network_not_found(
+            ad, network[WifiEnums.SSID_KEY])
+    else:
+        start_wifi_connection_scan_and_ensure_network_found(
+            ad, network[WifiEnums.SSID_KEY])
+    wifi_connect(ad,
+                 network,
+                 num_of_tries=3,
+                 assert_on_fail=assert_on_fail,
+                 check_connectivity=check_connectivity)
+
+
+def connect_to_wifi_network_with_id(ad, network_id, network_ssid):
+    """Connect to the given network using network id and verify SSID.
+
+    Args:
+        network_id: int Network Id of the network.
+        network_ssid: string SSID of the network.
+
+    Returns: True if connect using network id was successful;
+             False otherwise.
+
+    """
+    start_wifi_connection_scan_and_ensure_network_found(ad, network_ssid)
+    wifi_connect_by_id(ad, network_id)
+    connect_data = ad.droid.wifiGetConnectionInfo()
+    connect_ssid = connect_data[WifiEnums.SSID_KEY]
+    ad.log.debug("Expected SSID = %s Connected SSID = %s" %
+                   (network_ssid, connect_ssid))
+    if connect_ssid != network_ssid:
+        return False
+    return True
+
+
+def wifi_connect(ad, network, num_of_tries=1, assert_on_fail=True,
+        check_connectivity=True):
+    """Connect an Android device to a wifi network.
+
+    Initiate connection to a wifi network, wait for the "connected" event, then
+    confirm the connected ssid is the one requested.
+
+    This will directly fail a test if anything goes wrong.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network: A dictionary representing the network to connect to. The
+                 dictionary must have the key "SSID".
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        Returns a value only if assert_on_fail is false.
+        Returns True if the connection was successful, False otherwise.
+    """
+    return _assert_on_fail_handler(
+        _wifi_connect, assert_on_fail, ad, network, num_of_tries=num_of_tries,
+          check_connectivity=check_connectivity)
+
+
+def _wifi_connect(ad, network, num_of_tries=1, check_connectivity=True):
+    """Connect an Android device to a wifi network.
+
+    Initiate connection to a wifi network, wait for the "connected" event, then
+    confirm the connected ssid is the one requested.
+
+    This will directly fail a test if anything goes wrong.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network: A dictionary representing the network to connect to. The
+                 dictionary must have the key "SSID".
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+    """
+    asserts.assert_true(WifiEnums.SSID_KEY in network,
+                        "Key '%s' must be present in network definition." %
+                        WifiEnums.SSID_KEY)
+    ad.droid.wifiStartTrackingStateChange()
+    expected_ssid = network[WifiEnums.SSID_KEY]
+    ad.droid.wifiConnectByConfig(network)
+    ad.log.info("Starting connection process to %s", expected_ssid)
+    try:
+        event = ad.ed.pop_event(wifi_constants.CONNECT_BY_CONFIG_SUCCESS, 30)
+        connect_result = _wait_for_connect_event(
+            ad, ssid=expected_ssid, tries=num_of_tries)
+        asserts.assert_true(connect_result,
+                            "Failed to connect to Wi-Fi network %s on %s" %
+                            (network, ad.serial))
+        ad.log.debug("Wi-Fi connection result: %s.", connect_result)
+        actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
+        asserts.assert_equal(actual_ssid, expected_ssid,
+                             "Connected to the wrong network on %s." %
+                             ad.serial)
+        ad.log.info("Connected to Wi-Fi network %s.", actual_ssid)
+
+        if check_connectivity:
+            internet = validate_connection(ad, DEFAULT_PING_ADDR)
+            if not internet:
+                raise signals.TestFailure("Failed to connect to internet on %s" %
+                                          expected_ssid)
+    except Empty:
+        asserts.fail("Failed to start connection process to %s on %s" %
+                     (network, ad.serial))
+    except Exception as error:
+        ad.log.error("Failed to connect to %s with error %s", expected_ssid,
+                     error)
+        raise signals.TestFailure("Failed to connect to %s network" % network)
+
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
+def wifi_connect_by_id(ad, network_id, num_of_tries=3, assert_on_fail=True):
+    """Connect an Android device to a wifi network using network Id.
+
+    Start connection to the wifi network, with the given network Id, wait for
+    the "connected" event, then verify the connected network is the one requested.
+
+    This will directly fail a test if anything goes wrong.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network_id: Integer specifying the network id of the network.
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        Returns a value only if assert_on_fail is false.
+        Returns True if the connection was successful, False otherwise.
+    """
+    _assert_on_fail_handler(_wifi_connect_by_id, assert_on_fail, ad,
+                            network_id, num_of_tries)
+
+
+def _wifi_connect_by_id(ad, network_id, num_of_tries=1):
+    """Connect an Android device to a wifi network using it's network id.
+
+    Start connection to the wifi network, with the given network id, wait for
+    the "connected" event, then verify the connected network is the one requested.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network_id: Integer specifying the network id of the network.
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+    """
+    ad.droid.wifiStartTrackingStateChange()
+    # Clear all previous events.
+    ad.ed.clear_all_events()
+    ad.droid.wifiConnectByNetworkId(network_id)
+    ad.log.info("Starting connection to network with id %d", network_id)
+    try:
+        event = ad.ed.pop_event(wifi_constants.CONNECT_BY_NETID_SUCCESS, 60)
+        connect_result = _wait_for_connect_event(
+            ad, id=network_id, tries=num_of_tries)
+        asserts.assert_true(connect_result,
+                            "Failed to connect to Wi-Fi network using network id")
+        ad.log.debug("Wi-Fi connection result: %s", connect_result)
+        actual_id = connect_result['data'][WifiEnums.NETID_KEY]
+        asserts.assert_equal(actual_id, network_id,
+                             "Connected to the wrong network on %s."
+                             "Expected network id = %d, but got %d." %
+                             (ad.serial, network_id, actual_id))
+        expected_ssid = connect_result['data'][WifiEnums.SSID_KEY]
+        ad.log.info("Connected to Wi-Fi network %s with %d network id.",
+                     expected_ssid, network_id)
+
+        internet = validate_connection(ad, DEFAULT_PING_ADDR)
+        if not internet:
+            raise signals.TestFailure("Failed to connect to internet on %s" %
+                                      expected_ssid)
+    except Empty:
+        asserts.fail("Failed to connect to network with id %d on %s" %
+                    (network_id, ad.serial))
+    except Exception as error:
+        ad.log.error("Failed to connect to network with id %d with error %s",
+                      network_id, error)
+        raise signals.TestFailure("Failed to connect to network with network"
+                                  " id %d" % network_id)
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
+def wifi_connect_using_network_request(ad, network, network_specifier,
+                                       num_of_tries=3, assert_on_fail=True):
+    """Connect an Android device to a wifi network using network request.
+
+    Trigger a network request with the provided network specifier,
+    wait for the "onMatch" event, ensure that the scan results in "onMatch"
+    event contain the specified network, then simulate the user granting the
+    request with the specified network selected. Then wait for the "onAvailable"
+    network callback indicating successful connection to network.
+
+    This will directly fail a test if anything goes wrong.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network_specifier: A dictionary representing the network specifier to
+                           use.
+        network: A dictionary representing the network to connect to. The
+                 dictionary must have the key "SSID".
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        Returns a value only if assert_on_fail is false.
+        Returns True if the connection was successful, False otherwise.
+    """
+    _assert_on_fail_handler(_wifi_connect_using_network_request, assert_on_fail,
+                            ad, network, network_specifier, num_of_tries)
+
+
+def _wifi_connect_using_network_request(ad, network, network_specifier,
+                                        num_of_tries=3):
+    """Connect an Android device to a wifi network using network request.
+
+    Trigger a network request with the provided network specifier,
+    wait for the "onMatch" event, ensure that the scan results in "onMatch"
+    event contain the specified network, then simulate the user granting the
+    request with the specified network selected. Then wait for the "onAvailable"
+    network callback indicating successful connection to network.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network_specifier: A dictionary representing the network specifier to
+                           use.
+        network: A dictionary representing the network to connect to. The
+                 dictionary must have the key "SSID".
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure.
+    """
+    ad.droid.wifiRequestNetworkWithSpecifier(network_specifier)
+    ad.log.info("Sent network request with %s", network_specifier)
+    # Need a delay here because UI interaction should only start once wifi
+    # starts processing the request.
+    time.sleep(wifi_constants.NETWORK_REQUEST_CB_REGISTER_DELAY_SEC)
+    _wait_for_wifi_connect_after_network_request(ad, network, num_of_tries)
+
+
+def wait_for_wifi_connect_after_network_request(ad, network, num_of_tries=3,
+                                                assert_on_fail=True):
+    """
+    Simulate and verify the connection flow after initiating the network
+    request.
+
+    Wait for the "onMatch" event, ensure that the scan results in "onMatch"
+    event contain the specified network, then simulate the user granting the
+    request with the specified network selected. Then wait for the "onAvailable"
+    network callback indicating successful connection to network.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network: A dictionary representing the network to connect to. The
+                 dictionary must have the key "SSID".
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        Returns a value only if assert_on_fail is false.
+        Returns True if the connection was successful, False otherwise.
+    """
+    _assert_on_fail_handler(_wait_for_wifi_connect_after_network_request,
+                            assert_on_fail, ad, network, num_of_tries)
+
+
+def _wait_for_wifi_connect_after_network_request(ad, network, num_of_tries=3):
+    """
+    Simulate and verify the connection flow after initiating the network
+    request.
+
+    Wait for the "onMatch" event, ensure that the scan results in "onMatch"
+    event contain the specified network, then simulate the user granting the
+    request with the specified network selected. Then wait for the "onAvailable"
+    network callback indicating successful connection to network.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        network: A dictionary representing the network to connect to. The
+                 dictionary must have the key "SSID".
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure.
+    """
+    asserts.assert_true(WifiEnums.SSID_KEY in network,
+                        "Key '%s' must be present in network definition." %
+                        WifiEnums.SSID_KEY)
+    ad.droid.wifiStartTrackingStateChange()
+    expected_ssid = network[WifiEnums.SSID_KEY]
+    ad.droid.wifiRegisterNetworkRequestMatchCallback()
+    # Wait for the platform to scan and return a list of networks
+    # matching the request
+    try:
+        matched_network = None
+        for _ in [0,  num_of_tries]:
+            on_match_event = ad.ed.pop_event(
+                wifi_constants.WIFI_NETWORK_REQUEST_MATCH_CB_ON_MATCH, 60)
+            asserts.assert_true(on_match_event,
+                                "Network request on match not received.")
+            matched_scan_results = on_match_event["data"]
+            ad.log.debug("Network request on match results %s",
+                         matched_scan_results)
+            matched_network = match_networks(
+                {WifiEnums.SSID_KEY: network[WifiEnums.SSID_KEY]},
+                matched_scan_results)
+            if matched_network:
+                break;
+
+        asserts.assert_true(
+            matched_network, "Target network %s not found" % network)
+
+        ad.droid.wifiSendUserSelectionForNetworkRequestMatch(network)
+        ad.log.info("Sent user selection for network request %s",
+                    expected_ssid)
+
+        # Wait for the platform to connect to the network.
+        on_available_event = ad.ed.pop_event(
+            wifi_constants.WIFI_NETWORK_CB_ON_AVAILABLE, 60)
+        asserts.assert_true(on_available_event,
+                            "Network request on available not received.")
+        connected_network = on_available_event["data"]
+        ad.log.info("Connected to network %s", connected_network)
+        asserts.assert_equal(connected_network[WifiEnums.SSID_KEY],
+                             expected_ssid,
+                             "Connected to the wrong network."
+                             "Expected %s, but got %s." %
+                             (network, connected_network))
+    except Empty:
+        asserts.fail("Failed to connect to %s" % expected_ssid)
+    except Exception as error:
+        ad.log.error("Failed to connect to %s with error %s",
+                     (expected_ssid, error))
+        raise signals.TestFailure("Failed to connect to %s network" % network)
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
+def wifi_passpoint_connect(ad, passpoint_network, num_of_tries=1,
+                           assert_on_fail=True):
+    """Connect an Android device to a wifi network.
+
+    Initiate connection to a wifi network, wait for the "connected" event, then
+    confirm the connected ssid is the one requested.
+
+    This will directly fail a test if anything goes wrong.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        passpoint_network: SSID of the Passpoint network to connect to.
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+        assert_on_fail: If True, error checks in this function will raise test
+                        failure signals.
+
+    Returns:
+        If assert_on_fail is False, function returns network id, if the connect was
+        successful, False otherwise. If assert_on_fail is True, no return value.
+    """
+    _assert_on_fail_handler(_wifi_passpoint_connect, assert_on_fail, ad,
+                            passpoint_network, num_of_tries = num_of_tries)
+
+
+def _wifi_passpoint_connect(ad, passpoint_network, num_of_tries=1):
+    """Connect an Android device to a wifi network.
+
+    Initiate connection to a wifi network, wait for the "connected" event, then
+    confirm the connected ssid is the one requested.
+
+    This will directly fail a test if anything goes wrong.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        passpoint_network: SSID of the Passpoint network to connect to.
+        num_of_tries: An integer that is the number of times to try before
+                      delaring failure. Default is 1.
+    """
+    ad.droid.wifiStartTrackingStateChange()
+    expected_ssid = passpoint_network
+    ad.log.info("Starting connection process to passpoint %s", expected_ssid)
+
+    try:
+        connect_result = _wait_for_connect_event(
+            ad, expected_ssid, num_of_tries)
+        asserts.assert_true(connect_result,
+                            "Failed to connect to WiFi passpoint network %s on"
+                            " %s" % (expected_ssid, ad.serial))
+        ad.log.info("Wi-Fi connection result: %s.", connect_result)
+        actual_ssid = connect_result['data'][WifiEnums.SSID_KEY]
+        asserts.assert_equal(actual_ssid, expected_ssid,
+                             "Connected to the wrong network on %s." % ad.serial)
+        ad.log.info("Connected to Wi-Fi passpoint network %s.", actual_ssid)
+
+        internet = validate_connection(ad, DEFAULT_PING_ADDR)
+        if not internet:
+            raise signals.TestFailure("Failed to connect to internet on %s" %
+                                      expected_ssid)
+    except Exception as error:
+        ad.log.error("Failed to connect to passpoint network %s with error %s",
+                      expected_ssid, error)
+        raise signals.TestFailure("Failed to connect to %s passpoint network" %
+                                   expected_ssid)
+
+    finally:
+        ad.droid.wifiStopTrackingStateChange()
+
+
+def delete_passpoint(ad, fqdn):
+    """Delete a required Passpoint configuration."""
+    try:
+        ad.droid.removePasspointConfig(fqdn)
+        return True
+    except Exception as error:
+        ad.log.error("Failed to remove passpoint configuration with FQDN=%s "
+                     "and error=%s" , fqdn, error)
+        return False
+
+
+def start_wifi_single_scan(ad, scan_setting):
+    """Starts wifi single shot scan.
+
+    Args:
+        ad: android_device object to initiate connection on.
+        scan_setting: A dict representing the settings of the scan.
+
+    Returns:
+        If scan was started successfully, event data of success event is returned.
+    """
+    idx = ad.droid.wifiScannerStartScan(scan_setting)
+    event = ad.ed.pop_event("WifiScannerScan%sonSuccess" % idx, SHORT_TIMEOUT)
+    ad.log.debug("Got event %s", event)
+    return event['data']
+
+
+def track_connection(ad, network_ssid, check_connection_count):
+    """Track wifi connection to network changes for given number of counts
+
+    Args:
+        ad: android_device object for forget network.
+        network_ssid: network ssid to which connection would be tracked
+        check_connection_count: Integer for maximum number network connection
+                                check.
+    Returns:
+        True if connection to given network happen, else return False.
+    """
+    ad.droid.wifiStartTrackingStateChange()
+    while check_connection_count > 0:
+        connect_network = ad.ed.pop_event("WifiNetworkConnected", 120)
+        ad.log.info("Connected to network %s", connect_network)
+        if (WifiEnums.SSID_KEY in connect_network['data'] and
+                connect_network['data'][WifiEnums.SSID_KEY] == network_ssid):
+            return True
+        check_connection_count -= 1
+    ad.droid.wifiStopTrackingStateChange()
+    return False
+
+
+def get_scan_time_and_channels(wifi_chs, scan_setting, stime_channel):
+    """Calculate the scan time required based on the band or channels in scan
+    setting
+
+    Args:
+        wifi_chs: Object of channels supported
+        scan_setting: scan setting used for start scan
+        stime_channel: scan time per channel
+
+    Returns:
+        scan_time: time required for completing a scan
+        scan_channels: channel used for scanning
+    """
+    scan_time = 0
+    scan_channels = []
+    if "band" in scan_setting and "channels" not in scan_setting:
+        scan_channels = wifi_chs.band_to_freq(scan_setting["band"])
+    elif "channels" in scan_setting and "band" not in scan_setting:
+        scan_channels = scan_setting["channels"]
+    scan_time = len(scan_channels) * stime_channel
+    for channel in scan_channels:
+        if channel in WifiEnums.DFS_5G_FREQUENCIES:
+            scan_time += 132  #passive scan time on DFS
+    return scan_time, scan_channels
+
+
+def start_wifi_track_bssid(ad, track_setting):
+    """Start tracking Bssid for the given settings.
+
+    Args:
+      ad: android_device object.
+      track_setting: Setting for which the bssid tracking should be started
+
+    Returns:
+      If tracking started successfully, event data of success event is returned.
+    """
+    idx = ad.droid.wifiScannerStartTrackingBssids(
+        track_setting["bssidInfos"], track_setting["apLostThreshold"])
+    event = ad.ed.pop_event("WifiScannerBssid{}onSuccess".format(idx),
+                            SHORT_TIMEOUT)
+    return event['data']
+
+
+def convert_pem_key_to_pkcs8(in_file, out_file):
+    """Converts the key file generated by us to the format required by
+    Android using openssl.
+
+    The input file must have the extension "pem". The output file must
+    have the extension "der".
+
+    Args:
+        in_file: The original key file.
+        out_file: The full path to the converted key file, including
+        filename.
+    """
+    asserts.assert_true(in_file.endswith(".pem"), "Input file has to be .pem.")
+    asserts.assert_true(
+        out_file.endswith(".der"), "Output file has to be .der.")
+    cmd = ("openssl pkcs8 -inform PEM -in {} -outform DER -out {} -nocrypt"
+           " -topk8").format(in_file, out_file)
+    utils.exe_cmd(cmd)
+
+
+def validate_connection(ad, ping_addr=DEFAULT_PING_ADDR, wait_time=15,
+                        ping_gateway=True):
+    """Validate internet connection by pinging the address provided.
+
+    Args:
+        ad: android_device object.
+        ping_addr: address on internet for pinging.
+        wait_time: wait for some time before validating connection
+
+    Returns:
+        ping output if successful, NULL otherwise.
+    """
+    # wait_time to allow for DHCP to complete.
+    for i in range(wait_time):
+        if ad.droid.connectivityNetworkIsConnected():
+            break
+        time.sleep(1)
+    ping = False
+    try:
+        ping = ad.droid.httpPing(ping_addr)
+        ad.log.info("Http ping result: %s.", ping)
+    except:
+        pass
+    if not ping and ping_gateway:
+        ad.log.info("Http ping failed. Pinging default gateway")
+        gw = ad.droid.connectivityGetIPv4DefaultGateway()
+        result = ad.adb.shell("ping -c 6 {}".format(gw))
+        ad.log.info("Default gateway ping result: %s" % result)
+        ping = False if "100% packet loss" in result else True
+    return ping
+
+
+#TODO(angli): This can only verify if an actual value is exactly the same.
+# Would be nice to be able to verify an actual value is one of serveral.
+def verify_wifi_connection_info(ad, expected_con):
+    """Verifies that the information of the currently connected wifi network is
+    as expected.
+
+    Args:
+        expected_con: A dict representing expected key-value pairs for wifi
+            connection. e.g. {"SSID": "test_wifi"}
+    """
+    current_con = ad.droid.wifiGetConnectionInfo()
+    case_insensitive = ["BSSID", "supplicant_state"]
+    ad.log.debug("Current connection: %s", current_con)
+    for k, expected_v in expected_con.items():
+        # Do not verify authentication related fields.
+        if k == "password":
+            continue
+        msg = "Field %s does not exist in wifi connection info %s." % (
+            k, current_con)
+        if k not in current_con:
+            raise signals.TestFailure(msg)
+        actual_v = current_con[k]
+        if k in case_insensitive:
+            actual_v = actual_v.lower()
+            expected_v = expected_v.lower()
+        msg = "Expected %s to be %s, actual %s is %s." % (k, expected_v, k,
+                                                          actual_v)
+        if actual_v != expected_v:
+            raise signals.TestFailure(msg)
+
+
+def check_autoconnect_to_open_network(ad, conn_timeout=WIFI_CONNECTION_TIMEOUT_DEFAULT):
+    """Connects to any open WiFI AP
+     Args:
+         timeout value in sec to wait for UE to connect to a WiFi AP
+     Returns:
+         True if UE connects to WiFi AP (supplicant_state = completed)
+         False if UE fails to complete connection within WIFI_CONNECTION_TIMEOUT time.
+    """
+    if ad.droid.wifiCheckState():
+        return True
+    ad.droid.wifiToggleState()
+    wifi_connection_state = None
+    timeout = time.time() + conn_timeout
+    while wifi_connection_state != "completed":
+        wifi_connection_state = ad.droid.wifiGetConnectionInfo()[
+            'supplicant_state']
+        if time.time() > timeout:
+            ad.log.warning("Failed to connect to WiFi AP")
+            return False
+    return True
+
+
+def expand_enterprise_config_by_phase2(config):
+    """Take an enterprise config and generate a list of configs, each with
+    a different phase2 auth type.
+
+    Args:
+        config: A dict representing enterprise config.
+
+    Returns
+        A list of enterprise configs.
+    """
+    results = []
+    phase2_types = WifiEnums.EapPhase2
+    if config[WifiEnums.Enterprise.EAP] == WifiEnums.Eap.PEAP:
+        # Skip unsupported phase2 types for PEAP.
+        phase2_types = [WifiEnums.EapPhase2.GTC, WifiEnums.EapPhase2.MSCHAPV2]
+    for phase2_type in phase2_types:
+        # Skip a special case for passpoint TTLS.
+        if (WifiEnums.Enterprise.FQDN in config and
+                phase2_type == WifiEnums.EapPhase2.GTC):
+            continue
+        c = dict(config)
+        c[WifiEnums.Enterprise.PHASE2] = phase2_type.value
+        results.append(c)
+    return results
+
+
+def generate_eap_test_name(config, ad=None):
+    """ Generates a test case name based on an EAP configuration.
+
+    Args:
+        config: A dict representing an EAP credential.
+        ad object: Redundant but required as the same param is passed
+                   to test_func in run_generated_tests
+
+    Returns:
+        A string representing the name of a generated EAP test case.
+    """
+    eap = WifiEnums.Eap
+    eap_phase2 = WifiEnums.EapPhase2
+    Ent = WifiEnums.Enterprise
+    name = "test_connect-"
+    eap_name = ""
+    for e in eap:
+        if e.value == config[Ent.EAP]:
+            eap_name = e.name
+            break
+    if "peap0" in config[WifiEnums.SSID_KEY].lower():
+        eap_name = "PEAP0"
+    if "peap1" in config[WifiEnums.SSID_KEY].lower():
+        eap_name = "PEAP1"
+    name += eap_name
+    if Ent.PHASE2 in config:
+        for e in eap_phase2:
+            if e.value == config[Ent.PHASE2]:
+                name += "-{}".format(e.name)
+                break
+    return name
+
+
+def group_attenuators(attenuators):
+    """Groups a list of attenuators into attenuator groups for backward
+    compatibility reasons.
+
+    Most legacy Wi-Fi setups have two attenuators each connected to a separate
+    AP. The new Wi-Fi setup has four attenuators, each connected to one channel
+    on an AP, so two of them are connected to one AP.
+
+    To make the existing scripts work in the new setup, when the script needs
+    to attenuate one AP, it needs to set attenuation on both attenuators
+    connected to the same AP.
+
+    This function groups attenuators properly so the scripts work in both
+    legacy and new Wi-Fi setups.
+
+    Args:
+        attenuators: A list of attenuator objects, either two or four in length.
+
+    Raises:
+        signals.TestFailure is raised if the attenuator list does not have two
+        or four objects.
+    """
+    attn0 = attenuator.AttenuatorGroup("AP0")
+    attn1 = attenuator.AttenuatorGroup("AP1")
+    # Legacy testbed setup has two attenuation channels.
+    num_of_attns = len(attenuators)
+    if num_of_attns == 2:
+        attn0.add(attenuators[0])
+        attn1.add(attenuators[1])
+    elif num_of_attns == 4:
+        attn0.add(attenuators[0])
+        attn0.add(attenuators[1])
+        attn1.add(attenuators[2])
+        attn1.add(attenuators[3])
+    else:
+        asserts.fail(("Either two or four attenuators are required for this "
+                      "test, but found %s") % num_of_attns)
+    return [attn0, attn1]
+
+
+def set_attns(attenuator, attn_val_name, roaming_attn=ROAMING_ATTN):
+    """Sets attenuation values on attenuators used in this test.
+
+    Args:
+        attenuator: The attenuator object.
+        attn_val_name: Name of the attenuation value pair to use.
+        roaming_attn: Dictionary specifying the attenuation params.
+    """
+    logging.info("Set attenuation values to %s", roaming_attn[attn_val_name])
+    try:
+        attenuator[0].set_atten(roaming_attn[attn_val_name][0])
+        attenuator[1].set_atten(roaming_attn[attn_val_name][1])
+        attenuator[2].set_atten(roaming_attn[attn_val_name][2])
+        attenuator[3].set_atten(roaming_attn[attn_val_name][3])
+    except:
+        logging.exception("Failed to set attenuation values %s.",
+                       attn_val_name)
+        raise
+
+def set_attns_steps(attenuators,
+                    atten_val_name,
+                    roaming_attn=ROAMING_ATTN,
+                    steps=10,
+                    wait_time=12):
+    """Set attenuation values on attenuators used in this test. It will change
+    the attenuation values linearly from current value to target value step by
+    step.
+
+    Args:
+        attenuators: The list of attenuator objects that you want to change
+                     their attenuation value.
+        atten_val_name: Name of the attenuation value pair to use.
+        roaming_attn: Dictionary specifying the attenuation params.
+        steps: Number of attenuator changes to reach the target value.
+        wait_time: Sleep time for each change of attenuator.
+    """
+    logging.info("Set attenuation values to %s in %d step(s)",
+            roaming_attn[atten_val_name], steps)
+    start_atten = [attenuator.get_atten() for attenuator in attenuators]
+    target_atten = roaming_attn[atten_val_name]
+    for current_step in range(steps):
+        progress = (current_step + 1) / steps
+        for i, attenuator in enumerate(attenuators):
+            amount_since_start = (target_atten[i] - start_atten[i]) * progress
+            attenuator.set_atten(round(start_atten[i] + amount_since_start))
+        time.sleep(wait_time)
+
+
+def trigger_roaming_and_validate(dut,
+                                 attenuator,
+                                 attn_val_name,
+                                 expected_con,
+                                 roaming_attn=ROAMING_ATTN):
+    """Sets attenuators to trigger roaming and validate the DUT connected
+    to the BSSID expected.
+
+    Args:
+        attenuator: The attenuator object.
+        attn_val_name: Name of the attenuation value pair to use.
+        expected_con: The network information of the expected network.
+        roaming_attn: Dictionary specifying the attenaution params.
+    """
+    expected_con = {
+        WifiEnums.SSID_KEY: expected_con[WifiEnums.SSID_KEY],
+        WifiEnums.BSSID_KEY: expected_con["bssid"],
+    }
+    set_attns_steps(attenuator, attn_val_name, roaming_attn)
+
+    verify_wifi_connection_info(dut, expected_con)
+    expected_bssid = expected_con[WifiEnums.BSSID_KEY]
+    logging.info("Roamed to %s successfully", expected_bssid)
+    if not validate_connection(dut):
+        raise signals.TestFailure("Fail to connect to internet on %s" %
+                                      expected_bssid)
+
+def create_softap_config():
+    """Create a softap config with random ssid and password."""
+    ap_ssid = "softap_" + utils.rand_ascii_str(8)
+    ap_password = utils.rand_ascii_str(8)
+    logging.info("softap setup: %s %s", ap_ssid, ap_password)
+    config = {
+        WifiEnums.SSID_KEY: ap_ssid,
+        WifiEnums.PWD_KEY: ap_password,
+    }
+    return config
+
+def start_softap_and_verify(ad, band):
+    """Bring-up softap and verify AP mode and in scan results.
+
+    Args:
+        band: The band to use for softAP.
+
+    Returns: dict, the softAP config.
+
+    """
+    # Register before start the test.
+    callbackId = ad.dut.droid.registerSoftApCallback()
+    # Check softap info value is default
+    frequency, bandwdith = get_current_softap_info(ad.dut, callbackId, True)
+    asserts.assert_true(frequency == 0, "Softap frequency is not reset")
+    asserts.assert_true(bandwdith == 0, "Softap bandwdith is not reset")
+
+    config = create_softap_config()
+    start_wifi_tethering(ad.dut,
+                         config[WifiEnums.SSID_KEY],
+                         config[WifiEnums.PWD_KEY], band=band)
+    asserts.assert_true(ad.dut.droid.wifiIsApEnabled(),
+                         "SoftAp is not reported as running")
+    start_wifi_connection_scan_and_ensure_network_found(ad.dut_client,
+        config[WifiEnums.SSID_KEY])
+
+    # Check softap info can get from callback succeed and assert value should be
+    # valid.
+    frequency, bandwdith = get_current_softap_info(ad.dut, callbackId, True)
+    asserts.assert_true(frequency > 0, "Softap frequency is not valid")
+    asserts.assert_true(bandwdith > 0, "Softap bandwdith is not valid")
+    # Unregister callback
+    ad.dut.droid.unregisterSoftApCallback(callbackId)
+
+    return config
+
+def wait_for_expected_number_of_softap_clients(ad, callbackId,
+        expected_num_of_softap_clients):
+    """Wait for the number of softap clients to be updated as expected.
+    Args:
+        callbackId: Id of the callback associated with registering.
+        expected_num_of_softap_clients: expected number of softap clients.
+    """
+    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+            callbackId) + wifi_constants.SOFTAP_NUMBER_CLIENTS_CHANGED
+    clientData = ad.ed.pop_event(eventStr, SHORT_TIMEOUT)['data']
+    clientCount = clientData[wifi_constants.SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY]
+    clientMacAddresses = clientData[wifi_constants.SOFTAP_CLIENTS_MACS_CALLBACK_KEY]
+    asserts.assert_equal(clientCount, expected_num_of_softap_clients,
+            "The number of softap clients doesn't match the expected number")
+    asserts.assert_equal(len(clientMacAddresses), expected_num_of_softap_clients,
+                         "The number of mac addresses doesn't match the expected number")
+    for macAddress in clientMacAddresses:
+        asserts.assert_true(checkMacAddress(macAddress), "An invalid mac address was returned")
+
+def checkMacAddress(input):
+    """Validate whether a string is a valid mac address or not.
+
+    Args:
+        input: The string to validate.
+
+    Returns: True/False, returns true for a valid mac address and false otherwise.
+    """
+    macValidationRegex = "[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$"
+    if re.match(macValidationRegex, input.lower()):
+        return True
+    return False
+
+def wait_for_expected_softap_state(ad, callbackId, expected_softap_state):
+    """Wait for the expected softap state change.
+    Args:
+        callbackId: Id of the callback associated with registering.
+        expected_softap_state: The expected softap state.
+    """
+    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+            callbackId) + wifi_constants.SOFTAP_STATE_CHANGED
+    asserts.assert_equal(ad.ed.pop_event(eventStr,
+            SHORT_TIMEOUT)['data'][wifi_constants.
+            SOFTAP_STATE_CHANGE_CALLBACK_KEY],
+            expected_softap_state,
+            "Softap state doesn't match with expected state")
+
+def get_current_number_of_softap_clients(ad, callbackId):
+    """pop up all of softap client updated event from queue.
+    Args:
+        callbackId: Id of the callback associated with registering.
+
+    Returns:
+        If exist aleast callback, returns last updated number_of_softap_clients.
+        Returns None when no any match callback event in queue.
+    """
+    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+            callbackId) + wifi_constants.SOFTAP_NUMBER_CLIENTS_CHANGED
+    events = ad.ed.pop_all(eventStr)
+    for event in events:
+        num_of_clients = event['data'][wifi_constants.
+                SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY]
+    if len(events) == 0:
+        return None
+    return num_of_clients
+
+def get_current_softap_info(ad, callbackId, least_one):
+    """pop up all of softap info changed event from queue.
+    Args:
+        callbackId: Id of the callback associated with registering.
+        least_one: Wait for the info callback event before pop all.
+    Returns:
+        Returns last updated information of softap.
+    """
+    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+            callbackId) + wifi_constants.SOFTAP_INFO_CHANGED
+    ad.log.info("softap info dump from eventStr %s",
+                eventStr)
+    frequency = 0
+    bandwidth = 0
+    if (least_one):
+        event = ad.ed.pop_event(eventStr, SHORT_TIMEOUT)
+        frequency = event['data'][wifi_constants.
+                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+        bandwidth = event['data'][wifi_constants.
+                SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
+        ad.log.info("softap info updated, frequency is %s, bandwidth is %s",
+            frequency, bandwidth)
+
+    events = ad.ed.pop_all(eventStr)
+    for event in events:
+        frequency = event['data'][wifi_constants.
+                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+        bandwidth = event['data'][wifi_constants.
+                SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
+    ad.log.info("softap info, frequency is %s, bandwidth is %s",
+            frequency, bandwidth)
+    return frequency, bandwidth
+
+
+
+def get_ssrdumps(ad, test_name=""):
+    """Pulls dumps in the ssrdump dir
+    Args:
+        ad: android device object.
+        test_name: test case name
+    """
+    logs = ad.get_file_names("/data/vendor/ssrdump/")
+    if logs:
+        ad.log.info("Pulling ssrdumps %s", logs)
+        log_path = os.path.join(ad.log_path, test_name,
+                                "SSRDUMP_%s" % ad.serial)
+        os.makedirs(log_path, exist_ok=True)
+        ad.pull_files(logs, log_path)
+    ad.adb.shell("find /data/vendor/ssrdump/ -type f -delete")
+
+def start_pcap(pcap, wifi_band, test_name):
+    """Start packet capture in monitor mode.
+
+    Args:
+        pcap: packet capture object
+        wifi_band: '2g' or '5g' or 'dual'
+        test_name: test name to be used for pcap file name
+
+    Returns:
+        Dictionary with wifi band as key and the tuple
+        (pcap Process object, log directory) as the value
+    """
+    log_dir = os.path.join(
+        context.get_current_context().get_full_output_path(), 'PacketCapture')
+    os.makedirs(log_dir, exist_ok=True)
+    if wifi_band == 'dual':
+        bands = [BAND_2G, BAND_5G]
+    else:
+        bands = [wifi_band]
+    procs = {}
+    for band in bands:
+        proc = pcap.start_packet_capture(band, log_dir, test_name)
+        procs[band] = (proc, os.path.join(log_dir, test_name))
+    return procs
+
+
+def stop_pcap(pcap, procs, test_status=None):
+    """Stop packet capture in monitor mode.
+
+    Since, the pcap logs in monitor mode can be very large, we will
+    delete them if they are not required. 'test_status' if True, will delete
+    the pcap files. If False, we will keep them.
+
+    Args:
+        pcap: packet capture object
+        procs: dictionary returned by start_pcap
+        test_status: status of the test case
+    """
+    for proc, fname in procs.values():
+        pcap.stop_packet_capture(proc)
+
+    if test_status:
+        shutil.rmtree(os.path.dirname(fname))
+
+def verify_mac_not_found_in_pcap(ad, mac, packets):
+    """Verify that a mac address is not found in the captured packets.
+
+    Args:
+        ad: android device object
+        mac: string representation of the mac address
+        packets: packets obtained by rdpcap(pcap_fname)
+    """
+    for pkt in packets:
+        logging.debug("Packet Summary = %s", pkt.summary())
+        if mac in pkt.summary():
+            asserts.fail("Device %s caught Factory MAC: %s in packet sniffer."
+                         "Packet = %s" % (ad.serial, mac, pkt.show()))
+
+def verify_mac_is_found_in_pcap(ad, mac, packets):
+    """Verify that a mac address is found in the captured packets.
+
+    Args:
+        ad: android device object
+        mac: string representation of the mac address
+        packets: packets obtained by rdpcap(pcap_fname)
+    """
+    for pkt in packets:
+        if mac in pkt.summary():
+            return
+    asserts.fail("Did not find MAC = %s in packet sniffer."
+                 "for device %s" % (mac, ad.serial))
+
+def start_cnss_diags(ads):
+    for ad in ads:
+        start_cnss_diag(ad)
+
+
+def start_cnss_diag(ad):
+    """Start cnss_diag to record extra wifi logs
+
+    Args:
+        ad: android device object.
+    """
+    if ad.model in wifi_constants.DEVICES_USING_LEGACY_PROP:
+        prop = wifi_constants.LEGACY_CNSS_DIAG_PROP
+    else:
+        prop = wifi_constants.CNSS_DIAG_PROP
+    if ad.adb.getprop(prop) != 'true':
+        ad.adb.shell("find /data/vendor/wifi/cnss_diag/wlan_logs/ -type f -delete")
+        ad.adb.shell("setprop %s true" % prop, ignore_status=True)
+
+
+def stop_cnss_diags(ads):
+    for ad in ads:
+        stop_cnss_diag(ad)
+
+
+def stop_cnss_diag(ad):
+    """Stops cnss_diag
+
+    Args:
+        ad: android device object.
+    """
+    if ad.model in wifi_constants.DEVICES_USING_LEGACY_PROP:
+        prop = wifi_constants.LEGACY_CNSS_DIAG_PROP
+    else:
+        prop = wifi_constants.CNSS_DIAG_PROP
+    ad.adb.shell("setprop %s false" % prop, ignore_status=True)
+
+
+def get_cnss_diag_log(ad, test_name=""):
+    """Pulls the cnss_diag logs in the wlan_logs dir
+    Args:
+        ad: android device object.
+        test_name: test case name
+    """
+    logs = ad.get_file_names("/data/vendor/wifi/cnss_diag/wlan_logs/")
+    if logs:
+        ad.log.info("Pulling cnss_diag logs %s", logs)
+        log_path = os.path.join(ad.device_log_path, "CNSS_DIAG_%s" % ad.serial)
+        os.makedirs(log_path, exist_ok=True)
+        ad.pull_files(logs, log_path)
+
+
+LinkProbeResult = namedtuple('LinkProbeResult', (
+    'is_success', 'stdout', 'elapsed_time', 'failure_reason'))
+
+
+def send_link_probe(ad):
+    """Sends a link probe to the currently connected AP, and returns whether the
+    probe succeeded or not.
+
+    Args:
+         ad: android device object
+    Returns:
+        LinkProbeResult namedtuple
+    """
+    stdout = ad.adb.shell('cmd wifi send-link-probe')
+    asserts.assert_false('Error' in stdout or 'Exception' in stdout,
+                         'Exception while sending link probe: ' + stdout)
+
+    is_success = False
+    elapsed_time = None
+    failure_reason = None
+    if 'succeeded' in stdout:
+        is_success = True
+        elapsed_time = next(
+            (int(token) for token in stdout.split() if token.isdigit()), None)
+    elif 'failed with reason' in stdout:
+        failure_reason = next(
+            (int(token) for token in stdout.split() if token.isdigit()), None)
+    else:
+        asserts.fail('Unexpected link probe result: ' + stdout)
+
+    return LinkProbeResult(
+        is_success=is_success, stdout=stdout,
+        elapsed_time=elapsed_time, failure_reason=failure_reason)
+
+
+def send_link_probes(ad, num_probes, delay_sec):
+    """Sends a sequence of link probes to the currently connected AP, and
+    returns whether the probes succeeded or not.
+
+    Args:
+         ad: android device object
+         num_probes: number of probes to perform
+         delay_sec: delay time between probes, in seconds
+    Returns:
+        List[LinkProbeResult] one LinkProbeResults for each probe
+    """
+    logging.info('Sending link probes')
+    results = []
+    for _ in range(num_probes):
+        # send_link_probe() will also fail the test if it sees an exception
+        # in the stdout of the adb shell command
+        result = send_link_probe(ad)
+        logging.info('link probe results: ' + str(result))
+        results.append(result)
+        time.sleep(delay_sec)
+
+    return results
+
+
+def ap_setup(test, index, ap, network, bandwidth=80, channel=6):
+        """Set up the AP with provided network info.
+
+        Args:
+            test: the calling test class object.
+            index: int, index of the AP.
+            ap: access_point object of the AP.
+            network: dict with information of the network, including ssid,
+                     password and bssid.
+            bandwidth: the operation bandwidth for the AP, default 80MHz.
+            channel: the channel number for the AP.
+        Returns:
+            brconfigs: the bridge interface configs
+        """
+        bss_settings = []
+        ssid = network[WifiEnums.SSID_KEY]
+        test.access_points[index].close()
+        time.sleep(5)
+
+        # Configure AP as required.
+        if "password" in network.keys():
+            password = network["password"]
+            security = hostapd_security.Security(
+                security_mode="wpa", password=password)
+        else:
+            security = hostapd_security.Security(security_mode=None, password=None)
+        config = hostapd_ap_preset.create_ap_preset(
+                                                    channel=channel,
+                                                    ssid=ssid,
+                                                    security=security,
+                                                    bss_settings=bss_settings,
+                                                    vht_bandwidth=bandwidth,
+                                                    profile_name='whirlwind',
+                                                    iface_wlan_2g=ap.wlan_2g,
+                                                    iface_wlan_5g=ap.wlan_5g)
+        ap.start_ap(config)
+        logging.info("AP started on channel {} with SSID {}".format(channel, ssid))
+
+
+def turn_ap_off(test, AP):
+    """Bring down hostapd on the Access Point.
+    Args:
+        test: The test class object.
+        AP: int, indicating which AP to turn OFF.
+    """
+    hostapd_2g = test.access_points[AP-1]._aps['wlan0'].hostapd
+    if hostapd_2g.is_alive():
+        hostapd_2g.stop()
+        logging.debug('Turned WLAN0 AP%d off' % AP)
+    hostapd_5g = test.access_points[AP-1]._aps['wlan1'].hostapd
+    if hostapd_5g.is_alive():
+        hostapd_5g.stop()
+        logging.debug('Turned WLAN1 AP%d off' % AP)
+
+
+def turn_ap_on(test, AP):
+    """Bring up hostapd on the Access Point.
+    Args:
+        test: The test class object.
+        AP: int, indicating which AP to turn ON.
+    """
+    hostapd_2g = test.access_points[AP-1]._aps['wlan0'].hostapd
+    if not hostapd_2g.is_alive():
+        hostapd_2g.start(hostapd_2g.config)
+        logging.debug('Turned WLAN0 AP%d on' % AP)
+    hostapd_5g = test.access_points[AP-1]._aps['wlan1'].hostapd
+    if not hostapd_5g.is_alive():
+        hostapd_5g.start(hostapd_5g.config)
+        logging.debug('Turned WLAN1 AP%d on' % AP)
+
+
+def turn_location_off_and_scan_toggle_off(ad):
+    """Turns off wifi location scans."""
+    utils.set_location_service(ad, False)
+    ad.droid.wifiScannerToggleAlwaysAvailable(False)
+    msg = "Failed to turn off location service's scan."
+    asserts.assert_true(not ad.droid.wifiScannerIsAlwaysAvailable(), msg)
+
+
+def set_softap_channel(dut, ap_iface='wlan1', cs_count=10, channel=2462):
+    """ Set SoftAP mode channel
+
+    Args:
+        dut: android device object
+        ap_iface: interface of SoftAP mode.
+        cs_count: how many beacon frames before switch channel, default = 10
+        channel: a wifi channel.
+    """
+    chan_switch_cmd = 'hostapd_cli -i {} chan_switch {} {}'
+    chan_switch_cmd_show = chan_switch_cmd.format(ap_iface,cs_count,channel)
+    dut.log.info('adb shell {}'.format(chan_switch_cmd_show))
+    chan_switch_result = dut.adb.shell(chan_switch_cmd.format(ap_iface,
+                                                              cs_count,
+                                                              channel))
+    if chan_switch_result == 'OK':
+        dut.log.info('switch hotspot channel to {}'.format(channel))
+        return chan_switch_result
+
+    asserts.fail("Failed to switch hotspot channel")
+
diff --git a/acts_tests/acts_contrib/test_utils_tests b/acts_tests/acts_contrib/test_utils_tests
deleted file mode 120000
index 0615a35..0000000
--- a/acts_tests/acts_contrib/test_utils_tests
+++ /dev/null
@@ -1 +0,0 @@
-../../acts/framework/tests/test_utils
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils_tests/__init__.py b/acts_tests/acts_contrib/test_utils_tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils_tests/acts_import_test_utils_test.py b/acts_tests/acts_contrib/test_utils_tests/acts_import_test_utils_test.py
new file mode 100755
index 0000000..1fcb699
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/acts_import_test_utils_test.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2016 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+
+
+class ActsImportTestUtilsTest(unittest.TestCase):
+    """This test class has unit tests for the implementation of everything
+    under acts_contrib.test_utils.*
+    """
+
+    def test_import_successful(self):
+        """ Test to return true if all imports were successful.
+
+        This test will fail if any import was unsuccessful.
+        """
+        try:
+            from acts import utils
+
+            from acts_contrib.test_utils.bt import BleEnum
+            from acts_contrib.test_utils.bt import BluetoothBaseTest
+            from acts_contrib.test_utils.bt import BluetoothCarHfpBaseTest
+            from acts_contrib.test_utils.bt import BtEnum
+            from acts_contrib.test_utils.bt import GattConnectedBaseTest
+            from acts_contrib.test_utils.bt import GattEnum
+            from acts_contrib.test_utils.bt import bt_contacts_utils
+            from acts_contrib.test_utils.bt import bt_gatt_utils
+            from acts_contrib.test_utils.bt import bt_test_utils
+            from acts_contrib.test_utils.bt import native_bt_test_utils
+
+            from acts_contrib.test_utils.car import car_bt_utils
+            from acts_contrib.test_utils.car import car_media_utils
+            from acts_contrib.test_utils.car import car_telecom_utils
+            from acts_contrib.test_utils.car import tel_telecom_utils
+
+            from acts_contrib.test_utils.net import connectivity_const
+            from acts_contrib.test_utils.net import connectivity_const
+
+            from acts_contrib.test_utils.tel import TelephonyBaseTest
+            from acts_contrib.test_utils.tel import tel_atten_utils
+            from acts_contrib.test_utils.tel import tel_data_utils
+            from acts_contrib.test_utils.tel import tel_defines
+            from acts_contrib.test_utils.tel import tel_lookup_tables
+            from acts_contrib.test_utils.tel import tel_subscription_utils
+            from acts_contrib.test_utils.tel import tel_test_utils
+            from acts_contrib.test_utils.tel import tel_video_utils
+            from acts_contrib.test_utils.tel import tel_voice_utils
+
+            from acts_contrib.test_utils.wifi import wifi_constants
+            from acts_contrib.test_utils.wifi import wifi_test_utils
+
+        except Exception:
+            self.fail('Unable to import all supported test_utils')
+
+
+if __name__ == '__main__':
+    unittest.main()
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
new file mode 100644
index 0000000..a00a431
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/audio_analysis_integrationtest.py
@@ -0,0 +1,365 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+# Note: This test has been labelled as an integration test due to its use of
+# real data, and the five to six second execution time.
+import logging
+import numpy
+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
+
+
+class SpectralAnalysisTest(unittest.TestCase):
+    def setUp(self):
+        """Uses the same seed to generate noise for each test."""
+        numpy.random.seed(0)
+
+    def dummy_peak_detection(self, array, window_size):
+        """Detects peaks in an array in simple way.
+
+        A point (i, array[i]) is a peak if array[i] is the maximum among
+        array[i - half_window_size] to array[i + half_window_size].
+        If array[i - half_window_size] to array[i + half_window_size] are all
+        equal, then there is no peak in this window.
+
+        Args:
+            array: The input array to detect peaks in. Array is a list of
+                absolute values of the magnitude of transformed coefficient.
+            window_size: The window to detect peaks.
+
+        Returns:
+            A list of tuples:
+                [(peak_index_1, peak_value_1), (peak_index_2, peak_value_2),
+                ...]
+                where the tuples are sorted by peak values.
+
+        """
+        half_window_size = window_size / 2
+        length = len(array)
+
+        def mid_is_peak(array, mid, left, right):
+            """Checks if value at mid is the largest among left to right.
+
+            Args:
+                array: A list of numbers.
+                mid: The mid index.
+                left: The left index.
+                rigth: The right index.
+
+            Returns:
+                True if array[index] is the maximum among numbers in array
+                    between index [left, right] inclusively.
+
+            """
+            value_mid = array[int(mid)]
+            for index in range(int(left), int(right) + 1):
+                if index == mid:
+                    continue
+                if array[index] >= value_mid:
+                    return False
+            return True
+
+        results = []
+        for mid in range(length):
+            left = max(0, mid - half_window_size)
+            right = min(length - 1, mid + half_window_size)
+            if mid_is_peak(array, mid, left, right):
+                results.append((mid, array[int(mid)]))
+
+        # Sort the peaks by values.
+        return sorted(results, key=lambda x: x[1], reverse=True)
+
+    def test_peak_detection(self):
+        array = [0, 1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 5, 3, 2, 1, 1, 1, 1, 1]
+        result = audio_analysis.peak_detection(array, 4)
+        golden_answer = [(12, 5), (4, 4)]
+        self.assertEqual(result, golden_answer)
+
+    def test_peak_detection_large(self):
+        array = numpy.random.uniform(0, 1, 1000000)
+        window_size = 100
+        logging.debug('Test large array using dummy peak detection')
+        dummy_answer = self.dummy_peak_detection(array, window_size)
+        logging.debug('Test large array using improved peak detection')
+        improved_answer = audio_analysis.peak_detection(array, window_size)
+        logging.debug('Compare the result')
+        self.assertEqual(dummy_answer, improved_answer)
+
+    def test_spectral_analysis(self):
+        rate = 48000
+        length_in_secs = 0.5
+        freq_1 = 490.0
+        freq_2 = 60.0
+        coeff_1 = 1
+        coeff_2 = 0.3
+        samples = length_in_secs * rate
+        noise = numpy.random.standard_normal(int(samples)) * 0.005
+        x = numpy.linspace(0.0, (samples - 1) * 1.0 / rate, samples)
+        y = (coeff_1 * numpy.sin(freq_1 * 2.0 * numpy.pi * x) + coeff_2 *
+             numpy.sin(freq_2 * 2.0 * numpy.pi * x)) + noise
+        results = audio_analysis.spectral_analysis(y, rate)
+        # Results should contains
+        # [(490, 1*k), (60, 0.3*k), (0, 0.1*k)] where 490Hz is the dominant
+        # frequency with coefficient 1, 60Hz is the second dominant frequency
+        # with coefficient 0.3, 0Hz is from Gaussian noise with coefficient
+        # around 0.1. The k constant is resulted from window function.
+        logging.debug('Results: %s', results)
+        self.assertTrue(abs(results[0][0] - freq_1) < 1)
+        self.assertTrue(abs(results[1][0] - freq_2) < 1)
+        self.assertTrue(
+            abs(results[0][1] / results[1][1] - coeff_1 / coeff_2) < 0.01)
+
+    def test_spectral_snalysis_real_data(self):
+        """This unittest checks the spectral analysis works on real data."""
+        file_path = os.path.join(
+            os.path.dirname(__file__), '../../../acts/framework/tests/test_data', '1k_2k.raw')
+        binary = open(file_path, 'rb').read()
+        data = audio_data.AudioRawData(binary, 2, 'S32_LE')
+        saturate_value = audio_data.get_maximum_value_from_sample_format(
+            'S32_LE')
+        golden_frequency = [1000, 2000]
+        for channel in [0, 1]:
+            normalized_signal = audio_analysis.normalize_signal(
+                data.channel_data[channel], saturate_value)
+            spectral = audio_analysis.spectral_analysis(normalized_signal,
+                                                        48000, 0.02)
+            logging.debug('channel %s: %s', channel, spectral)
+            self.assertTrue(
+                abs(spectral[0][0] - golden_frequency[channel]) < 5,
+                'Dominant frequency is not correct')
+
+    def test_not_meaningful_data(self):
+        """Checks that sepectral analysis handles un-meaningful data."""
+        rate = 48000
+        length_in_secs = 0.5
+        samples = length_in_secs * rate
+        noise_amplitude = audio_analysis.MEANINGFUL_RMS_THRESHOLD * 0.5
+        noise = numpy.random.standard_normal(int(samples)) * noise_amplitude
+        results = audio_analysis.spectral_analysis(noise, rate)
+        self.assertEqual([(0, 0)], results)
+
+    def testEmptyData(self):
+        """Checks that sepectral analysis rejects empty data."""
+        with self.assertRaises(audio_analysis.EmptyDataError):
+            results = audio_analysis.spectral_analysis([], 100)
+
+
+class NormalizeTest(unittest.TestCase):
+    def test_normalize(self):
+        y = [1, 2, 3, 4, 5]
+        normalized_y = audio_analysis.normalize_signal(y, 10)
+        expected = numpy.array([0.1, 0.2, 0.3, 0.4, 0.5])
+        for i in range(len(y)):
+            self.assertEqual(expected[i], normalized_y[i])
+
+
+class AnomalyTest(unittest.TestCase):
+    def setUp(self):
+        """Creates a test signal of sine wave."""
+        # Use the same seed for each test case.
+        numpy.random.seed(0)
+
+        self.block_size = 120
+        self.rate = 48000
+        self.freq = 440
+        length_in_secs = 0.25
+        self.samples = length_in_secs * self.rate
+        x = numpy.linspace(0.0, (self.samples - 1) * 1.0 / self.rate,
+                           self.samples)
+        self.y = numpy.sin(self.freq * 2.0 * numpy.pi * x)
+
+    def add_noise(self):
+        """Add noise to the test signal."""
+        noise_amplitude = 0.3
+        noise = numpy.random.standard_normal(len(self.y)) * noise_amplitude
+        self.y = self.y + noise
+
+    def insert_anomaly(self):
+        """Inserts an anomaly to the test signal.
+
+        The anomaly self.anomaly_samples should be created before calling this
+        method.
+
+        """
+        self.anomaly_start_secs = 0.1
+        self.y = numpy.insert(self.y,
+                              int(self.anomaly_start_secs * self.rate),
+                              self.anomaly_samples)
+
+    def generate_skip_anomaly(self):
+        """Skips a section of test signal."""
+        self.anomaly_start_secs = 0.1
+        self.anomaly_duration_secs = 0.005
+        anomaly_append_secs = self.anomaly_start_secs + self.anomaly_duration_secs
+        anomaly_start_index = self.anomaly_start_secs * self.rate
+        anomaly_append_index = anomaly_append_secs * self.rate
+        self.y = numpy.append(self.y[:int(anomaly_start_index)],
+                              self.y[int(anomaly_append_index):])
+
+    def create_constant_anomaly(self, amplitude):
+        """Creates an anomaly of constant samples.
+
+        Args:
+            amplitude: The amplitude of the constant samples.
+
+        """
+        self.anomaly_duration_secs = 0.005
+        self.anomaly_samples = ([amplitude] *
+                                int(self.anomaly_duration_secs * self.rate))
+
+    def run_analysis(self):
+        """Runs the anomaly detection."""
+        self.results = audio_analysis.anomaly_detection(
+            self.y, self.rate, self.freq, self.block_size)
+        logging.debug('Results: %s', self.results)
+
+    def check_no_anomaly(self):
+        """Verifies that there is no anomaly in detection result."""
+        self.run_analysis()
+        self.assertFalse(self.results)
+
+    def check_anomaly(self):
+        """Verifies that there is anomaly in detection result.
+
+        The detection result should contain anomaly time stamps that are
+        close to where anomaly was inserted. There can be multiple anomalies
+        since the detection depends on the block size.
+
+        """
+        self.run_analysis()
+        self.assertTrue(self.results)
+        # Anomaly can be detected as long as the detection window of block size
+        # overlaps with anomaly.
+        expected_detected_range_secs = (
+            self.anomaly_start_secs - float(self.block_size) / self.rate,
+            self.anomaly_start_secs + self.anomaly_duration_secs)
+        for detected_secs in self.results:
+            self.assertTrue(detected_secs <= expected_detected_range_secs[1])
+            self.assertTrue(detected_secs >= expected_detected_range_secs[0])
+
+    def test_good_signal(self):
+        """Sine wave signal with no noise or anomaly."""
+        self.check_no_anomaly()
+
+    def test_good_signal_noise(self):
+        """Sine wave signal with noise."""
+        self.add_noise()
+        self.check_no_anomaly()
+
+    def test_zero_anomaly(self):
+        """Sine wave signal with no noise but with anomaly.
+
+        This test case simulates underrun in digital data where there will be
+        one block of samples with 0 amplitude.
+
+        """
+        self.create_constant_anomaly(0)
+        self.insert_anomaly()
+        self.check_anomaly()
+
+    def test_zero_anomaly_noise(self):
+        """Sine wave signal with noise and anomaly.
+
+        This test case simulates underrun in analog data where there will be
+        one block of samples with amplitudes close to 0.
+
+        """
+        self.create_constant_anomaly(0)
+        self.insert_anomaly()
+        self.add_noise()
+        self.check_anomaly()
+
+    def test_low_constant_anomaly(self):
+        """Sine wave signal with low constant anomaly.
+
+        The anomaly is one block of constant values.
+
+        """
+        self.create_constant_anomaly(0.05)
+        self.insert_anomaly()
+        self.check_anomaly()
+
+    def test_low_constant_anomaly_noise(self):
+        """Sine wave signal with low constant anomaly and noise.
+
+        The anomaly is one block of constant values.
+
+        """
+        self.create_constant_anomaly(0.05)
+        self.insert_anomaly()
+        self.add_noise()
+        self.check_anomaly()
+
+    def test_high_constant_anomaly(self):
+        """Sine wave signal with high constant anomaly.
+
+        The anomaly is one block of constant values.
+
+        """
+        self.create_constant_anomaly(2)
+        self.insert_anomaly()
+        self.check_anomaly()
+
+    def test_high_constant_anomaly_noise(self):
+        """Sine wave signal with high constant anomaly and noise.
+
+        The anomaly is one block of constant values.
+
+        """
+        self.create_constant_anomaly(2)
+        self.insert_anomaly()
+        self.add_noise()
+        self.check_anomaly()
+
+    def test_skipped_anomaly(self):
+        """Sine wave signal with skipped anomaly.
+
+        The anomaly simulates the symptom where a block is skipped.
+
+        """
+        self.generate_skip_anomaly()
+        self.check_anomaly()
+
+    def test_skipped_anomaly_noise(self):
+        """Sine wave signal with skipped anomaly with noise.
+
+        The anomaly simulates the symptom where a block is skipped.
+
+        """
+        self.generate_skip_anomaly()
+        self.add_noise()
+        self.check_anomaly()
+
+    def test_empty_data(self):
+        """Checks that anomaly detection rejects empty data."""
+        self.y = []
+        with self.assertRaises(audio_analysis.EmptyDataError):
+            self.check_anomaly()
+
+
+if __name__ == '__main__':
+    logging.basicConfig(
+        level=logging.DEBUG,
+        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+    unittest.main()
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
new file mode 100644
index 0000000..396e5b8
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/audio_quality_measurement_integrationtest.py
@@ -0,0 +1,272 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2017 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+# Note: This test has been labelled as an integration test due to its use of
+# real data, and the 12+ second execution time. It also generates sine waves
+# during the test, rather than using data that has been pre-calculated.
+
+import math
+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
+
+
+class NoiseLevelTest(unittest.TestCase):
+    def setUp(self):
+        """Uses the same seed to generate noise for each test."""
+        numpy.random.seed(0)
+
+    def test_noise_level(self):
+        # Generates the standard sin wave with standard_noise portion of noise.
+        rate = 48000
+        length_in_secs = 2
+        frequency = 440
+        amplitude = 1
+        standard_noise = 0.05
+
+        wave = []
+        for index in range(0, rate * length_in_secs):
+            phase = 2.0 * math.pi * frequency * float(index) / float(rate)
+            sine_wave = math.sin(phase)
+            noise = standard_noise * numpy.random.standard_normal()
+            wave.append(float(amplitude) * (sine_wave + noise))
+
+        # Calculates the average value after applying teager operator.
+        teager_value_of_wave, length = 0, len(wave)
+        for i in range(1, length - 1):
+            ith_teager_value = abs(wave[i] * wave[i] - wave[i - 1] * wave[i +
+                                                                          1])
+            ith_teager_value *= max(1, abs(wave[i]))
+            teager_value_of_wave += ith_teager_value
+        teager_value_of_wave /= float(length * (amplitude**2))
+
+        noise = audio_quality_measurement.noise_level(
+            amplitude, frequency, rate, teager_value_of_wave)
+
+        self.assertTrue(abs(noise - standard_noise) < 0.01)
+
+
+class ErrorTest(unittest.TestCase):
+    def test_error(self):
+        value1 = [0.2, 0.4, 0.1, 0.01, 0.01, 0.01]
+        value2 = [0.3, 0.3, 0.08, 0.0095, 0.0098, 0.0099]
+        error = [0.5, 0.25, 0.2, 0.05, 0.02, 0.01]
+        for i in range(len(value1)):
+            ret = audio_quality_measurement.error(value1[i], value2[i])
+            self.assertTrue(abs(ret - error[i]) < 0.001)
+
+
+class QualityMeasurementTest(unittest.TestCase):
+    def setUp(self):
+        """Creates a test signal of sine wave."""
+        numpy.random.seed(0)
+
+        self.rate = 48000
+        self.freq = 440
+        self.amplitude = 1
+        length_in_secs = 2
+        self.samples = length_in_secs * self.rate
+        self.y = []
+        for index in range(self.samples):
+            phase = 2.0 * math.pi * self.freq * float(index) / float(self.rate)
+            sine_wave = math.sin(phase)
+            self.y.append(float(self.amplitude) * sine_wave)
+
+    def add_noise(self):
+        """Adds noise to the test signal."""
+        noise_amplitude = 0.01 * self.amplitude
+        for index in range(self.samples):
+            noise = noise_amplitude * numpy.random.standard_normal()
+            self.y[index] += noise
+
+    def generate_delay(self):
+        """Generates some delays during playing."""
+        self.delay_start_time = [0.200, 0.375, 0.513, 0.814, 1.000, 1.300]
+        self.delay_end_time = [0.201, 0.377, 0.516, 0.824, 1.100, 1.600]
+
+        for i in range(len(self.delay_start_time)):
+            start_index = int(self.delay_start_time[i] * self.rate)
+            end_index = int(self.delay_end_time[i] * self.rate)
+            for j in range(start_index, end_index):
+                self.y[j] = 0
+
+    def generate_artifacts_before_playback(self):
+        """Generates artifacts before playing."""
+        silence_before_playback_end_time = 0.2
+        end_index = int(silence_before_playback_end_time * self.rate)
+        for i in range(0, end_index):
+            self.y[i] = 0
+        noise_start_index = int(0.1 * self.rate)
+        noise_end_index = int(0.1005 * self.rate)
+        for i in range(noise_start_index, noise_end_index):
+            self.y[i] = 3 * self.amplitude
+
+    def generate_artifacts_after_playback(self):
+        """Generates artifacts after playing."""
+        silence_after_playback_start_time = int(1.9 * self.rate)
+        noise_start_index = int(1.95 * self.rate)
+        noise_end_index = int((1.95 + 0.02) * self.rate)
+
+        for i in range(silence_after_playback_start_time, self.samples):
+            self.y[i] = 0
+        for i in range(noise_start_index, noise_end_index):
+            self.y[i] = self.amplitude
+
+    def generate_burst_during_playback(self):
+        """Generates bursts during playing."""
+        self.burst_start_time = [0.300, 0.475, 0.613, 0.814, 1.300]
+        self.burst_end_time = [0.301, 0.476, 0.614, 0.815, 1.301]
+
+        for i in range(len(self.burst_start_time)):
+            start_index = int(self.burst_start_time[i] * self.rate)
+            end_index = int(self.burst_end_time[i] * self.rate)
+            for j in range(start_index, end_index):
+                self.y[j] = self.amplitude * (3 + numpy.random.uniform(-1, 1))
+
+    def generate_volume_changing(self):
+        """Generates volume changing during playing."""
+        start_time = [0.300, 1.400]
+        end_time = [0.600, 1.700]
+        for i in range(len(start_time)):
+            start_index = int(start_time[i] * self.rate)
+            end_index = int(end_time[i] * self.rate)
+            for j in range(start_index, end_index):
+                self.y[j] *= 1.4
+        self.volume_changing = [+1, -1, +1, -1]
+        self.volume_changing_time = [0.3, 0.6, 1.4, 1.7]
+
+    def test_good_signal(self):
+        """Sine wave signal with no noise or artifacts."""
+        result = audio_quality_measurement.quality_measurement(self.y,
+                                                               self.rate)
+        self.assertTrue(len(result['artifacts']['noise_before_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['noise_after_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['delay_during_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['burst_during_playback']) == 0)
+        self.assertTrue(len(result['volume_changes']) == 0)
+        self.assertTrue(result['equivalent_noise_level'] < 0.005)
+
+    def test_good_signal_with_noise(self):
+        """Sine wave signal with noise."""
+        self.add_noise()
+        result = audio_quality_measurement.quality_measurement(self.y,
+                                                               self.rate)
+        self.assertTrue(len(result['artifacts']['noise_before_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['noise_after_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['delay_during_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['burst_during_playback']) == 0)
+        self.assertTrue(len(result['volume_changes']) == 0)
+        self.assertTrue(0.009 < result['equivalent_noise_level'] < 0.011)
+
+    def test_delay(self):
+        """Sine wave with delay during playing."""
+        self.generate_delay()
+        result = audio_quality_measurement.quality_measurement(self.y,
+                                                               self.rate)
+        self.assertTrue(len(result['artifacts']['noise_before_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['noise_after_playback']) == 0)
+        self.assertTrue(
+            len(result['volume_changes']) == 2 * len(self.delay_start_time))
+        self.assertTrue(result['equivalent_noise_level'] < 0.005)
+
+        self.assertTrue(
+            len(result['artifacts']['delay_during_playback']) ==
+            len(self.delay_start_time))
+        for i in range(len(result['artifacts']['delay_during_playback'])):
+            delta = abs(result['artifacts']['delay_during_playback'][i][0] -
+                        self.delay_start_time[i])
+            self.assertTrue(delta < 0.001)
+            duration = self.delay_end_time[i] - self.delay_start_time[i]
+            delta = abs(result['artifacts']['delay_during_playback'][i][1] -
+                        duration)
+            self.assertTrue(delta < 0.001)
+
+    def test_artifacts_before_playback(self):
+        """Sine wave with artifacts before playback."""
+        self.generate_artifacts_before_playback()
+        result = audio_quality_measurement.quality_measurement(self.y,
+                                                               self.rate)
+        self.assertTrue(len(result['artifacts']['noise_before_playback']) == 1)
+        delta = abs(result['artifacts']['noise_before_playback'][0][0] - 0.1)
+        self.assertTrue(delta < 0.01)
+        delta = abs(result['artifacts']['noise_before_playback'][0][1] - 0.005)
+        self.assertTrue(delta < 0.004)
+        self.assertTrue(len(result['artifacts']['noise_after_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['delay_during_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['burst_during_playback']) == 0)
+        self.assertTrue(len(result['volume_changes']) == 0)
+        self.assertTrue(result['equivalent_noise_level'] < 0.005)
+
+    def test_artifacts_after_playback(self):
+        """Sine wave with artifacts after playback."""
+        self.generate_artifacts_after_playback()
+        result = audio_quality_measurement.quality_measurement(self.y,
+                                                               self.rate)
+        self.assertTrue(len(result['artifacts']['noise_before_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['noise_after_playback']) == 1)
+        delta = abs(result['artifacts']['noise_after_playback'][0][0] - 1.95)
+        self.assertTrue(delta < 0.01)
+        delta = abs(result['artifacts']['noise_after_playback'][0][1] - 0.02)
+        self.assertTrue(delta < 0.001)
+        self.assertTrue(len(result['artifacts']['delay_during_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['burst_during_playback']) == 0)
+        self.assertTrue(len(result['volume_changes']) == 0)
+        self.assertTrue(result['equivalent_noise_level'] < 0.005)
+
+    def test_burst_during_playback(self):
+        """Sine wave with burst during playback."""
+        self.generate_burst_during_playback()
+        result = audio_quality_measurement.quality_measurement(self.y,
+                                                               self.rate)
+        self.assertTrue(len(result['artifacts']['noise_before_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['noise_after_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['delay_during_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['burst_during_playback']) == 5)
+        self.assertTrue(len(result['volume_changes']) == 10)
+        self.assertTrue(result['equivalent_noise_level'] > 0.02)
+        for i in range(len(result['artifacts']['burst_during_playback'])):
+            delta = abs(self.burst_start_time[i] - result['artifacts'][
+                'burst_during_playback'][i])
+            self.assertTrue(delta < 0.002)
+
+    def test_volume_changing(self):
+        """Sine wave with volume changing during playback."""
+        self.generate_volume_changing()
+        result = audio_quality_measurement.quality_measurement(self.y,
+                                                               self.rate)
+        self.assertTrue(len(result['artifacts']['noise_before_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['noise_after_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['delay_during_playback']) == 0)
+        self.assertTrue(len(result['artifacts']['burst_during_playback']) == 0)
+        self.assertTrue(result['equivalent_noise_level'] < 0.005)
+        self.assertTrue(
+            len(result['volume_changes']) == len(self.volume_changing))
+        for i in range(len(self.volume_changing)):
+            self.assertTrue(
+                abs(self.volume_changing_time[i] - result['volume_changes'][i][
+                    0]) < 0.01)
+            self.assertTrue(
+                self.volume_changing[i] == result['volume_changes'][i][1])
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/__init__.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/__init__.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/__init__.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/instrumentation_command_builder_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/instrumentation_command_builder_test.py
new file mode 100755
index 0000000..584c8bf
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/instrumentation_command_builder_test.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+
+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
+
+
+class InstrumentationCommandBuilderTest(unittest.TestCase):
+
+    def test__runner_and_manifest_package_definition(self):
+        builder = InstrumentationCommandBuilder()
+        builder.set_manifest_package('package')
+        builder.set_runner('runner')
+        call = builder.build()
+        self.assertIn('package/runner', call)
+
+    def test__manifest_package_must_be_defined(self):
+        builder = InstrumentationCommandBuilder()
+
+        with self.assertRaisesRegex(Exception, '.*package cannot be none.*'):
+            builder.build()
+
+    def test__runner_must_be_defined(self):
+        builder = InstrumentationCommandBuilder()
+
+        with self.assertRaisesRegex(Exception, '.*runner cannot be none.*'):
+            builder.build()
+
+    def test__output_as_proto(self):
+        builder = InstrumentationCommandBuilder()
+        builder.set_runner('runner')
+        builder.set_manifest_package('some.manifest.package')
+        builder.set_proto_path()
+
+        call = builder.build()
+        self.assertIn('-f', call)
+
+    def test__proto_flag_with_set_proto_path(self):
+        builder = InstrumentationCommandBuilder()
+        builder.set_runner('runner')
+        builder.set_manifest_package('some.manifest.package')
+        builder.set_proto_path('/some/proto/path')
+
+        call = builder.build()
+        self.assertIn('-f', call)
+        self.assertIn('/some/proto/path', call)
+
+    def test__set_output_as_text_clears_proto_options(self):
+        builder = InstrumentationCommandBuilder()
+        builder.set_runner('runner')
+        builder.set_manifest_package('some.manifest.package')
+        builder.set_proto_path('/some/proto/path')
+        builder.set_output_as_text()
+
+        call = builder.build()
+        self.assertNotIn('-f', call)
+        self.assertNotIn('/some/proto/path', call)
+
+    def test__set_nohup(self):
+        builder = InstrumentationCommandBuilder()
+        builder.set_runner('runner')
+        builder.set_manifest_package('some.manifest.package')
+        builder.set_nohup()
+
+        call = builder.build()
+        self.assertEqual(
+            call, 'nohup am instrument some.manifest.package/runner >> '
+                  '$EXTERNAL_STORAGE/instrumentation_output.txt 2>&1')
+
+    def test__key_value_param_definition(self):
+        builder = InstrumentationCommandBuilder()
+        builder.set_runner('runner')
+        builder.set_manifest_package('some.manifest.package')
+
+        builder.add_key_value_param('my_key_1', 'my_value_1')
+        builder.add_key_value_param('my_key_2', 'my_value_2')
+
+        call = builder.build()
+        self.assertIn('-e my_key_1 my_value_1', call)
+        self.assertIn('-e my_key_2 my_value_2', call)
+
+    def test__flags(self):
+        builder = InstrumentationCommandBuilder()
+        builder.set_runner('runner')
+        builder.set_manifest_package('some.manifest.package')
+
+        builder.add_flag('--flag1')
+        builder.add_flag('--flag2')
+
+        call = builder.build()
+        self.assertIn('--flag1', call)
+        self.assertIn('--flag2', call)
+
+    def test__remove_flags(self):
+        builder = InstrumentationCommandBuilder()
+        builder.set_runner('runner')
+        builder.set_manifest_package('some.manifest.package')
+
+        builder.add_flag('--flag1')
+        builder.add_flag('--flag2')
+        builder.remove_flag('--flag1')
+
+        call = builder.build()
+        self.assertNotIn('--flag1', call)
+        self.assertIn('--flag2', call)
+
+
+class InstrumentationTestCommandBuilderTest(unittest.TestCase):
+    """Test class for
+    acts_contrib/test_utils/instrumentation/instrumentation_call_builder.py
+    """
+
+    def test__test_packages_can_not_be_added_if_classes_were_added_first(self):
+        builder = InstrumentationTestCommandBuilder()
+        builder.add_test_class('some.tests.Class')
+
+        with self.assertRaisesRegex(Exception, '.*only a list of classes.*'):
+            builder.add_test_package('some.tests.package')
+
+    def test__test_classes_can_not_be_added_if_packages_were_added_first(self):
+        builder = InstrumentationTestCommandBuilder()
+        builder.add_test_package('some.tests.package')
+
+        with self.assertRaisesRegex(Exception, '.*only a list of classes.*'):
+            builder.add_test_class('some.tests.Class')
+
+    def test__test_classes_and_test_methods_can_be_combined(self):
+        builder = InstrumentationTestCommandBuilder()
+        builder.set_runner('runner')
+        builder.set_manifest_package('some.manifest.package')
+        builder.add_test_class('some.tests.Class1')
+        builder.add_test_method('some.tests.Class2', 'favoriteTestMethod')
+
+        call = builder.build()
+        self.assertIn('some.tests.Class1', call)
+        self.assertIn('some.tests.Class2', call)
+        self.assertIn('favoriteTestMethod', call)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/power/__init__.py b/acts_tests/acts_contrib/test_utils_tests/power/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/power/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils_tests/power/tel/__init__.py b/acts_tests/acts_contrib/test_utils_tests/power/tel/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/power/tel/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/consume_parameter_test.py b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/consume_parameter_test.py
new file mode 100644
index 0000000..f1b899e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/consume_parameter_test.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+import mobly.config_parser as mobly_config_parser
+import mock_bokeh
+from unittest import mock
+
+
+class ConsumeParameterTest(unittest.TestCase):
+    """ Unit tests for testing the consumption of test name parameters
+      for instances of PowerCellularLabBaseTest
+    """
+    @classmethod
+    def setUpClass(self):
+        from acts_contrib.test_utils.power.cellular.cellular_power_base_test import PowerCellularLabBaseTest as PCBT
+        self.PCBT = PCBT
+        PCBT.log = mock.Mock()
+        PCBT.log_path = ''
+
+    def setUp(self):
+        self.tb_key = 'testbed_configs'
+        test_run_config = mobly_config_parser.TestRunConfig()
+        test_run_config.testbed_name = 'MockTestBed'
+        test_run_config.log_path = '/tmp'
+        test_run_config.summary_writer = mock.MagicMock()
+        test = self.PCBT(test_run_config)
+        self.test = test
+
+    def test_consume_parameter_typical_case(self):
+        """ Tests the typical case: The parameter is available
+            for consumption and it has enough values
+        """
+        parameters = ['param1', 1, 2, 'param2', 3, 'param3', 'value']
+        expected = ['param2', 3]
+        self.test.unpack_userparams(parameters=parameters)
+        try:
+            result = self.test.consume_parameter('param2', 1)
+            self.assertTrue(
+                result == expected,
+                'Consume parameter did not return the expected result')
+        except ValueError as e:
+            self.fail('Error thrown: {}'.format(e))
+
+    def test_consume_parameter_returns_empty_when_parameter_unavailabe(self):
+        """ Tests the case where the requested parameter is unavailable
+            for consumption. In this case, a ValueError should be raised
+        """
+        parameters = ['param1', 1, 2]
+        expected = []
+        self.test.unpack_userparams(parameters=parameters)
+        try:
+            result = self.test.consume_parameter('param2', 1)
+            self.assertTrue(
+                result == expected,
+                'Consume parameter should return empty list for an invalid key'
+            )
+        except ValueError as e:
+            self.fail('Error thrown: {}'.format(e))
+
+    def test_consume_parameter_throws_when_requesting_too_many_parameters(
+            self):
+        """ Tests the case where the requested parameter is available
+            for consumption, but too many values are requested
+        """
+        parameters = ['param1', 1, 2]
+        self.test.unpack_userparams(parameters=parameters)
+        with self.assertRaises(ValueError):
+            self.test.consume_parameter('param1', 3)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/ensure_valid_calibration_table_test.py b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/ensure_valid_calibration_table_test.py
new file mode 100644
index 0000000..b15e892
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/ensure_valid_calibration_table_test.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+import mobly.config_parser as mobly_config_parser
+import mock_bokeh
+from unittest import mock
+
+
+class EnsureValidCalibrationTableTest(unittest.TestCase):
+    """ Unit tests for exercising the logic of ensure_valid_calibration_table
+        for instances of PowerCellularLabBaseTest
+    """
+
+    VALID_CALIBRATION_TABLE = {'1': {'2': {'3': 123, '4': 3.14}}, '2': 45.67}
+
+    INVALID_CALIBRATION_TABLE = invalid = {'1': {'a': 'invalid'}, '2': 1234}
+
+    @classmethod
+    def setUpClass(self):
+        from acts_contrib.test_utils.power.cellular.cellular_power_base_test import PowerCellularLabBaseTest as PCBT
+        self.PCBT = PCBT
+        PCBT.log = mock.Mock()
+        PCBT.log_path = ''
+
+
+    def setUp(self):
+        self.tb_key = 'testbed_configs'
+        test_run_config = mobly_config_parser.TestRunConfig()
+        test_run_config.testbed_name = 'MockTestBed'
+        test_run_config.log_path = '/tmp'
+        test_run_config.summary_writer = mock.MagicMock()
+        test = self.PCBT(test_run_config)
+        self.test = test
+
+
+    def _assert_no_exception(self, func, *args, **kwargs):
+        try:
+            func(*args, **kwargs)
+        except Exception as e:
+            self.fail('Error thrown: {}'.format(e))
+
+    def _assert_calibration_table_passes(self, table):
+        self._assert_no_exception(self.test.ensure_valid_calibration_table, table)
+
+    def _assert_calibration_table_fails(self, table):
+        with self.assertRaises(TypeError):
+            self.test.ensure_valid_calibration_table(table)
+
+    def test_ensure_valid_calibration_table_passes_with_empty_table(self):
+        """ Ensure that empty calibration tables are invalid """
+        self._assert_calibration_table_passes({})
+
+    def test_ensure_valid_calibration_table_passes_with_valid_table(self):
+        """ Ensure that valid calibration tables throw no error """
+        self._assert_calibration_table_passes(self.VALID_CALIBRATION_TABLE)
+
+    def test_ensure_valid_calibration_table_fails_with_invalid_data(self):
+        """ Ensure that calibration tables with invalid entries throw an error """
+        self._assert_calibration_table_fails(self.INVALID_CALIBRATION_TABLE)
+
+    def test_ensure_valid_calibration_table_fails_with_none(self):
+        """ Ensure an exception is thrown if no calibration table is given """
+        self._assert_calibration_table_fails(None)
+
+    def test_ensure_valid_calibration_table_fails_with_invalid_type(self):
+        """ Ensure an exception is thrown if no calibration table is given """
+        self._assert_calibration_table_fails([])
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/init_simulation_test.py b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/init_simulation_test.py
new file mode 100644
index 0000000..ef89239
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/init_simulation_test.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+import mobly.config_parser as mobly_config_parser
+import mock_bokeh
+from acts.controllers.cellular_lib.LteSimulation import LteSimulation
+from acts.controllers.cellular_lib.UmtsSimulation import UmtsSimulation
+from unittest import mock
+
+
+class InitSimulationTest(unittest.TestCase):
+    """ Unit tests for ensuring the simulation is correctly
+        initialized for instances of PowerCellularLabBaseTest
+    """
+    @classmethod
+    def setUpClass(self):
+        from acts_contrib.test_utils.power.cellular.cellular_power_base_test import PowerCellularLabBaseTest as PCBT
+        self.PCBT = PCBT
+        PCBT.log = mock.Mock()
+        PCBT.log_path = ''
+
+    def setUp(self):
+        self.tb_key = 'testbed_configs'
+        test_run_config = mobly_config_parser.TestRunConfig()
+        test_run_config.testbed_name = 'MockTestBed'
+        test_run_config.log_path = '/tmp'
+        test_run_config.summary_writer = mock.MagicMock()
+        test = self.PCBT(test_run_config)
+        self.test = test
+
+    def test_init_simulation_reuses_simulation_if_same_type(self):
+        """ Ensure that a new simulation is not instantiated if
+            the type is the same as the last simulation
+        """
+        mock_lte_sim = mock.Mock(spec=LteSimulation)
+        self.test.unpack_userparams(simulation=mock_lte_sim)
+        try:
+            self.test.init_simulation(self.PCBT.PARAM_SIM_TYPE_LTE)
+        except ValueError as e:
+            self.fail('Error thrown: {}'.format(e))
+        self.assertTrue(self.test.simulation is mock_lte_sim,
+                        'A new simulation was instantiated')
+
+    def test_init_simulation_does_not_reuse_simulation_if_different_type(self):
+        """ Ensure that a new simulation is instantiated if
+            the type is different from the last simulation
+        """
+        self.test.unpack_userparams(simulation=mock.Mock(spec=LteSimulation),
+                               test_params=mock.Mock())
+        try:
+            with mock.patch.object(UmtsSimulation,
+                                   '__init__',
+                                   return_value=None) as mock_init:
+                self.test.init_simulation(self.PCBT.PARAM_SIM_TYPE_UMTS)
+        except Exception as e:
+            self.fail('Error thrown: {}'.format(e))
+        self.assertTrue(mock_init.called,
+                        'A new simulation was not instantiated')
+
+    def test_init_simulation_throws_error_with_invalid_simulation_type(self):
+        """ Ensure that a new simulation is not instantiated if
+            the type is invalid
+        """
+        self.test.unpack_userparams(simulation=mock.Mock(spec=LteSimulation))
+        with self.assertRaises(ValueError):
+            self.test.init_simulation('Invalid simulation type')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/initialize_simulator_test.py b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/initialize_simulator_test.py
new file mode 100644
index 0000000..e495a2a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/initialize_simulator_test.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+import mobly.config_parser as mobly_config_parser
+import mock_bokeh
+from acts.controllers.anritsu_lib import md8475_cellular_simulator as anritsu
+from acts.controllers.rohdeschwarz_lib import cmw500_cellular_simulator as cmw
+from unittest import mock
+
+
+class InitializeSimulatorTest(unittest.TestCase):
+    """ Unit tests for ensuring the simulator is correctly
+        initialized for instances of PowerCellularLabBaseTest
+    """
+    @classmethod
+    def setUpClass(self):
+        from acts_contrib.test_utils.power.cellular.cellular_power_base_test import PowerCellularLabBaseTest as PCBT
+        self.PCBT = PCBT
+        PCBT.log = mock.Mock()
+        PCBT.log_path = ''
+
+    def setUp(self):
+        self.tb_key = 'testbed_configs'
+        test_run_config = mobly_config_parser.TestRunConfig()
+        test_run_config.testbed_name = 'MockTestBed'
+        test_run_config.log_path = '/tmp'
+        test_run_config.summary_writer = mock.MagicMock()
+        test = self.PCBT(test_run_config)
+        self.test = test
+
+    def test_initialize_simulator_md8475_A(self):
+        """ Ensure that an instance of MD8475CellularSimulator
+            is returned when requesting md8475_version A
+        """
+        self.test.unpack_userparams(md8475_version='A', md8475a_ip_address='12345')
+        try:
+            with mock.patch.object(anritsu.MD8475CellularSimulator,
+                                   '__init__',
+                                   return_value=None):
+                result = self.test.initialize_simulator()
+                self.assertTrue(
+                    isinstance(result, anritsu.MD8475CellularSimulator),
+                    'Incorrect simulator type returned for md8475_version A')
+        except ValueError as e:
+            self.fail('Error thrown: {}'.format(e))
+
+    def test_initialize_simulator_md8475_B(self):
+        """ Ensure that an instance of MD8475BCellularSimulator
+            is returned when requesting md8475_version B
+        """
+        self.test.unpack_userparams(md8475_version='B', md8475a_ip_address='12345')
+        try:
+            with mock.patch.object(anritsu.MD8475BCellularSimulator,
+                                   '__init__',
+                                   return_value=None):
+                result = self.test.initialize_simulator()
+                self.assertTrue(
+                    isinstance(result, anritsu.MD8475BCellularSimulator),
+                    'Incorrect simulator type returned for md8475_version B')
+        except ValueError as e:
+            self.fail('Error thrown: {}'.format(e))
+
+    def test_initialize_simulator_cmw500(self):
+        """ Ensure that an instance of CMW500CellularSimulator
+            is returned when requesting cmw500
+        """
+        self.test.unpack_userparams(md8475_version=None,
+                               md8475a_ip_address=None,
+                               cmw500_ip='12345',
+                               cmw500_port='12345')
+        try:
+            with mock.patch.object(cmw.CMW500CellularSimulator,
+                                   '__init__',
+                                   return_value=None):
+                result = self.test.initialize_simulator()
+                self.assertTrue(
+                    isinstance(result, cmw.CMW500CellularSimulator),
+                    'Incorrect simulator type returned for cmw500')
+        except ValueError as e:
+            self.fail('Error thrown: {}'.format(e))
+
+    def test_initialize_simulator_throws_with_missing_configs(self):
+        """ Ensure that an error is raised when initialize_simulator
+            is called with missing configs
+        """
+        self.test.unpack_userparams(md8475_version=None,
+                               md8475a_ip_address=None,
+                               cmw500_ip='12345',
+                               cmw500_port=None)
+        with self.assertRaises(RuntimeError), mock.patch.object(
+                cmw.CMW500CellularSimulator, '__init__', return_value=None):
+            self.test.initialize_simulator()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/mock_bokeh.py b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/mock_bokeh.py
new file mode 100644
index 0000000..7364386
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/mock_bokeh.py
@@ -0,0 +1,8 @@
+import sys
+from mock import Mock
+
+sys.modules['bokeh'] = Mock()
+sys.modules['bokeh.layouts'] = Mock()
+sys.modules['bokeh.models'] = Mock()
+sys.modules['bokeh.models.widgets'] = Mock()
+sys.modules['bokeh.plotting'] = Mock()
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/power_tel_traffic_e2e_test.py b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/power_tel_traffic_e2e_test.py
new file mode 100644
index 0000000..d0c9c0a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/power_tel_traffic_e2e_test.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+import mock_bokeh
+import acts_contrib.test_utils.power.cellular.cellular_traffic_power_test as ctpt
+import mobly.config_parser as mobly_config_parser
+from acts.controllers.cellular_lib.LteSimulation import LteSimulation
+from acts.controllers.rohdeschwarz_lib import cmw500_cellular_simulator as cmw
+from unittest import mock
+
+magic_patch = lambda patched: mock.patch(patched, mock.MagicMock())
+
+
+class PowerTelTrafficE2eTest(unittest.TestCase):
+    """ E2E sanity test for the power cellular traffic tests """
+    @classmethod
+    def setUpClass(cls):
+        cls.PTTT = ctpt.PowerTelTrafficTest
+        cls.PTTT.log = mock.Mock()
+        cls.PTTT.log_path = ''
+
+    @magic_patch('json.load')
+    @magic_patch('builtins.open')
+    @magic_patch('os.chmod')
+    @magic_patch('os.system')
+    @magic_patch('time.sleep')
+    @magic_patch(
+        'acts_contrib.test_utils.power.cellular.cellular_power_base_test.telutils')
+    @magic_patch('acts_contrib.test_utils.power.PowerBaseTest.wutils')
+    @magic_patch(
+        'acts.metrics.loggers.blackbox.BlackboxMetricLogger.for_test_case')
+    @magic_patch(
+        'acts_contrib.test_utils.power.loggers.power_metric_logger.PowerMetricLogger.for_test_case'
+    )
+    def test_e2e(self, *args):
+
+        # Configure the test
+        test_to_mock = 'test_lte_traffic_direction_dlul_blimit_0_0'
+        self.tb_key = 'testbed_configs'
+        test_run_config = mobly_config_parser.TestRunConfig()
+        test_run_config.testbed_name = 'MockTestBed'
+        test_run_config.log_path = '/tmp'
+        test_run_config.summary_writer = mock.MagicMock()
+        test = self.PTTT(test_run_config)
+        mock_android = mock.Mock()
+        mock_android.model = 'coral'
+        test.unpack_userparams(
+            android_devices=[mock_android],
+            monsoons=[mock.Mock()],
+            iperf_servers=[mock.Mock(), mock.Mock()],
+            packet_senders=[mock.Mock(), mock.Mock()],
+            custom_files=[
+                'pass_fail_threshold_coral.json', 'rockbottom_coral.sh'
+            ],
+            simulation=mock.Mock(spec=LteSimulation),
+            mon_freq=5000,
+            mon_duration=0,
+            mon_offset=0,
+            current_test_name=test_to_mock,
+            test_name=test_to_mock,
+            test_result=mock.Mock(),
+            bug_report={},
+            dut_rockbottom=mock.Mock(),
+            start_tel_traffic=mock.Mock(),
+            init_simulation=mock.Mock(),
+            initialize_simulator=mock.Mock(return_value=mock.Mock(
+                spec=cmw.CMW500CellularSimulator)),
+            collect_power_data=mock.Mock(),
+            get_iperf_results=mock.Mock(return_value={
+                'ul': 0,
+                'dl': 0
+            }),
+            pass_fail_check=mock.Mock())
+
+        # Emulate lifecycle
+        test.setup_class()
+        test.setup_test()
+        test.power_tel_traffic_test()
+        test.teardown_test()
+        test.teardown_class()
+
+        self.assertTrue(test.start_tel_traffic.called,
+                        'Start traffic was not called')
+        self.assertTrue(test.init_simulation.called,
+                        'Simulation was not initialized')
+        self.assertTrue(test.initialize_simulator.called,
+                        'Simulator was not initialized')
+        self.assertTrue(test.collect_power_data.called,
+                        'Power data was not collected')
+        self.assertTrue(test.get_iperf_results.called,
+                        'Did not get iperf results')
+        self.assertTrue(test.pass_fail_check.called,
+                        'Pass/Fail check was not performed')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/save_summary_to_file_test.py b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/save_summary_to_file_test.py
new file mode 100644
index 0000000..e9563af
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/power/tel/lab/save_summary_to_file_test.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+import mobly.config_parser as mobly_config_parser
+import mock_bokeh
+from acts.controllers.cellular_lib.LteSimulation import LteSimulation
+from unittest import mock
+from unittest.mock import mock_open
+
+
+class SaveSummaryToFileTest(unittest.TestCase):
+    """ Unit tests for testing the save summary functionality for
+        instances of PowerCellularLabBaseTest
+    """
+
+    @classmethod
+    def setUpClass(self):
+        from acts_contrib.test_utils.power.cellular.cellular_power_base_test import PowerCellularLabBaseTest as PCBT
+        self.PCBT = PCBT
+        PCBT.log = mock.Mock()
+        PCBT.log_path = ''
+
+    def setUp(self):
+        self.tb_key = 'testbed_configs'
+        test_run_config = mobly_config_parser.TestRunConfig()
+        test_run_config.testbed_name = 'MockTestBed'
+        test_run_config.log_path = '/tmp'
+        test_run_config.summary_writer = mock.MagicMock()
+        test = self.PCBT(test_run_config)
+        self.test = test
+
+    def test_save_summary_to_file(self):
+        """ Ensure that a new file is written when saving
+            the test summary
+        """
+        self.test.unpack_userparams(simulation=mock.Mock(spec=LteSimulation))
+        m = mock_open()
+        with mock.patch('builtins.open', m, create=False):
+            self.test.save_summary_to_file()
+        self.assertTrue(m.called, 'Test summary was not written to output')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/tests/google/ble/api/BleAdvertiseApiTest.py b/acts_tests/tests/google/ble/api/BleAdvertiseApiTest.py
index 26c459f..c044b4d 100644
--- a/acts_tests/tests/google/ble/api/BleAdvertiseApiTest.py
+++ b/acts_tests/tests/google/ble/api/BleAdvertiseApiTest.py
@@ -22,12 +22,12 @@
 
 from acts.controllers.sl4a_lib import rpc_client
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import adv_fail
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_tx_powers
-from acts.test_utils.bt.bt_constants import java_integer
+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 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 java_integer
 
 
 class BleAdvertiseVerificationError(Exception):
diff --git a/acts_tests/tests/google/ble/api/BleScanApiTest.py b/acts_tests/tests/google/ble/api/BleScanApiTest.py
index 06f2362..7e824f4 100644
--- a/acts_tests/tests/google/ble/api/BleScanApiTest.py
+++ b/acts_tests/tests/google/ble/api/BleScanApiTest.py
@@ -22,12 +22,12 @@
 
 from acts.controllers.sl4a_lib import rpc_client
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_scan_settings_callback_types
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_result_types
-from acts.test_utils.bt.bt_constants import ble_scan_settings_report_delay_milli_seconds
-from acts.test_utils.bt.bt_constants import ble_uuids
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_callback_types
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_result_types
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_report_delay_milli_seconds
+from acts_contrib.test_utils.bt.bt_constants import ble_uuids
 
 
 class BleScanResultsError(Exception):
diff --git a/acts_tests/tests/google/ble/api/GattApiTest.py b/acts_tests/tests/google/ble/api/GattApiTest.py
index cc87979..f13809f 100644
--- a/acts_tests/tests/google/ble/api/GattApiTest.py
+++ b/acts_tests/tests/google/ble/api/GattApiTest.py
@@ -19,8 +19,8 @@
 
 from acts.controllers.sl4a_lib import rpc_client
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
 
 
 class GattApiTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/beacon_tests/BeaconSwarmTest.py b/acts_tests/tests/google/ble/beacon_tests/BeaconSwarmTest.py
index 0df9a7b..a084794 100644
--- a/acts_tests/tests/google/ble/beacon_tests/BeaconSwarmTest.py
+++ b/acts_tests/tests/google/ble/beacon_tests/BeaconSwarmTest.py
@@ -22,17 +22,17 @@
 
 import threading
 
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.BleEnum import AdvertiseSettingsAdvertiseMode
-from acts.test_utils.bt.BleEnum import ScanSettingsScanMode
-from acts.test_utils.bt.bt_test_utils import adv_succ
-from acts.test_utils.bt.bt_test_utils import batch_scan_result
-from acts.test_utils.bt.bt_test_utils import scan_result
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BleEnum import AdvertiseSettingsAdvertiseMode
+from acts_contrib.test_utils.bt.BleEnum import ScanSettingsScanMode
+from acts_contrib.test_utils.bt.bt_test_utils import adv_succ
+from acts_contrib.test_utils.bt.bt_test_utils import batch_scan_result
+from acts_contrib.test_utils.bt.bt_test_utils import scan_result
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
 
 
 class BeaconSwarmTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/bt5/AdvertisingSetTest.py b/acts_tests/tests/google/ble/bt5/AdvertisingSetTest.py
index de4192f..0245b8d 100644
--- a/acts_tests/tests/google/ble/bt5/AdvertisingSetTest.py
+++ b/acts_tests/tests/google/ble/bt5/AdvertisingSetTest.py
@@ -26,18 +26,18 @@
 from acts.asserts import assert_equal
 from acts.asserts import assert_true
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import advertising_set_started
-from acts.test_utils.bt.bt_constants import advertising_set_stopped
-from acts.test_utils.bt.bt_constants import advertising_set_enabled
-from acts.test_utils.bt.bt_constants import advertising_set_data_set
-from acts.test_utils.bt.bt_constants import advertising_set_scan_response_set
-from acts.test_utils.bt.bt_constants import advertising_set_parameters_update
-from acts.test_utils.bt.bt_constants import advertising_set_periodic_parameters_updated
-from acts.test_utils.bt.bt_constants import advertising_set_periodic_data_set
-from acts.test_utils.bt.bt_constants import advertising_set_periodic_enable
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_started
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_stopped
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_enabled
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_data_set
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_scan_response_set
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_parameters_update
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_periodic_parameters_updated
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_periodic_data_set
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_periodic_enable
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
 from acts import signals
 
 
diff --git a/acts_tests/tests/google/ble/bt5/Bt5ScanTest.py b/acts_tests/tests/google/ble/bt5/Bt5ScanTest.py
index e2c9c83..496bbf9 100644
--- a/acts_tests/tests/google/ble/bt5/Bt5ScanTest.py
+++ b/acts_tests/tests/google/ble/bt5/Bt5ScanTest.py
@@ -25,17 +25,17 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_scan_settings_phys
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import batch_scan_result
-from acts.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_constants import scan_result
-from acts.test_utils.bt.bt_constants import advertising_set_on_own_address_read
-from acts.test_utils.bt.bt_constants import advertising_set_started
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_phys
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import batch_scan_result
+from acts_contrib.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_on_own_address_read
+from acts_contrib.test_utils.bt.bt_constants import advertising_set_started
 from acts import signals
 
 
diff --git a/acts_tests/tests/google/ble/bt5/PhyTest.py b/acts_tests/tests/google/ble/bt5/PhyTest.py
index 0b1ecfa..69487b2 100644
--- a/acts_tests/tests/google/ble/bt5/PhyTest.py
+++ b/acts_tests/tests/google/ble/bt5/PhyTest.py
@@ -20,11 +20,11 @@
 from queue import Empty
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
-from acts.test_utils.bt.bt_constants import gatt_connection_priority
-from acts.test_utils.bt.bt_constants import gatt_event
-from acts.test_utils.bt.bt_constants import gatt_phy
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
+from acts_contrib.test_utils.bt.bt_constants import gatt_connection_priority
+from acts_contrib.test_utils.bt.bt_constants import gatt_event
+from acts_contrib.test_utils.bt.bt_constants import gatt_phy
 from acts import signals
 
 CONNECTION_PRIORITY_HIGH = gatt_connection_priority['high']
diff --git a/acts_tests/tests/google/ble/concurrency/ConcurrentBleAdvertisementDiscoveryTest.py b/acts_tests/tests/google/ble/concurrency/ConcurrentBleAdvertisementDiscoveryTest.py
index c38fc93..2e2be83 100644
--- a/acts_tests/tests/google/ble/concurrency/ConcurrentBleAdvertisementDiscoveryTest.py
+++ b/acts_tests/tests/google/ble/concurrency/ConcurrentBleAdvertisementDiscoveryTest.py
@@ -24,21 +24,21 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_callback_types
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import adv_succ
-from acts.test_utils.bt.bt_constants import scan_result
-from acts.test_utils.bt.bt_test_utils import BtTestUtilsError
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.bt.bt_test_utils import get_advanced_droid_list
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_test_utils import setup_n_advertisements
-from acts.test_utils.bt.bt_test_utils import take_btsnoop_logs
-from acts.test_utils.bt.bt_test_utils import teardown_n_advertisements
-from acts.test_utils.bt.bt_test_utils import scan_and_verify_n_advertisements
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_callback_types
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_test_utils import BtTestUtilsError
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_test_utils import get_advanced_droid_list
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import setup_n_advertisements
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.bt_test_utils import teardown_n_advertisements
+from acts_contrib.test_utils.bt.bt_test_utils import scan_and_verify_n_advertisements
 
 
 class ConcurrentBleAdvertisementDiscoveryTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/concurrency/ConcurrentBleAdvertisingTest.py b/acts_tests/tests/google/ble/concurrency/ConcurrentBleAdvertisingTest.py
index 09c6cd3..7fd0d52 100644
--- a/acts_tests/tests/google/ble/concurrency/ConcurrentBleAdvertisingTest.py
+++ b/acts_tests/tests/google/ble/concurrency/ConcurrentBleAdvertisingTest.py
@@ -24,21 +24,21 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import BtTestUtilsError
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_callback_types
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import adv_succ
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.bt.bt_test_utils import get_advanced_droid_list
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_test_utils import scan_and_verify_n_advertisements
-from acts.test_utils.bt.bt_constants import scan_result
-from acts.test_utils.bt.bt_test_utils import setup_n_advertisements
-from acts.test_utils.bt.bt_test_utils import take_btsnoop_logs
-from acts.test_utils.bt.bt_test_utils import teardown_n_advertisements
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import BtTestUtilsError
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_callback_types
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_test_utils import get_advanced_droid_list
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import scan_and_verify_n_advertisements
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_test_utils import setup_n_advertisements
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.bt_test_utils import teardown_n_advertisements
 
 
 class ConcurrentBleAdvertisingTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/concurrency/ConcurrentBleScanningTest.py b/acts_tests/tests/google/ble/concurrency/ConcurrentBleScanningTest.py
index 512aed8..3905bdb 100644
--- a/acts_tests/tests/google/ble/concurrency/ConcurrentBleScanningTest.py
+++ b/acts_tests/tests/google/ble/concurrency/ConcurrentBleScanningTest.py
@@ -23,16 +23,16 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_callback_types
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import adv_succ
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_constants import scan_failed
-from acts.test_utils.bt.bt_constants import scan_result
-from acts.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_callback_types
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_constants import scan_failed
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
 
 
 class ConcurrentBleScanningTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/concurrency/ConcurrentGattConnectTest.py b/acts_tests/tests/google/ble/concurrency/ConcurrentGattConnectTest.py
index ec5e09c..6a5b861 100644
--- a/acts_tests/tests/google/ble/concurrency/ConcurrentGattConnectTest.py
+++ b/acts_tests/tests/google/ble/concurrency/ConcurrentGattConnectTest.py
@@ -23,19 +23,19 @@
 import concurrent.futures
 import threading
 import time
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import bt_profile_constants
-from acts.test_utils.bt.bt_constants import gatt_characteristic
-from acts.test_utils.bt.bt_constants import gatt_characteristic_value_format
-from acts.test_utils.bt.bt_constants import gatt_char_desc_uuids
-from acts.test_utils.bt.bt_constants import gatt_descriptor
-from acts.test_utils.bt.bt_constants import gatt_service_types
-from acts.test_utils.bt.bt_constants import scan_result
-from acts.test_utils.bt.bt_gatt_utils import run_continuous_write_descriptor
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_connection
-from acts.test_utils.bt.gatts_lib import GattServerLib
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import bt_profile_constants
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+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
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_service_types
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_gatt_utils import run_continuous_write_descriptor
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_connection
+from acts_contrib.test_utils.bt.gatts_lib import GattServerLib
 from acts.test_decorators import test_tracker_info
 
 service_uuid = '0000a00a-0000-1000-8000-00805f9b34fb'
diff --git a/acts_tests/tests/google/ble/conn_oriented_chan/BleCoc2ConnTest.py b/acts_tests/tests/google/ble/conn_oriented_chan/BleCoc2ConnTest.py
index 353f507..70dcb89 100644
--- a/acts_tests/tests/google/ble/conn_oriented_chan/BleCoc2ConnTest.py
+++ b/acts_tests/tests/google/ble/conn_oriented_chan/BleCoc2ConnTest.py
@@ -24,21 +24,21 @@
 from acts import utils
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_coc_test_utils import orchestrate_coc_connection
-from acts.test_utils.bt.bt_coc_test_utils import do_multi_connection_throughput
-from acts.test_utils.bt.bt_constants import default_le_data_length
-from acts.test_utils.bt.bt_constants import default_bluetooth_socket_timeout_ms
-from acts.test_utils.bt.bt_constants import l2cap_coc_header_size
-from acts.test_utils.bt.bt_constants import l2cap_max_inactivity_delay_after_disconnect
-from acts.test_utils.bt.bt_constants import le_connection_event_time_step_ms
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.bt.bt_test_utils import kill_bluetooth_process
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.bt_test_utils import take_btsnoop_logs
-from acts.test_utils.bt.bt_test_utils import write_read_verify_data
-from acts.test_utils.bt.bt_test_utils import verify_server_and_client_connected
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_coc_test_utils import orchestrate_coc_connection
+from acts_contrib.test_utils.bt.bt_coc_test_utils import do_multi_connection_throughput
+from acts_contrib.test_utils.bt.bt_constants import default_le_data_length
+from acts_contrib.test_utils.bt.bt_constants import default_bluetooth_socket_timeout_ms
+from acts_contrib.test_utils.bt.bt_constants import l2cap_coc_header_size
+from acts_contrib.test_utils.bt.bt_constants import l2cap_max_inactivity_delay_after_disconnect
+from acts_contrib.test_utils.bt.bt_constants import le_connection_event_time_step_ms
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.bt.bt_test_utils import kill_bluetooth_process
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.bt_test_utils import write_read_verify_data
+from acts_contrib.test_utils.bt.bt_test_utils import verify_server_and_client_connected
 
 
 class BleCoc2ConnTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/conn_oriented_chan/BleCocTest.py b/acts_tests/tests/google/ble/conn_oriented_chan/BleCocTest.py
index 166b848..9e95dcb 100644
--- a/acts_tests/tests/google/ble/conn_oriented_chan/BleCocTest.py
+++ b/acts_tests/tests/google/ble/conn_oriented_chan/BleCocTest.py
@@ -24,19 +24,19 @@
 from acts import utils
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_coc_test_utils import orchestrate_coc_connection
-from acts.test_utils.bt.bt_coc_test_utils import do_multi_connection_throughput
-from acts.test_utils.bt.bt_constants import default_le_data_length
-from acts.test_utils.bt.bt_constants import l2cap_coc_header_size
-from acts.test_utils.bt.bt_constants import l2cap_max_inactivity_delay_after_disconnect
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.bt.bt_test_utils import kill_bluetooth_process
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.bt_test_utils import take_btsnoop_logs
-from acts.test_utils.bt.bt_test_utils import write_read_verify_data
-from acts.test_utils.bt.bt_test_utils import verify_server_and_client_connected
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_coc_test_utils import orchestrate_coc_connection
+from acts_contrib.test_utils.bt.bt_coc_test_utils import do_multi_connection_throughput
+from acts_contrib.test_utils.bt.bt_constants import default_le_data_length
+from acts_contrib.test_utils.bt.bt_constants import l2cap_coc_header_size
+from acts_contrib.test_utils.bt.bt_constants import l2cap_max_inactivity_delay_after_disconnect
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.bt.bt_test_utils import kill_bluetooth_process
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.bt_test_utils import write_read_verify_data
+from acts_contrib.test_utils.bt.bt_test_utils import verify_server_and_client_connected
 
 
 class BleCocTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/examples/BleExamplesTest.py b/acts_tests/tests/google/ble/examples/BleExamplesTest.py
index 1ced2db..a9ec058 100644
--- a/acts_tests/tests/google/ble/examples/BleExamplesTest.py
+++ b/acts_tests/tests/google/ble/examples/BleExamplesTest.py
@@ -20,11 +20,11 @@
 import pprint
 
 from acts.controllers import android_device
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import adv_succ
-from acts.test_utils.bt.bt_constants import scan_result
-from acts.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
 
 
 class BleExamplesTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/examples/GattServerExampleTest.py b/acts_tests/tests/google/ble/examples/GattServerExampleTest.py
index e1f6476..7ffcebb 100644
--- a/acts_tests/tests/google/ble/examples/GattServerExampleTest.py
+++ b/acts_tests/tests/google/ble/examples/GattServerExampleTest.py
@@ -14,13 +14,13 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import gatt_characteristic
-from acts.test_utils.bt.bt_constants import gatt_descriptor
-from acts.test_utils.bt.bt_constants import gatt_service_types
-from acts.test_utils.bt.bt_constants import gatt_characteristic_value_format
-from acts.test_utils.bt.bt_constants import gatt_char_desc_uuids
-from acts.test_utils.bt.gatts_lib import GattServerLib
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_service_types
+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
+from acts_contrib.test_utils.bt.gatts_lib import GattServerLib
 
 service_uuid = '0000a00a-0000-1000-8000-00805f9b34fb'
 characteristic_uuid = 'aa7edd5a-4d1d-4f0e-883a-d145616a1630'
diff --git a/acts_tests/tests/google/ble/filtering/FilteringTest.py b/acts_tests/tests/google/ble/filtering/FilteringTest.py
index d1bdc39..0e898c8 100644
--- a/acts_tests/tests/google/ble/filtering/FilteringTest.py
+++ b/acts_tests/tests/google/ble/filtering/FilteringTest.py
@@ -20,18 +20,18 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_tx_powers
-from acts.test_utils.bt.bt_constants import java_integer
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import small_timeout
-from acts.test_utils.bt.bt_constants import adv_fail
-from acts.test_utils.bt.bt_constants import adv_succ
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+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 java_integer
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import small_timeout
+from acts_contrib.test_utils.bt.bt_constants import adv_fail
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_constants import scan_result
 
 
 class FilteringTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/filtering/UniqueFilteringTest.py b/acts_tests/tests/google/ble/filtering/UniqueFilteringTest.py
index c2e837c..fa20a39 100644
--- a/acts_tests/tests/google/ble/filtering/UniqueFilteringTest.py
+++ b/acts_tests/tests/google/ble/filtering/UniqueFilteringTest.py
@@ -25,14 +25,14 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.bt.bt_constants import adv_succ
-from acts.test_utils.bt.bt_constants import batch_scan_result
-from acts.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_constants import batch_scan_result
+from acts_contrib.test_utils.bt.bt_constants import scan_result
 
 
 class UniqueFilteringTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/gatt/GattConnectTest.py b/acts_tests/tests/google/ble/gatt/GattConnectTest.py
index 52f3601..ebe5714 100644
--- a/acts_tests/tests/google/ble/gatt/GattConnectTest.py
+++ b/acts_tests/tests/google/ble/gatt/GattConnectTest.py
@@ -22,31 +22,31 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_match_nums
-from acts.test_utils.bt.bt_constants import bt_profile_constants
-from acts.test_utils.bt.bt_constants import gatt_characteristic
-from acts.test_utils.bt.bt_constants import gatt_descriptor
-from acts.test_utils.bt.bt_constants import gatt_service_types
-from acts.test_utils.bt.bt_constants import gatt_cb_err
-from acts.test_utils.bt.bt_constants import gatt_cb_strings
-from acts.test_utils.bt.bt_constants import gatt_connection_state
-from acts.test_utils.bt.bt_constants import gatt_mtu_size
-from acts.test_utils.bt.bt_constants import gatt_phy_mask
-from acts.test_utils.bt.bt_constants import gatt_transport
-from acts.test_utils.bt.bt_constants import scan_result
-from acts.test_utils.bt.bt_gatt_utils import GattTestUtilsError
-from acts.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import wait_for_gatt_disconnect_event
-from acts.test_utils.bt.bt_gatt_utils import close_gatt_client
-from acts.test_utils.bt.bt_gatt_utils import log_gatt_server_uuids
-from acts.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import setup_multiple_services
-from acts.test_utils.bt.bt_test_utils import get_mac_address_of_generic_advertisement
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_match_nums
+from acts_contrib.test_utils.bt.bt_constants import bt_profile_constants
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_service_types
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_err
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import gatt_connection_state
+from acts_contrib.test_utils.bt.bt_constants import gatt_mtu_size
+from acts_contrib.test_utils.bt.bt_constants import gatt_phy_mask
+from acts_contrib.test_utils.bt.bt_constants import gatt_transport
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_gatt_utils import GattTestUtilsError
+from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import wait_for_gatt_disconnect_event
+from acts_contrib.test_utils.bt.bt_gatt_utils import close_gatt_client
+from acts_contrib.test_utils.bt.bt_gatt_utils import log_gatt_server_uuids
+from acts_contrib.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_multiple_services
+from acts_contrib.test_utils.bt.bt_test_utils import get_mac_address_of_generic_advertisement
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
 
 PHYSICAL_DISCONNECT_TIMEOUT = 5
 
diff --git a/acts_tests/tests/google/ble/gatt/GattNotifyTest.py b/acts_tests/tests/google/ble/gatt/GattNotifyTest.py
index 7d62d65..4b142a5 100644
--- a/acts_tests/tests/google/ble/gatt/GattNotifyTest.py
+++ b/acts_tests/tests/google/ble/gatt/GattNotifyTest.py
@@ -18,13 +18,13 @@
 """
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
-from acts.test_utils.bt.bt_constants import gatt_characteristic
-from acts.test_utils.bt.bt_constants import gatt_descriptor
-from acts.test_utils.bt.bt_constants import gatt_event
-from acts.test_utils.bt.bt_constants import gatt_cb_strings
-from acts.test_utils.bt.bt_constants import gatt_char_desc_uuids
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_event
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import gatt_char_desc_uuids
 from math import ceil
 
 
diff --git a/acts_tests/tests/google/ble/gatt/GattReadTest.py b/acts_tests/tests/google/ble/gatt/GattReadTest.py
index e880757..216f470 100644
--- a/acts_tests/tests/google/ble/gatt/GattReadTest.py
+++ b/acts_tests/tests/google/ble/gatt/GattReadTest.py
@@ -18,12 +18,12 @@
 """
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
-from acts.test_utils.bt.bt_constants import gatt_characteristic
-from acts.test_utils.bt.bt_constants import gatt_descriptor
-from acts.test_utils.bt.bt_constants import gatt_event
-from acts.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_event
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
 from math import ceil
 
 
diff --git a/acts_tests/tests/google/ble/gatt/GattToolTest.py b/acts_tests/tests/google/ble/gatt/GattToolTest.py
index 8e7a9f0..116495c 100644
--- a/acts_tests/tests/google/ble/gatt/GattToolTest.py
+++ b/acts_tests/tests/google/ble/gatt/GattToolTest.py
@@ -27,19 +27,19 @@
 from queue import Empty
 import time
 
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import gatt_cb_err
-from acts.test_utils.bt.bt_constants import gatt_cb_strings
-from acts.test_utils.bt.bt_constants import gatt_descriptor
-from acts.test_utils.bt.bt_constants import gatt_transport
-from acts.test_utils.bt.bt_constants import scan_result
-from acts.test_utils.bt.bt_gatt_utils import GattTestUtilsError
-from acts.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import log_gatt_server_uuids
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_err
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_transport
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_gatt_utils import GattTestUtilsError
+from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import log_gatt_server_uuids
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
 
 
 class GattToolTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/gatt/GattWriteTest.py b/acts_tests/tests/google/ble/gatt/GattWriteTest.py
index d245e9e..87e043a 100644
--- a/acts_tests/tests/google/ble/gatt/GattWriteTest.py
+++ b/acts_tests/tests/google/ble/gatt/GattWriteTest.py
@@ -18,16 +18,16 @@
 """
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
-from acts.test_utils.bt.bt_constants import gatt_characteristic
-from acts.test_utils.bt.bt_constants import gatt_descriptor
-from acts.test_utils.bt.bt_constants import gatt_event
-from acts.test_utils.bt.bt_constants import gatt_cb_strings
-from acts.test_utils.bt.bt_constants import gatt_connection_priority
-from acts.test_utils.bt.bt_constants import gatt_characteristic_attr_length
-from acts.test_utils.bt.bt_constants import gatt_mtu_size
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_mtu
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_event
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import gatt_connection_priority
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic_attr_length
+from acts_contrib.test_utils.bt.bt_constants import gatt_mtu_size
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_mtu
 
 
 class GattWriteTest(GattConnectedBaseTest):
diff --git a/acts_tests/tests/google/ble/scan/BleBackgroundScanTest.py b/acts_tests/tests/google/ble/scan/BleBackgroundScanTest.py
index 6839602..533d9fa 100644
--- a/acts_tests/tests/google/ble/scan/BleBackgroundScanTest.py
+++ b/acts_tests/tests/google/ble/scan/BleBackgroundScanTest.py
@@ -21,18 +21,18 @@
 
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import bluetooth_off
-from acts.test_utils.bt.bt_test_utils import bluetooth_on
-from acts.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
-from acts.test_utils.bt.bt_test_utils import enable_bluetooth
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.bt.bt_constants import bluetooth_le_off
-from acts.test_utils.bt.bt_constants import bluetooth_le_on
-from acts.test_utils.bt.bt_constants import bt_adapter_states
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import bluetooth_off
+from acts_contrib.test_utils.bt.bt_test_utils import bluetooth_on
+from acts_contrib.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
+from acts_contrib.test_utils.bt.bt_test_utils import enable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_constants import bluetooth_le_off
+from acts_contrib.test_utils.bt.bt_constants import bluetooth_le_on
+from acts_contrib.test_utils.bt.bt_constants import bt_adapter_states
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import scan_result
 
 import time
 
diff --git a/acts_tests/tests/google/ble/scan/BleOnLostOnFoundTest.py b/acts_tests/tests/google/ble/scan/BleOnLostOnFoundTest.py
index 01f7976..6926c6a 100644
--- a/acts_tests/tests/google/ble/scan/BleOnLostOnFoundTest.py
+++ b/acts_tests/tests/google/ble/scan/BleOnLostOnFoundTest.py
@@ -20,18 +20,18 @@
 from queue import Empty
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_callback_types
-from acts.test_utils.bt.BleEnum import ScanSettingsMatchMode
-from acts.test_utils.bt.BleEnum import ScanSettingsMatchNum
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_match_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_match_nums
-from acts.test_utils.bt.bt_constants import adv_succ
-from acts.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_callback_types
+from acts_contrib.test_utils.bt.BleEnum import ScanSettingsMatchMode
+from acts_contrib.test_utils.bt.BleEnum import ScanSettingsMatchNum
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_match_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_match_nums
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_constants import scan_result
 
 
 class BleOnLostOnFoundTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/scan/BleOpportunisticScanTest.py b/acts_tests/tests/google/ble/scan/BleOpportunisticScanTest.py
index 9e59128..570dd1e 100644
--- a/acts_tests/tests/google/ble/scan/BleOpportunisticScanTest.py
+++ b/acts_tests/tests/google/ble/scan/BleOpportunisticScanTest.py
@@ -25,15 +25,15 @@
 
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import batch_scan_result
-from acts.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import batch_scan_result
+from acts_contrib.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_constants import scan_result
 
 
 class BleOpportunisticScanTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/scan/BleScanScreenStateTest.py b/acts_tests/tests/google/ble/scan/BleScanScreenStateTest.py
index 07ae898..a19e80c 100644
--- a/acts_tests/tests/google/ble/scan/BleScanScreenStateTest.py
+++ b/acts_tests/tests/google/ble/scan/BleScanScreenStateTest.py
@@ -25,15 +25,15 @@
 from queue import Empty
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import adv_succ
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import bt_default_timeout
-from acts.test_utils.bt.bt_constants import scan_result
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import bt_default_timeout
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
 
 
 class BleScanScreenStateTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/system_tests/BleStressTest.py b/acts_tests/tests/google/ble/system_tests/BleStressTest.py
index afbb67a..721ef6c 100644
--- a/acts_tests/tests/google/ble/system_tests/BleStressTest.py
+++ b/acts_tests/tests/google/ble/system_tests/BleStressTest.py
@@ -23,15 +23,15 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import BtTestUtilsError
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.bt.bt_test_utils import get_advanced_droid_list
-from acts.test_utils.bt.bt_test_utils import get_mac_address_of_generic_advertisement
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import BtTestUtilsError
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.bt.bt_test_utils import get_advanced_droid_list
+from acts_contrib.test_utils.bt.bt_test_utils import get_mac_address_of_generic_advertisement
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_constants import scan_result
 
 
 class BleStressTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/ble/system_tests/GattLongevityTest.py b/acts_tests/tests/google/ble/system_tests/GattLongevityTest.py
index bb13646..88b1774 100644
--- a/acts_tests/tests/google/ble/system_tests/GattLongevityTest.py
+++ b/acts_tests/tests/google/ble/system_tests/GattLongevityTest.py
@@ -18,16 +18,16 @@
 """
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
-from acts.test_utils.bt.bt_constants import gatt_characteristic
-from acts.test_utils.bt.bt_constants import gatt_descriptor
-from acts.test_utils.bt.bt_constants import gatt_event
-from acts.test_utils.bt.bt_constants import gatt_cb_strings
-from acts.test_utils.bt.bt_constants import gatt_connection_priority
-from acts.test_utils.bt.bt_constants import gatt_characteristic_attr_length
-from acts.test_utils.bt.GattEnum import MtuSize
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_mtu
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.GattConnectedBaseTest import GattConnectedBaseTest
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_event
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_constants import gatt_connection_priority
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic_attr_length
+from acts_contrib.test_utils.bt.GattEnum import MtuSize
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_mtu
 
 
 class GattLongevityTest(GattConnectedBaseTest):
diff --git a/acts_tests/tests/google/bt/AkXB10PairingTest.py b/acts_tests/tests/google/bt/AkXB10PairingTest.py
index 10e7335..99e7529 100644
--- a/acts_tests/tests/google/bt/AkXB10PairingTest.py
+++ b/acts_tests/tests/google/bt/AkXB10PairingTest.py
@@ -20,7 +20,7 @@
 import time
 
 from acts.controllers.relay_lib.relay import SynchronizeRelays
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
 
 log = logging
 
diff --git a/acts_tests/tests/google/bt/BtAirplaneModeTest.py b/acts_tests/tests/google/bt/BtAirplaneModeTest.py
index e2dd7eb..aeafc7a 100644
--- a/acts_tests/tests/google/bt/BtAirplaneModeTest.py
+++ b/acts_tests/tests/google/bt/BtAirplaneModeTest.py
@@ -19,9 +19,9 @@
 """
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import bluetooth_enabled_check
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import bluetooth_enabled_check
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
 from queue import Empty
 import time
 
diff --git a/acts_tests/tests/google/bt/BtBasicFunctionalityTest.py b/acts_tests/tests/google/bt/BtBasicFunctionalityTest.py
index 3194d76..ade4c0a 100644
--- a/acts_tests/tests/google/bt/BtBasicFunctionalityTest.py
+++ b/acts_tests/tests/google/bt/BtBasicFunctionalityTest.py
@@ -22,14 +22,14 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import bt_scan_mode_types
-from acts.test_utils.bt.bt_test_utils import check_device_supported_profiles
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_test_utils import set_device_name
-from acts.test_utils.bt.bt_test_utils import set_bt_scan_mode
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import bt_scan_mode_types
+from acts_contrib.test_utils.bt.bt_test_utils import check_device_supported_profiles
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import set_device_name
+from acts_contrib.test_utils.bt.bt_test_utils import set_bt_scan_mode
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
 
 
 class BtBasicFunctionalityTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/BtFactoryResetTest.py b/acts_tests/tests/google/bt/BtFactoryResetTest.py
index f33b210..49ea0e0 100644
--- a/acts_tests/tests/google/bt/BtFactoryResetTest.py
+++ b/acts_tests/tests/google/bt/BtFactoryResetTest.py
@@ -17,8 +17,8 @@
 Test script to test BluetoothAdapter's resetFactory method.
 """
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import pair_pri_to_sec
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import pair_pri_to_sec
 
 
 class BtFactoryResetTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/BtKillProcessTest.py b/acts_tests/tests/google/bt/BtKillProcessTest.py
index ed53e20..b5adc77 100644
--- a/acts_tests/tests/google/bt/BtKillProcessTest.py
+++ b/acts_tests/tests/google/bt/BtKillProcessTest.py
@@ -21,7 +21,7 @@
 import re
 import time
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
 
 
 class BtKillProcessTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/BtMetricsTest.py b/acts_tests/tests/google/bt/BtMetricsTest.py
index 37d4150..42a6da3 100644
--- a/acts_tests/tests/google/bt/BtMetricsTest.py
+++ b/acts_tests/tests/google/bt/BtMetricsTest.py
@@ -16,11 +16,11 @@
 from google import protobuf
 
 from acts import asserts
-from acts.test_utils.bt.BtMetricsBaseTest import BtMetricsBaseTest
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.bt.bt_test_utils import pair_pri_to_sec
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.protos import bluetooth_pb2
+from acts_contrib.test_utils.bt.BtMetricsBaseTest import BtMetricsBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.bt.bt_test_utils import pair_pri_to_sec
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.protos import bluetooth_pb2
 from acts.utils import get_current_epoch_time, sync_device_time
 
 
diff --git a/acts_tests/tests/google/bt/RfcommTest.py b/acts_tests/tests/google/bt/RfcommTest.py
index 58de1be..14dc7bb 100644
--- a/acts_tests/tests/google/bt/RfcommTest.py
+++ b/acts_tests/tests/google/bt/RfcommTest.py
@@ -23,18 +23,18 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import bt_rfcomm_uuids
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.bt.bt_test_utils import kill_bluetooth_process
-from acts.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.bt_test_utils import take_btsnoop_logs
-from acts.test_utils.bt.bt_test_utils import write_read_verify_data
-from acts.test_utils.bt.bt_test_utils import verify_server_and_client_connected
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import bt_rfcomm_uuids
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.bt.bt_test_utils import kill_bluetooth_process
+from acts_contrib.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.bt_test_utils import write_read_verify_data
+from acts_contrib.test_utils.bt.bt_test_utils import verify_server_and_client_connected
 
-from acts.test_utils.bt.BtEnum import RfcommUuid
+from acts_contrib.test_utils.bt.BtEnum import RfcommUuid
 
 
 class RfcommTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/SonyXB2PairingTest.py b/acts_tests/tests/google/bt/SonyXB2PairingTest.py
index 4dcf863..795914e 100644
--- a/acts_tests/tests/google/bt/SonyXB2PairingTest.py
+++ b/acts_tests/tests/google/bt/SonyXB2PairingTest.py
@@ -20,7 +20,7 @@
 import time
 
 from acts.controllers.relay_lib.relay import SynchronizeRelays
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
 
 log = logging
 
diff --git a/acts_tests/tests/google/bt/audio_lab/BtChameleonTest.py b/acts_tests/tests/google/bt/audio_lab/BtChameleonTest.py
index 1e76729..3a0be9d 100644
--- a/acts_tests/tests/google/bt/audio_lab/BtChameleonTest.py
+++ b/acts_tests/tests/google/bt/audio_lab/BtChameleonTest.py
@@ -27,16 +27,16 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.audio_analysis_lib.check_quality import quality_analysis
-from acts.test_utils.bt.BtFunhausBaseTest import BtFunhausBaseTest
-from acts.test_utils.bt.bt_constants import audio_bits_per_sample_32
-from acts.test_utils.bt.bt_constants import audio_channel_mode_8
-from acts.test_utils.bt.bt_constants import audio_sample_rate_48000
-from acts.test_utils.bt.bt_constants import delay_after_binding_seconds
-from acts.test_utils.bt.bt_constants import delay_before_record_seconds
-from acts.test_utils.bt.bt_constants import fpga_linein_bus_endpoint
-from acts.test_utils.bt.bt_constants import headphone_bus_endpoint
-from acts.test_utils.bt.bt_constants import silence_wait_seconds
+from acts_contrib.test_utils.audio_analysis_lib.check_quality import quality_analysis
+from acts_contrib.test_utils.bt.BtFunhausBaseTest import BtFunhausBaseTest
+from acts_contrib.test_utils.bt.bt_constants import audio_bits_per_sample_32
+from acts_contrib.test_utils.bt.bt_constants import audio_channel_mode_8
+from acts_contrib.test_utils.bt.bt_constants import audio_sample_rate_48000
+from acts_contrib.test_utils.bt.bt_constants import delay_after_binding_seconds
+from acts_contrib.test_utils.bt.bt_constants import delay_before_record_seconds
+from acts_contrib.test_utils.bt.bt_constants import fpga_linein_bus_endpoint
+from acts_contrib.test_utils.bt.bt_constants import headphone_bus_endpoint
+from acts_contrib.test_utils.bt.bt_constants import silence_wait_seconds
 
 
 class BtChameleonTest(BtFunhausBaseTest):
diff --git a/acts_tests/tests/google/bt/audio_lab/BtFunhausMetricsTest.py b/acts_tests/tests/google/bt/audio_lab/BtFunhausMetricsTest.py
index c734191..84166e9 100644
--- a/acts_tests/tests/google/bt/audio_lab/BtFunhausMetricsTest.py
+++ b/acts_tests/tests/google/bt/audio_lab/BtFunhausMetricsTest.py
@@ -15,7 +15,7 @@
 
 from acts.test_decorators import test_tracker_info
 from acts import asserts
-from acts.test_utils.bt.BtFunhausBaseTest import BtFunhausBaseTest
+from acts_contrib.test_utils.bt.BtFunhausBaseTest import BtFunhausBaseTest
 
 
 class BtFunhausMetricsTest(BtFunhausBaseTest):
diff --git a/acts_tests/tests/google/bt/audio_lab/BtFunhausTest.py b/acts_tests/tests/google/bt/audio_lab/BtFunhausTest.py
index 42cbc22..20363f9 100644
--- a/acts_tests/tests/google/bt/audio_lab/BtFunhausTest.py
+++ b/acts_tests/tests/google/bt/audio_lab/BtFunhausTest.py
@@ -19,7 +19,7 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BtFunhausBaseTest import BtFunhausBaseTest
+from acts_contrib.test_utils.bt.BtFunhausBaseTest import BtFunhausBaseTest
 
 
 class BtFunhausTest(BtFunhausBaseTest):
diff --git a/acts_tests/tests/google/bt/audio_lab/ThreeButtonDongleTest.py b/acts_tests/tests/google/bt/audio_lab/ThreeButtonDongleTest.py
index abf97be..4c032fd 100644
--- a/acts_tests/tests/google/bt/audio_lab/ThreeButtonDongleTest.py
+++ b/acts_tests/tests/google/bt/audio_lab/ThreeButtonDongleTest.py
@@ -19,8 +19,8 @@
 import time
 
 from acts.controllers.relay_lib.relay import SynchronizeRelays
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
 
 
 class ThreeButtonDongleTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/avrcp/BtAvrcpPassthroughTest.py b/acts_tests/tests/google/bt/avrcp/BtAvrcpPassthroughTest.py
index 496db44..a0c82ac 100644
--- a/acts_tests/tests/google/bt/avrcp/BtAvrcpPassthroughTest.py
+++ b/acts_tests/tests/google/bt/avrcp/BtAvrcpPassthroughTest.py
@@ -17,8 +17,8 @@
 import time
 
 from acts.asserts import assert_equal
-from acts.test_utils.bt.AvrcpBaseTest import AvrcpBaseTest
-from acts.test_utils.car.car_media_utils import PlaybackState
+from acts_contrib.test_utils.bt.AvrcpBaseTest import AvrcpBaseTest
+from acts_contrib.test_utils.car.car_media_utils import PlaybackState
 
 
 DEFAULT_TIMEOUT = 0.5
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarBasicFunctionalityTest.py b/acts_tests/tests/google/bt/car_bt/BtCarBasicFunctionalityTest.py
index 99f2851..b8d5ec8 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarBasicFunctionalityTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarBasicFunctionalityTest.py
@@ -21,15 +21,15 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.BtEnum import BluetoothScanModeType
-from acts.test_utils.bt.bt_test_utils import check_device_supported_profiles
-from acts.test_utils.bt.bt_test_utils import log_energy_info
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_test_utils import set_device_name
-from acts.test_utils.bt.bt_test_utils import set_bt_scan_mode
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BtEnum import BluetoothScanModeType
+from acts_contrib.test_utils.bt.bt_test_utils import check_device_supported_profiles
+from acts_contrib.test_utils.bt.bt_test_utils import log_energy_info
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import set_device_name
+from acts_contrib.test_utils.bt.bt_test_utils import set_bt_scan_mode
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
 
 
 class BtCarBasicFunctionalityTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarHfpConferenceTest.py b/acts_tests/tests/google/bt/car_bt/BtCarHfpConferenceTest.py
index 6a12e8f..edb9683 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarHfpConferenceTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarHfpConferenceTest.py
@@ -20,19 +20,19 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.car import car_telecom_utils
-from acts.test_utils.tel import tel_defines
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import wait_and_answer_call
-from acts.test_utils.tel.tel_test_utils import wait_for_ringing_call
-from acts.test_utils.tel.tel_voice_utils import get_audio_route
-from acts.test_utils.tel.tel_voice_utils import set_audio_route
-from acts.test_utils.tel.tel_voice_utils import swap_calls
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
+from acts_contrib.test_utils.bt import BtEnum
+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_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
+from acts_contrib.test_utils.tel.tel_voice_utils import swap_calls
 
 BLUETOOTH_PKG_NAME = "com.android.bluetooth"
 CALL_TYPE_OUTGOING = "CALL_TYPE_OUTGOING"
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarHfpConnectionTest.py b/acts_tests/tests/google/bt/car_bt/BtCarHfpConnectionTest.py
index 006aa20..bda7e88 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarHfpConnectionTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarHfpConnectionTest.py
@@ -20,16 +20,16 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.car import car_bt_utils
-from acts.test_utils.car import car_telecom_utils
-from acts.test_utils.tel import tel_defines
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import wait_and_answer_call
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt import bt_test_utils
+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
 
 BLUETOOTH_PKG_NAME = "com.android.bluetooth"
 CALL_TYPE_OUTGOING = "CALL_TYPE_OUTGOING"
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarHfpFuzzTest.py b/acts_tests/tests/google/bt/car_bt/BtCarHfpFuzzTest.py
index ebb0d28..a081901 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarHfpFuzzTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarHfpFuzzTest.py
@@ -21,12 +21,12 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.car import car_telecom_utils
-from acts.test_utils.tel import tel_defines
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
+from acts_contrib.test_utils.bt import BtEnum
+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
 
 STABILIZATION_DELAY_SEC = 5
 
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarHfpTest.py b/acts_tests/tests/google/bt/car_bt/BtCarHfpTest.py
index 2fc6260..2806feb 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarHfpTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarHfpTest.py
@@ -19,13 +19,13 @@
 
 import time
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.car import car_telecom_utils
-from acts.test_utils.car import tel_telecom_utils
-from acts.test_utils.tel import tel_defines
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
+from acts_contrib.test_utils.bt import BtEnum
+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.car import tel_telecom_utils
+from acts_contrib.test_utils.tel import tel_defines
 
 BLUETOOTH_PKG_NAME = "com.android.bluetooth"
 CALL_TYPE_OUTGOING = "CALL_TYPE_OUTGOING"
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarMapMceTest.py b/acts_tests/tests/google/bt/car_bt/BtCarMapMceTest.py
index 93dbab8..b72ffbb 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarMapMceTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarMapMceTest.py
@@ -22,15 +22,15 @@
 
 import acts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.tel.tel_defines import EventSmsReceived
-from acts.test_utils.tel.tel_defines import EventSmsSentSuccess
-from acts.test_utils.tel.tel_defines import EventSmsDeliverSuccess
-from acts.test_utils.tel.tel_test_utils import get_phone_number
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothCarHfpBaseTest import BluetoothCarHfpBaseTest
+from acts_contrib.test_utils.bt import bt_test_utils
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.tel.tel_defines import EventSmsReceived
+from acts_contrib.test_utils.tel.tel_defines import EventSmsSentSuccess
+from acts_contrib.test_utils.tel.tel_defines import EventSmsDeliverSuccess
+from acts_contrib.test_utils.tel.tel_test_utils import get_phone_number
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
 
 EVENT_MAP_MESSAGE_RECEIVED = "MapMessageReceived"
 TIMEOUT = 2000
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarMediaConnectionTest.py b/acts_tests/tests/google/bt/car_bt/BtCarMediaConnectionTest.py
index 3f6e495..068b807 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarMediaConnectionTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarMediaConnectionTest.py
@@ -20,11 +20,11 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.car import car_bt_utils
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt.bt_test_utils import is_a2dp_connected
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt import bt_test_utils
+from acts_contrib.test_utils.car import car_bt_utils
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt.bt_test_utils import is_a2dp_connected
 
 
 class BtCarMediaConnectionTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarMediaPassthroughTest.py b/acts_tests/tests/google/bt/car_bt/BtCarMediaPassthroughTest.py
index dbb1cc9..1db9a11 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarMediaPassthroughTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarMediaPassthroughTest.py
@@ -21,11 +21,11 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.car import car_media_utils
-from acts.test_utils.bt.bt_test_utils import is_a2dp_connected
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt import bt_test_utils
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.car import car_media_utils
+from acts_contrib.test_utils.bt.bt_test_utils import is_a2dp_connected
 from acts.keys import Config
 
 
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarMultiUserTest.py b/acts_tests/tests/google/bt/car_bt/BtCarMultiUserTest.py
index 3cde80f..6ec1320 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarMultiUserTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarMultiUserTest.py
@@ -18,9 +18,9 @@
 
 """
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.users import users
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt import bt_test_utils
+from acts_contrib.test_utils.users import users
 import time
 import random
 import re
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarPairedConnectDisconnectTest.py b/acts_tests/tests/google/bt/car_bt/BtCarPairedConnectDisconnectTest.py
index 705b4ec..eb2a06c 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarPairedConnectDisconnectTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarPairedConnectDisconnectTest.py
@@ -28,10 +28,10 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
 from acts.base_test import BaseTestClass
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt import bt_test_utils
+from acts_contrib.test_utils.bt import BtEnum
 from acts import asserts
 
 
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarPairingTest.py b/acts_tests/tests/google/bt/car_bt/BtCarPairingTest.py
index 603f1a8..135c450 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarPairingTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarPairingTest.py
@@ -20,11 +20,11 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
 from acts.base_test import BaseTestClass
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.car import car_bt_utils
-from acts.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt import bt_test_utils
+from acts_contrib.test_utils.car import car_bt_utils
+from acts_contrib.test_utils.bt import BtEnum
 
 # Timed wait between Bonding happens and Android actually gets the list of
 # supported services (and subsequently updates the priorities)
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarPbapTest.py b/acts_tests/tests/google/bt/car_bt/BtCarPbapTest.py
index 6d5bccb..a4d241f 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarPbapTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarPbapTest.py
@@ -20,14 +20,14 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
 from acts.base_test import BaseTestClass
-from acts.test_utils.bt import bt_contacts_utils
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.car import car_bt_utils
+from acts_contrib.test_utils.bt import bt_contacts_utils
+from acts_contrib.test_utils.bt import bt_test_utils
+from acts_contrib.test_utils.car import car_bt_utils
 from acts.utils import exe_cmd
-import acts.test_utils.bt.BtEnum as BtEnum
+import acts_contrib.test_utils.bt.BtEnum as BtEnum
 
 # Offset call logs by 1 minute
 CALL_LOG_TIME_OFFSET_IN_MSEC = 60000
diff --git a/acts_tests/tests/google/bt/car_bt/BtCarToggleTest.py b/acts_tests/tests/google/bt/car_bt/BtCarToggleTest.py
index aa7f5b4..27d08e8 100644
--- a/acts_tests/tests/google/bt/car_bt/BtCarToggleTest.py
+++ b/acts_tests/tests/google/bt/car_bt/BtCarToggleTest.py
@@ -18,8 +18,8 @@
 """
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt import bt_test_utils
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt import bt_test_utils
 
 import random
 import time
diff --git a/acts_tests/tests/google/bt/gatt/GattOverBrEdrTest.py b/acts_tests/tests/google/bt/gatt/GattOverBrEdrTest.py
index 7985540..0d0ac0c 100644
--- a/acts_tests/tests/google/bt/gatt/GattOverBrEdrTest.py
+++ b/acts_tests/tests/google/bt/gatt/GattOverBrEdrTest.py
@@ -21,22 +21,22 @@
 from queue import Empty
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_constants import gatt_characteristic
-from acts.test_utils.bt.bt_constants import gatt_service_types
-from acts.test_utils.bt.bt_constants import gatt_transport
-from acts.test_utils.bt.bt_constants import gatt_cb_strings
-from acts.test_utils.bt.bt_gatt_utils import GattTestUtilsError
-from acts.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import log_gatt_server_uuids
-from acts.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_characteristics
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import setup_gatt_descriptors
-from acts.test_utils.bt.bt_gatt_utils import setup_multiple_services
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.bt_test_utils import take_btsnoop_logs
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_service_types
+from acts_contrib.test_utils.bt.bt_constants import gatt_transport
+from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings
+from acts_contrib.test_utils.bt.bt_gatt_utils import GattTestUtilsError
+from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import log_gatt_server_uuids
+from acts_contrib.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_characteristics
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_gatt_descriptors
+from acts_contrib.test_utils.bt.bt_gatt_utils import setup_multiple_services
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import take_btsnoop_logs
 
 
 class GattOverBrEdrTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/headphone_automation/HeadphoneTest.py b/acts_tests/tests/google/bt/headphone_automation/HeadphoneTest.py
index 43934a1..9d04088 100644
--- a/acts_tests/tests/google/bt/headphone_automation/HeadphoneTest.py
+++ b/acts_tests/tests/google/bt/headphone_automation/HeadphoneTest.py
@@ -30,12 +30,12 @@
 from acts.asserts import assert_true
 from acts.keys import Config
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import bluetooth_enabled_check
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.bt.bt_test_utils import disable_bluetooth
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import bluetooth_enabled_check
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
 from acts.controllers.relay_lib.sony_xb2_speaker import SonyXB2Speaker
 
 
diff --git a/acts_tests/tests/google/bt/headphone_automation/SineWaveQualityTest.py b/acts_tests/tests/google/bt/headphone_automation/SineWaveQualityTest.py
index b7fef62..5688afc 100644
--- a/acts_tests/tests/google/bt/headphone_automation/SineWaveQualityTest.py
+++ b/acts_tests/tests/google/bt/headphone_automation/SineWaveQualityTest.py
@@ -1,7 +1,7 @@
 from acts import asserts
 from acts.signals import TestPass
-from acts.test_utils.audio_analysis_lib import audio_analysis
-from acts.test_utils.bt.A2dpBaseTest import A2dpBaseTest
+from acts_contrib.test_utils.audio_analysis_lib import audio_analysis
+from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
 
 
 class SineWaveQualityTest(A2dpBaseTest):
diff --git a/acts_tests/tests/google/bt/hid/HidDeviceTest.py b/acts_tests/tests/google/bt/hid/HidDeviceTest.py
index e7e1778..0466376 100644
--- a/acts_tests/tests/google/bt/hid/HidDeviceTest.py
+++ b/acts_tests/tests/google/bt/hid/HidDeviceTest.py
@@ -19,14 +19,14 @@
 
 from acts.base_test import BaseTestClass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.bt.bt_test_utils import pair_pri_to_sec
-from acts.test_utils.bt.bt_test_utils import hid_keyboard_report
-from acts.test_utils.bt.bt_test_utils import hid_device_send_key_data_report
-from acts.test_utils.bt.bt_constants import hid_connection_timeout
-from acts.test_utils.bt import bt_constants
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.bt.bt_test_utils import pair_pri_to_sec
+from acts_contrib.test_utils.bt.bt_test_utils import hid_keyboard_report
+from acts_contrib.test_utils.bt.bt_test_utils import hid_device_send_key_data_report
+from acts_contrib.test_utils.bt.bt_constants import hid_connection_timeout
+from acts_contrib.test_utils.bt import bt_constants
 import time
 
 
diff --git a/acts_tests/tests/google/bt/ota/BtOtaTest.py b/acts_tests/tests/google/bt/ota/BtOtaTest.py
index 86e3098..2c8411d 100644
--- a/acts_tests/tests/google/bt/ota/BtOtaTest.py
+++ b/acts_tests/tests/google/bt/ota/BtOtaTest.py
@@ -15,8 +15,8 @@
 
 from acts.libs.ota import ota_updater
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import pair_pri_to_sec
+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 import signals
 
 
diff --git a/acts_tests/tests/google/bt/pan/BtPanTest.py b/acts_tests/tests/google/bt/pan/BtPanTest.py
index 01f6078..75d14b2 100644
--- a/acts_tests/tests/google/bt/pan/BtPanTest.py
+++ b/acts_tests/tests/google/bt/pan/BtPanTest.py
@@ -23,10 +23,10 @@
 This device was not intended to run in a sheild box.
 """
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import bluetooth_enabled_check
-from acts.test_utils.bt.bt_test_utils import orchestrate_and_verify_pan_connection
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import bluetooth_enabled_check
+from acts_contrib.test_utils.bt.bt_test_utils import orchestrate_and_verify_pan_connection
+from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
 from queue import Empty
 import time
 
diff --git a/acts_tests/tests/google/bt/performance/BtA2dpOtaRangeTest.py b/acts_tests/tests/google/bt/performance/BtA2dpOtaRangeTest.py
index e0a7962..e794b1b 100755
--- a/acts_tests/tests/google/bt/performance/BtA2dpOtaRangeTest.py
+++ b/acts_tests/tests/google/bt/performance/BtA2dpOtaRangeTest.py
@@ -9,13 +9,13 @@
 import os
 import pyvisa
 import time
-import acts.test_utils.coex.audio_test_utils as atu
-import acts.test_utils.bt.bt_test_utils as btutils
+import acts_contrib.test_utils.coex.audio_test_utils as atu
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
 import pandas as pd
 from acts import asserts
-from acts.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bt_factory
-from acts.test_utils.bt.A2dpBaseTest import A2dpBaseTest
-from acts.test_utils.power.PowerBTBaseTest import ramp_attenuation
+from acts_contrib.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bt_factory
+from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
+from acts_contrib.test_utils.power.PowerBTBaseTest import ramp_attenuation
 
 PHONE_MUSIC_FILE_DIRECTORY = '/sdcard/Music'
 
diff --git a/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py b/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
index 55b0751..d616230 100644
--- a/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
+++ b/acts_tests/tests/google/bt/performance/BtA2dpRangeTest.py
@@ -14,14 +14,14 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-import acts.test_utils.bt.bt_test_utils as btutils
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
 from acts import asserts
 from acts.signals import TestPass
-from acts.test_utils.bt import bt_constants
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt.A2dpBaseTest import A2dpBaseTest
-from acts.test_utils.bt.loggers import bluetooth_metric_logger as log
-from acts.test_utils.power.PowerBTBaseTest import ramp_attenuation
+from acts_contrib.test_utils.bt import bt_constants
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
+from acts_contrib.test_utils.bt.loggers import bluetooth_metric_logger as log
+from acts_contrib.test_utils.power.PowerBTBaseTest import ramp_attenuation
 
 
 class BtA2dpRangeTest(A2dpBaseTest):
diff --git a/acts_tests/tests/google/bt/performance/BtCodecSweepTest.py b/acts_tests/tests/google/bt/performance/BtCodecSweepTest.py
index 7770852..e9cc90f 100644
--- a/acts_tests/tests/google/bt/performance/BtCodecSweepTest.py
+++ b/acts_tests/tests/google/bt/performance/BtCodecSweepTest.py
@@ -18,9 +18,9 @@
 
 from acts import asserts
 from acts.signals import TestPass
-from acts.test_utils.bt.A2dpBaseTest import A2dpBaseTest
-from acts.test_utils.bt import bt_constants
-from acts.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger
+from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
+from acts_contrib.test_utils.bt import bt_constants
+from acts_contrib.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger
 
 DEFAULT_THDN_THRESHOLD = .1
 DEFAULT_ANOMALIES_THRESHOLD = 0
diff --git a/acts_tests/tests/google/bt/performance/BtInterferenceDynamicTest.py b/acts_tests/tests/google/bt/performance/BtInterferenceDynamicTest.py
index 664121d..59721ca 100644
--- a/acts_tests/tests/google/bt/performance/BtInterferenceDynamicTest.py
+++ b/acts_tests/tests/google/bt/performance/BtInterferenceDynamicTest.py
@@ -19,9 +19,9 @@
 import random
 import time
 from acts.signals import TestFailure
-from acts.test_utils.bt.BtInterferenceBaseTest import BtInterferenceBaseTest
-from acts.test_utils.bt.BtInterferenceBaseTest import get_iperf_results
-from acts.test_utils.power.PowerBTBaseTest import ramp_attenuation
+from acts_contrib.test_utils.bt.BtInterferenceBaseTest import BtInterferenceBaseTest
+from acts_contrib.test_utils.bt.BtInterferenceBaseTest import get_iperf_results
+from acts_contrib.test_utils.power.PowerBTBaseTest import ramp_attenuation
 from multiprocessing import Process, Queue
 
 DEFAULT_THDN_THRESHOLD = 0.9
diff --git a/acts_tests/tests/google/bt/performance/BtInterferenceRSSITest.py b/acts_tests/tests/google/bt/performance/BtInterferenceRSSITest.py
index a1c40f7..4f75031 100644
--- a/acts_tests/tests/google/bt/performance/BtInterferenceRSSITest.py
+++ b/acts_tests/tests/google/bt/performance/BtInterferenceRSSITest.py
@@ -1,7 +1,7 @@
 from multiprocessing import Process
 import time
 
-from acts.test_utils.bt.A2dpBaseTest import A2dpBaseTest
+from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest
 
 END_TOKEN = "end"
 
diff --git a/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py b/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py
index 84ca6d4..bf6b0de 100644
--- a/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py
+++ b/acts_tests/tests/google/bt/performance/BtInterferenceStaticTest.py
@@ -16,9 +16,9 @@
 """Stream music through connected device from phone across different
 attenuations."""
 from acts.signals import TestPass
-from acts.test_utils.bt.BtInterferenceBaseTest import BtInterferenceBaseTest
+from acts_contrib.test_utils.bt.BtInterferenceBaseTest import BtInterferenceBaseTest
 from acts.metrics.loggers.blackbox import BlackboxMetricLogger
-from acts.test_utils.bt.BtInterferenceBaseTest import get_iperf_results
+from acts_contrib.test_utils.bt.BtInterferenceBaseTest import get_iperf_results
 from multiprocessing import Process, Queue
 
 DEFAULT_THDN_THRESHOLD = 0.9
diff --git a/acts_tests/tests/google/bt/pts/A2dpPtsTest.py b/acts_tests/tests/google/bt/pts/A2dpPtsTest.py
index 2c2ffee..84b7b1b 100644
--- a/acts_tests/tests/google/bt/pts/A2dpPtsTest.py
+++ b/acts_tests/tests/google/bt/pts/A2dpPtsTest.py
@@ -16,12 +16,12 @@
 """
 A2DP PTS Tests.
 """
-from acts.test_utils.abstract_devices.bluetooth_device import AndroidBluetoothDevice
-from acts.test_utils.abstract_devices.bluetooth_device import FuchsiaBluetoothDevice
-from acts.test_utils.bt.pts.pts_base_class import PtsBaseClass
+from acts_contrib.test_utils.abstract_devices.bluetooth_device import AndroidBluetoothDevice
+from acts_contrib.test_utils.abstract_devices.bluetooth_device import FuchsiaBluetoothDevice
+from acts_contrib.test_utils.bt.pts.pts_base_class import PtsBaseClass
 
-import acts.test_utils.bt.pts.fuchsia_pts_ics_lib as f_ics_lib
-import acts.test_utils.bt.pts.fuchsia_pts_ixit_lib as f_ixit_lib
+import acts_contrib.test_utils.bt.pts.fuchsia_pts_ics_lib as f_ics_lib
+import acts_contrib.test_utils.bt.pts.fuchsia_pts_ixit_lib as f_ixit_lib
 
 
 class A2dpPtsTest(PtsBaseClass):
diff --git a/acts_tests/tests/google/bt/pts/BtCmdLineTest.py b/acts_tests/tests/google/bt/pts/BtCmdLineTest.py
index 8d925ed..16e1aa0 100644
--- a/acts_tests/tests/google/bt/pts/BtCmdLineTest.py
+++ b/acts_tests/tests/google/bt/pts/BtCmdLineTest.py
@@ -21,14 +21,14 @@
 Optional config parameters:
 'sim_conf_file' : '/path_to_config/'
 """
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
 from cmd_input import CmdInput
 from queue import Empty
 
 import os
 import uuid
 
-from acts.test_utils.tel.tel_test_utils import setup_droid_properties
+from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
 
 
 class BtCmdLineTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/pts/GattPtsTest.py b/acts_tests/tests/google/bt/pts/GattPtsTest.py
index 4166f8e..d910233 100644
--- a/acts_tests/tests/google/bt/pts/GattPtsTest.py
+++ b/acts_tests/tests/google/bt/pts/GattPtsTest.py
@@ -18,14 +18,14 @@
 """
 
 from acts import signals
-from acts.test_utils.abstract_devices.bluetooth_device import AndroidBluetoothDevice
-from acts.test_utils.abstract_devices.bluetooth_device import FuchsiaBluetoothDevice
+from acts_contrib.test_utils.abstract_devices.bluetooth_device import AndroidBluetoothDevice
+from acts_contrib.test_utils.abstract_devices.bluetooth_device import FuchsiaBluetoothDevice
 from acts.controllers.bluetooth_pts_device import VERDICT_STRINGS
-from acts.test_utils.bt.pts.pts_base_class import PtsBaseClass
+from acts_contrib.test_utils.bt.pts.pts_base_class import PtsBaseClass
 
-import acts.test_utils.bt.gatt_test_database as gatt_test_database
-import acts.test_utils.bt.pts.fuchsia_pts_ics_lib as f_ics_lib
-import acts.test_utils.bt.pts.fuchsia_pts_ixit_lib as f_ixit_lib
+import acts_contrib.test_utils.bt.gatt_test_database as gatt_test_database
+import acts_contrib.test_utils.bt.pts.fuchsia_pts_ics_lib as f_ics_lib
+import acts_contrib.test_utils.bt.pts.fuchsia_pts_ixit_lib as f_ixit_lib
 
 
 class GattPtsTest(PtsBaseClass):
diff --git a/acts_tests/tests/google/bt/pts/SdpPtsTest.py b/acts_tests/tests/google/bt/pts/SdpPtsTest.py
index 1dd5d66..25642b8 100644
--- a/acts_tests/tests/google/bt/pts/SdpPtsTest.py
+++ b/acts_tests/tests/google/bt/pts/SdpPtsTest.py
@@ -16,14 +16,14 @@
 """
 SDP PTS Tests.
 """
-from acts.test_utils.abstract_devices.bluetooth_device import AndroidBluetoothDevice
-from acts.test_utils.abstract_devices.bluetooth_device import FuchsiaBluetoothDevice
-from acts.test_utils.bt.bt_constants import bt_attribute_values
-from acts.test_utils.bt.bt_constants import sig_uuid_constants
-from acts.test_utils.bt.pts.pts_base_class import PtsBaseClass
+from acts_contrib.test_utils.abstract_devices.bluetooth_device import AndroidBluetoothDevice
+from acts_contrib.test_utils.abstract_devices.bluetooth_device import FuchsiaBluetoothDevice
+from acts_contrib.test_utils.bt.bt_constants import bt_attribute_values
+from acts_contrib.test_utils.bt.bt_constants import sig_uuid_constants
+from acts_contrib.test_utils.bt.pts.pts_base_class import PtsBaseClass
 
-import acts.test_utils.bt.pts.fuchsia_pts_ics_lib as f_ics_lib
-import acts.test_utils.bt.pts.fuchsia_pts_ixit_lib as f_ixit_lib
+import acts_contrib.test_utils.bt.pts.fuchsia_pts_ics_lib as f_ics_lib
+import acts_contrib.test_utils.bt.pts.fuchsia_pts_ixit_lib as f_ixit_lib
 
 # SDP_RECORD Definition is WIP
 SDP_RECORD = {
diff --git a/acts_tests/tests/google/bt/pts/cmd_input.py b/acts_tests/tests/google/bt/pts/cmd_input.py
index 4037efc..2db77a7 100644
--- a/acts_tests/tests/google/bt/pts/cmd_input.py
+++ b/acts_tests/tests/google/bt/pts/cmd_input.py
@@ -16,10 +16,10 @@
 """
 Python script for wrappers to various libraries.
 """
-from acts.test_utils.bt.bt_constants import bt_scan_mode_types
-from acts.test_utils.bt.bt_constants import gatt_server_responses
-import acts.test_utils.bt.gatt_test_database as gatt_test_database
-from acts.test_utils.bt.bt_carkit_lib import E2eBtCarkitLib
+from acts_contrib.test_utils.bt.bt_constants import bt_scan_mode_types
+from acts_contrib.test_utils.bt.bt_constants import gatt_server_responses
+import acts_contrib.test_utils.bt.gatt_test_database as gatt_test_database
+from acts_contrib.test_utils.bt.bt_carkit_lib import E2eBtCarkitLib
 
 import threading
 import time
diff --git a/acts_tests/tests/google/bt/sar/BleSarPowerLimitTest.py b/acts_tests/tests/google/bt/sar/BleSarPowerLimitTest.py
index 490cb30..cf9ea2b 100644
--- a/acts_tests/tests/google/bt/sar/BleSarPowerLimitTest.py
+++ b/acts_tests/tests/google/bt/sar/BleSarPowerLimitTest.py
@@ -18,13 +18,13 @@
 import time
 from acts import utils
 
-import acts.test_utils.bt.bt_test_utils as bt_utils
-import acts.test_utils.wifi.wifi_performance_test_utils as wifi_utils
-from acts.test_utils.bt.ble_performance_test_utils import ble_gatt_disconnection
-from acts.test_utils.bt.ble_performance_test_utils import ble_coc_connection
-from acts.test_utils.bt.bt_constants import l2cap_max_inactivity_delay_after_disconnect
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.BtSarBaseTest import BtSarBaseTest
+import acts_contrib.test_utils.bt.bt_test_utils as bt_utils
+import acts_contrib.test_utils.wifi.wifi_performance_test_utils as wifi_utils
+from acts_contrib.test_utils.bt.ble_performance_test_utils import ble_gatt_disconnection
+from acts_contrib.test_utils.bt.ble_performance_test_utils import ble_coc_connection
+from acts_contrib.test_utils.bt.bt_constants import l2cap_max_inactivity_delay_after_disconnect
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.BtSarBaseTest import BtSarBaseTest
 
 FIXED_ATTENUATION = 36
 
diff --git a/acts_tests/tests/google/bt/sar/BtSarPowerLimitTest.py b/acts_tests/tests/google/bt/sar/BtSarPowerLimitTest.py
index 74a1000..5b75606 100644
--- a/acts_tests/tests/google/bt/sar/BtSarPowerLimitTest.py
+++ b/acts_tests/tests/google/bt/sar/BtSarPowerLimitTest.py
@@ -16,10 +16,10 @@
 
 import os
 import time
-import acts.test_utils.bt.bt_test_utils as bt_utils
-from acts.test_utils.bt.BtSarBaseTest import BtSarBaseTest
-from acts.test_utils.power.PowerBTBaseTest import ramp_attenuation
-import acts.test_utils.wifi.wifi_performance_test_utils as wifi_utils
+import acts_contrib.test_utils.bt.bt_test_utils as bt_utils
+from acts_contrib.test_utils.bt.BtSarBaseTest import BtSarBaseTest
+from acts_contrib.test_utils.power.PowerBTBaseTest import ramp_attenuation
+import acts_contrib.test_utils.wifi.wifi_performance_test_utils as wifi_utils
 
 SLEEP_DURATION = 2
 
diff --git a/acts_tests/tests/google/bt/sar/BtSarSanityTest.py b/acts_tests/tests/google/bt/sar/BtSarSanityTest.py
index 7ef8840..08a4369 100644
--- a/acts_tests/tests/google/bt/sar/BtSarSanityTest.py
+++ b/acts_tests/tests/google/bt/sar/BtSarSanityTest.py
@@ -19,8 +19,8 @@
 import time
 
 from acts import asserts
-import acts.test_utils.bt.bt_test_utils as bt_utils
-from acts.test_utils.bt.BtSarBaseTest import BtSarBaseTest
+import acts_contrib.test_utils.bt.bt_test_utils as bt_utils
+from acts_contrib.test_utils.bt.BtSarBaseTest import BtSarBaseTest
 
 
 class BtSarSanityTest(BtSarBaseTest):
diff --git a/acts_tests/tests/google/bt/sar/BtSarTpcTest.py b/acts_tests/tests/google/bt/sar/BtSarTpcTest.py
index cf29d68..770ebf3 100644
--- a/acts_tests/tests/google/bt/sar/BtSarTpcTest.py
+++ b/acts_tests/tests/google/bt/sar/BtSarTpcTest.py
@@ -17,13 +17,13 @@
 import os
 import time
 import numpy as np
-import acts.test_utils.bt.bt_test_utils as bt_utils
+import acts_contrib.test_utils.bt.bt_test_utils as bt_utils
 from acts.metrics.loggers.blackbox import BlackboxMetricLogger
-import acts.test_utils.wifi.wifi_performance_test_utils as wifi_utils
+import acts_contrib.test_utils.wifi.wifi_performance_test_utils as wifi_utils
 
 from acts import asserts
 from functools import partial
-from acts.test_utils.bt.BtSarBaseTest import BtSarBaseTest
+from acts_contrib.test_utils.bt.BtSarBaseTest import BtSarBaseTest
 
 
 class BtSarTpcTest(BtSarBaseTest):
diff --git a/acts_tests/tests/google/bt/sdp/SdpSetupTest.py b/acts_tests/tests/google/bt/sdp/SdpSetupTest.py
index 0725825..6a9409a 100644
--- a/acts_tests/tests/google/bt/sdp/SdpSetupTest.py
+++ b/acts_tests/tests/google/bt/sdp/SdpSetupTest.py
@@ -22,12 +22,12 @@
 """
 from acts import signals
 from acts.base_test import BaseTestClass
-from acts.test_utils.abstract_devices.bluetooth_device import AndroidBluetoothDevice
-from acts.test_utils.abstract_devices.bluetooth_device import FuchsiaBluetoothDevice
-from acts.test_utils.abstract_devices.bluetooth_device import create_bluetooth_device
-from acts.test_utils.bt.bt_constants import bt_attribute_values
-from acts.test_utils.bt.bt_constants import sig_uuid_constants
-from acts.test_utils.fuchsia.sdp_records import sdp_pts_record_list
+from acts_contrib.test_utils.abstract_devices.bluetooth_device import AndroidBluetoothDevice
+from acts_contrib.test_utils.abstract_devices.bluetooth_device import FuchsiaBluetoothDevice
+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_uuid_constants
+from acts_contrib.test_utils.fuchsia.sdp_records import sdp_pts_record_list
 
 
 class SdpSetupTest(BaseTestClass):
diff --git a/acts_tests/tests/google/bt/system_tests/BtStressTest.py b/acts_tests/tests/google/bt/system_tests/BtStressTest.py
index 0473aa0..107fdac 100644
--- a/acts_tests/tests/google/bt/system_tests/BtStressTest.py
+++ b/acts_tests/tests/google/bt/system_tests/BtStressTest.py
@@ -20,13 +20,13 @@
 import time
 from acts.base_test import BaseTestClass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import bluetooth_off
-from acts.test_utils.bt.bt_constants import bluetooth_on
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.bt.bt_test_utils import pair_pri_to_sec
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import bluetooth_off
+from acts_contrib.test_utils.bt.bt_constants import bluetooth_on
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.bt.bt_test_utils import pair_pri_to_sec
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
 
 
 class BtStressTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/system_tests/RfcommLongevityTest.py b/acts_tests/tests/google/bt/system_tests/RfcommLongevityTest.py
index d1d4fe5..356ac8d 100644
--- a/acts_tests/tests/google/bt/system_tests/RfcommLongevityTest.py
+++ b/acts_tests/tests/google/bt/system_tests/RfcommLongevityTest.py
@@ -23,10 +23,10 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
-from acts.test_utils.bt.bt_test_utils import write_read_verify_data
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
+from acts_contrib.test_utils.bt.bt_test_utils import write_read_verify_data
 
 
 class RfcommLongevityTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/system_tests/RfcommStressTest.py b/acts_tests/tests/google/bt/system_tests/RfcommStressTest.py
index 3fac543..ea9d999 100644
--- a/acts_tests/tests/google/bt/system_tests/RfcommStressTest.py
+++ b/acts_tests/tests/google/bt/system_tests/RfcommStressTest.py
@@ -23,10 +23,10 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
-from acts.test_utils.bt.bt_test_utils import write_read_verify_data
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
+from acts_contrib.test_utils.bt.bt_test_utils import write_read_verify_data
 
 
 class RfcommStressTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/test_tools/BtReconnectTest.py b/acts_tests/tests/google/bt/test_tools/BtReconnectTest.py
index a56264b..a2f7f98 100644
--- a/acts_tests/tests/google/bt/test_tools/BtReconnectTest.py
+++ b/acts_tests/tests/google/bt/test_tools/BtReconnectTest.py
@@ -16,8 +16,8 @@
 
 import time
 
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import *
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import *
 
 
 class BtReconnectTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/test_tools/EnergyTest.py b/acts_tests/tests/google/bt/test_tools/EnergyTest.py
index a9afc86..7797f8a 100644
--- a/acts_tests/tests/google/bt/test_tools/EnergyTest.py
+++ b/acts_tests/tests/google/bt/test_tools/EnergyTest.py
@@ -18,7 +18,7 @@
 """
 
 from queue import Empty
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
 
 
 class EnergyTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/bt/test_tools/ToolsTest.py b/acts_tests/tests/google/bt/test_tools/ToolsTest.py
index eabf5fc..5bffd6f 100644
--- a/acts_tests/tests/google/bt/test_tools/ToolsTest.py
+++ b/acts_tests/tests/google/bt/test_tools/ToolsTest.py
@@ -14,8 +14,8 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import *
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import *
 
 import time
 import pprint
diff --git a/acts_tests/tests/google/coex/functionality_tests/CoexBasicFunctionalityTest.py b/acts_tests/tests/google/coex/functionality_tests/CoexBasicFunctionalityTest.py
index 38db103..1775b9e 100644
--- a/acts_tests/tests/google/coex/functionality_tests/CoexBasicFunctionalityTest.py
+++ b/acts_tests/tests/google/coex/functionality_tests/CoexBasicFunctionalityTest.py
@@ -21,11 +21,11 @@
 One Android device.
 """
 
-from acts.test_utils.coex.CoexBaseTest import CoexBaseTest
-from acts.test_utils.coex.coex_test_utils import multithread_func
-from acts.test_utils.coex.coex_test_utils import perform_classic_discovery
-from acts.test_utils.coex.coex_test_utils import toggle_bluetooth
-from acts.test_utils.coex.coex_test_utils import start_fping
+from acts_contrib.test_utils.coex.CoexBaseTest import CoexBaseTest
+from acts_contrib.test_utils.coex.coex_test_utils import multithread_func
+from acts_contrib.test_utils.coex.coex_test_utils import perform_classic_discovery
+from acts_contrib.test_utils.coex.coex_test_utils import toggle_bluetooth
+from acts_contrib.test_utils.coex.coex_test_utils import start_fping
 
 
 class CoexBasicFunctionalityTest(CoexBaseTest):
diff --git a/acts_tests/tests/google/coex/functionality_tests/CoexBtMultiProfileFunctionalityTest.py b/acts_tests/tests/google/coex/functionality_tests/CoexBtMultiProfileFunctionalityTest.py
index 53dc7fa..1acfc0c 100644
--- a/acts_tests/tests/google/coex/functionality_tests/CoexBtMultiProfileFunctionalityTest.py
+++ b/acts_tests/tests/google/coex/functionality_tests/CoexBtMultiProfileFunctionalityTest.py
@@ -21,15 +21,15 @@
 Two Android device.
 One A2DP and HFP Headset connected to Relay.
 """
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.coex.CoexBaseTest import CoexBaseTest
-from acts.test_utils.coex.coex_test_utils import connect_ble
-from acts.test_utils.coex.coex_test_utils import initiate_disconnect_from_hf
-from acts.test_utils.coex.coex_test_utils import multithread_func
-from acts.test_utils.coex.coex_test_utils import music_play_and_check_via_app
-from acts.test_utils.coex.coex_test_utils import pair_and_connect_headset
-from acts.test_utils.coex.coex_test_utils import setup_tel_config
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.coex.CoexBaseTest import CoexBaseTest
+from acts_contrib.test_utils.coex.coex_test_utils import connect_ble
+from acts_contrib.test_utils.coex.coex_test_utils import initiate_disconnect_from_hf
+from acts_contrib.test_utils.coex.coex_test_utils import multithread_func
+from acts_contrib.test_utils.coex.coex_test_utils import music_play_and_check_via_app
+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
 
 
 class CoexBtMultiProfileFunctionalityTest(CoexBaseTest):
diff --git a/acts_tests/tests/google/coex/functionality_tests/WlanWithHfpFunctionalityTest.py b/acts_tests/tests/google/coex/functionality_tests/WlanWithHfpFunctionalityTest.py
index f03ade0..f6aadc3 100644
--- a/acts_tests/tests/google/coex/functionality_tests/WlanWithHfpFunctionalityTest.py
+++ b/acts_tests/tests/google/coex/functionality_tests/WlanWithHfpFunctionalityTest.py
@@ -14,21 +14,21 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.coex.CoexBaseTest import CoexBaseTest
-from acts.test_utils.coex.coex_test_utils import connect_dev_to_headset
-from acts.test_utils.coex.coex_test_utils import connect_wlan_profile
-from acts.test_utils.coex.coex_test_utils import initiate_disconnect_from_hf
-from acts.test_utils.coex.coex_test_utils import initiate_disconnect_call_dut
-from acts.test_utils.coex.coex_test_utils import multithread_func
-from acts.test_utils.coex.coex_test_utils import pair_and_connect_headset
-from acts.test_utils.coex.coex_test_utils import perform_classic_discovery
-from acts.test_utils.coex.coex_test_utils import toggle_screen_state
-from acts.test_utils.coex.coex_test_utils import setup_tel_config
-from acts.test_utils.coex.coex_test_utils import start_fping
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import initiate_call
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.coex.CoexBaseTest import CoexBaseTest
+from acts_contrib.test_utils.coex.coex_test_utils import connect_dev_to_headset
+from acts_contrib.test_utils.coex.coex_test_utils import connect_wlan_profile
+from acts_contrib.test_utils.coex.coex_test_utils import initiate_disconnect_from_hf
+from acts_contrib.test_utils.coex.coex_test_utils import initiate_disconnect_call_dut
+from acts_contrib.test_utils.coex.coex_test_utils import multithread_func
+from acts_contrib.test_utils.coex.coex_test_utils import pair_and_connect_headset
+from acts_contrib.test_utils.coex.coex_test_utils import perform_classic_discovery
+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
 
 BLUETOOTH_WAIT_TIME = 2
 
diff --git a/acts_tests/tests/google/coex/hotspot_tests/HotspotWiFiChannelTest.py b/acts_tests/tests/google/coex/hotspot_tests/HotspotWiFiChannelTest.py
index b276a3a..2a26d88 100644
--- a/acts_tests/tests/google/coex/hotspot_tests/HotspotWiFiChannelTest.py
+++ b/acts_tests/tests/google/coex/hotspot_tests/HotspotWiFiChannelTest.py
@@ -25,16 +25,16 @@
 
 import acts.base_test
 import acts.controllers.rohdeschwarz_lib.cmw500 as cmw500
-from acts.test_utils.coex.hotspot_utils import band_channel_map
-from acts.test_utils.coex.hotspot_utils import supported_lte_bands
-from acts.test_utils.coex.hotspot_utils import tdd_band_list
-from acts.test_utils.coex.hotspot_utils import wifi_channel_map
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.wifi.wifi_test_utils import reset_wifi
-from acts.test_utils.wifi.wifi_test_utils import start_wifi_tethering
-from acts.test_utils.wifi.wifi_test_utils import stop_wifi_tethering
-from acts.test_utils.wifi.wifi_test_utils import wifi_connect
-from acts.test_utils.wifi.wifi_test_utils import WifiEnums
+from acts_contrib.test_utils.coex.hotspot_utils import band_channel_map
+from acts_contrib.test_utils.coex.hotspot_utils import supported_lte_bands
+from acts_contrib.test_utils.coex.hotspot_utils import tdd_band_list
+from acts_contrib.test_utils.coex.hotspot_utils import wifi_channel_map
+from acts_contrib.test_utils.tel.tel_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.wifi.wifi_test_utils import reset_wifi
+from acts_contrib.test_utils.wifi.wifi_test_utils import start_wifi_tethering
+from acts_contrib.test_utils.wifi.wifi_test_utils import stop_wifi_tethering
+from acts_contrib.test_utils.wifi.wifi_test_utils import wifi_connect
+from acts_contrib.test_utils.wifi.wifi_test_utils import WifiEnums
 
 BANDWIDTH_2G = 20
 CNSS_LOG_PATH = '/data/vendor/wifi/wlan_logs'
diff --git a/acts_tests/tests/google/coex/performance_tests/CoexBasicPerformanceTest.py b/acts_tests/tests/google/coex/performance_tests/CoexBasicPerformanceTest.py
index c5c1879..76d7151 100644
--- a/acts_tests/tests/google/coex/performance_tests/CoexBasicPerformanceTest.py
+++ b/acts_tests/tests/google/coex/performance_tests/CoexBasicPerformanceTest.py
@@ -16,9 +16,9 @@
 
 import itertools
 
-from acts.test_utils.bt.bt_test_utils import enable_bluetooth
-from acts.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
-from acts.test_utils.coex.coex_test_utils import perform_classic_discovery
+from acts_contrib.test_utils.bt.bt_test_utils import enable_bluetooth
+from acts_contrib.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
+from acts_contrib.test_utils.coex.coex_test_utils import perform_classic_discovery
 
 
 class CoexBasicPerformanceTest(CoexPerformanceBaseTest):
diff --git a/acts_tests/tests/google/coex/performance_tests/CoexBtMultiProfilePerformanceTest.py b/acts_tests/tests/google/coex/performance_tests/CoexBtMultiProfilePerformanceTest.py
index af75373..de36052 100644
--- a/acts_tests/tests/google/coex/performance_tests/CoexBtMultiProfilePerformanceTest.py
+++ b/acts_tests/tests/google/coex/performance_tests/CoexBtMultiProfilePerformanceTest.py
@@ -24,18 +24,18 @@
 """
 import time
 
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.car.tel_telecom_utils import wait_for_dialing
-from acts.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
-from acts.test_utils.coex.coex_test_utils import connect_dev_to_headset
-from acts.test_utils.coex.coex_test_utils import music_play_and_check_via_app
-from acts.test_utils.coex.coex_test_utils import pair_and_connect_headset
-from acts.test_utils.coex.coex_test_utils import setup_tel_config
-from acts.test_utils.coex.coex_test_utils import connect_wlan_profile
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import wait_and_answer_call
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.car.tel_telecom_utils import wait_for_dialing
+from acts_contrib.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
+from acts_contrib.test_utils.coex.coex_test_utils import connect_dev_to_headset
+from acts_contrib.test_utils.coex.coex_test_utils import music_play_and_check_via_app
+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
 
 
 class CoexBtMultiProfilePerformanceTest(CoexPerformanceBaseTest):
diff --git a/acts_tests/tests/google/coex/performance_tests/WlanStandalonePerformanceTest.py b/acts_tests/tests/google/coex/performance_tests/WlanStandalonePerformanceTest.py
index 8edd0c5..4aea4e2 100644
--- a/acts_tests/tests/google/coex/performance_tests/WlanStandalonePerformanceTest.py
+++ b/acts_tests/tests/google/coex/performance_tests/WlanStandalonePerformanceTest.py
@@ -16,8 +16,8 @@
 
 import itertools
 
-from acts.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
-from acts.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts_contrib.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth
 
 
 class WlanStandalonePerformanceTest(CoexPerformanceBaseTest):
diff --git a/acts_tests/tests/google/coex/performance_tests/WlanWithA2dpPerformanceTest.py b/acts_tests/tests/google/coex/performance_tests/WlanWithA2dpPerformanceTest.py
index e06b4c6..01e177f 100644
--- a/acts_tests/tests/google/coex/performance_tests/WlanWithA2dpPerformanceTest.py
+++ b/acts_tests/tests/google/coex/performance_tests/WlanWithA2dpPerformanceTest.py
@@ -16,15 +16,15 @@
 
 import itertools
 
-from acts.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bf
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
-from acts.test_utils.coex.coex_test_utils import avrcp_actions
-from acts.test_utils.coex.coex_test_utils import music_play_and_check
-from acts.test_utils.coex.coex_test_utils import pair_and_connect_headset
-from acts.test_utils.coex.coex_test_utils import perform_classic_discovery
-from acts.test_utils.coex.coex_test_utils import push_music_to_android_device
+from acts_contrib.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bf
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
+from acts_contrib.test_utils.coex.coex_test_utils import avrcp_actions
+from acts_contrib.test_utils.coex.coex_test_utils import music_play_and_check
+from acts_contrib.test_utils.coex.coex_test_utils import pair_and_connect_headset
+from acts_contrib.test_utils.coex.coex_test_utils import perform_classic_discovery
+from acts_contrib.test_utils.coex.coex_test_utils import push_music_to_android_device
 
 
 class WlanWithA2dpPerformanceTest(CoexPerformanceBaseTest):
diff --git a/acts_tests/tests/google/coex/performance_tests/WlanWithBlePerformanceTest.py b/acts_tests/tests/google/coex/performance_tests/WlanWithBlePerformanceTest.py
index 23d7423..e3179df 100644
--- a/acts_tests/tests/google/coex/performance_tests/WlanWithBlePerformanceTest.py
+++ b/acts_tests/tests/google/coex/performance_tests/WlanWithBlePerformanceTest.py
@@ -17,12 +17,12 @@
 import itertools
 import time
 
-from acts.test_utils.bt.bt_gatt_utils import close_gatt_client
-from acts.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
-from acts.test_utils.bt.bt_gatt_utils import GattTestUtilsError
-from acts.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
-from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
-from acts.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
+from acts_contrib.test_utils.bt.bt_gatt_utils import close_gatt_client
+from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection
+from acts_contrib.test_utils.bt.bt_gatt_utils import GattTestUtilsError
+from acts_contrib.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection
+from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects
+from acts_contrib.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
 
 
 class WlanWithBlePerformanceTest(CoexPerformanceBaseTest):
diff --git a/acts_tests/tests/google/coex/performance_tests/WlanWithHfpPerformanceTest.py b/acts_tests/tests/google/coex/performance_tests/WlanWithHfpPerformanceTest.py
index bec999e..b56cbde 100644
--- a/acts_tests/tests/google/coex/performance_tests/WlanWithHfpPerformanceTest.py
+++ b/acts_tests/tests/google/coex/performance_tests/WlanWithHfpPerformanceTest.py
@@ -16,14 +16,14 @@
 
 import time
 
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
-from acts.test_utils.coex.coex_test_utils import initiate_disconnect_from_hf
-from acts.test_utils.coex.coex_test_utils import pair_and_connect_headset
-from acts.test_utils.coex.coex_test_utils import setup_tel_config
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import initiate_call
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.coex.CoexPerformanceBaseTest import CoexPerformanceBaseTest
+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
 
 
 class WlanWithHfpPerformanceTest(CoexPerformanceBaseTest):
diff --git a/acts_tests/tests/google/coex/stress_tests/CoexBasicStressTest.py b/acts_tests/tests/google/coex/stress_tests/CoexBasicStressTest.py
index 1bd7b06..60881de 100644
--- a/acts_tests/tests/google/coex/stress_tests/CoexBasicStressTest.py
+++ b/acts_tests/tests/google/coex/stress_tests/CoexBasicStressTest.py
@@ -22,9 +22,9 @@
 """
 import time
 
-from acts.test_utils.coex.CoexBaseTest import CoexBaseTest
-from acts.test_utils.coex.coex_test_utils import toggle_bluetooth
-from acts.test_utils.coex.coex_test_utils import device_discoverable
+from acts_contrib.test_utils.coex.CoexBaseTest import CoexBaseTest
+from acts_contrib.test_utils.coex.coex_test_utils import toggle_bluetooth
+from acts_contrib.test_utils.coex.coex_test_utils import device_discoverable
 
 
 class CoexBasicStressTest(CoexBaseTest):
diff --git a/acts_tests/tests/google/coex/stress_tests/CoexBtMultiProfileStressTest.py b/acts_tests/tests/google/coex/stress_tests/CoexBtMultiProfileStressTest.py
index 5516bec..a709dda 100644
--- a/acts_tests/tests/google/coex/stress_tests/CoexBtMultiProfileStressTest.py
+++ b/acts_tests/tests/google/coex/stress_tests/CoexBtMultiProfileStressTest.py
@@ -22,11 +22,11 @@
 """
 import time
 
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.coex.CoexBaseTest import CoexBaseTest
-from acts.test_utils.coex.coex_test_utils import disconnect_headset_from_dev
-from acts.test_utils.coex.coex_test_utils import pair_and_connect_headset
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.coex.CoexBaseTest import CoexBaseTest
+from acts_contrib.test_utils.coex.coex_test_utils import disconnect_headset_from_dev
+from acts_contrib.test_utils.coex.coex_test_utils import pair_and_connect_headset
 
 
 class CoexBtMultiProfileStressTest(CoexBaseTest):
diff --git a/acts_tests/tests/google/coex/stress_tests/CoexHfpStressTest.py b/acts_tests/tests/google/coex/stress_tests/CoexHfpStressTest.py
index b210d59..8add009 100644
--- a/acts_tests/tests/google/coex/stress_tests/CoexHfpStressTest.py
+++ b/acts_tests/tests/google/coex/stress_tests/CoexHfpStressTest.py
@@ -16,18 +16,18 @@
 
 import time
 
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
-from acts.test_utils.coex.CoexBaseTest import CoexBaseTest
-from acts.test_utils.coex.coex_test_utils import connect_dev_to_headset
-from acts.test_utils.coex.coex_test_utils import disconnect_headset_from_dev
-from acts.test_utils.coex.coex_test_utils import initiate_disconnect_from_hf
-from acts.test_utils.coex.coex_test_utils import pair_and_connect_headset
-from acts.test_utils.tel.tel_defines import AUDIO_ROUTE_BLUETOOTH
-from acts.test_utils.tel.tel_defines import AUDIO_ROUTE_SPEAKER
+from acts_contrib.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt.bt_test_utils import clear_bonded_devices
+from acts_contrib.test_utils.coex.CoexBaseTest import CoexBaseTest
+from acts_contrib.test_utils.coex.coex_test_utils import connect_dev_to_headset
+from acts_contrib.test_utils.coex.coex_test_utils import disconnect_headset_from_dev
+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.tel.tel_defines import AUDIO_ROUTE_BLUETOOTH
+from acts_contrib.test_utils.tel.tel_defines import AUDIO_ROUTE_SPEAKER
 
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_voice_utils import set_audio_route
+from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_voice_utils import set_audio_route
 
 
 class CoexHfpStressTest(CoexBaseTest):
diff --git a/acts_tests/tests/google/experimental/BluetoothLatencyTest.py b/acts_tests/tests/google/experimental/BluetoothLatencyTest.py
index ea6ed4a..6ccc949 100644
--- a/acts_tests/tests/google/experimental/BluetoothLatencyTest.py
+++ b/acts_tests/tests/google/experimental/BluetoothLatencyTest.py
@@ -22,13 +22,13 @@
 from acts.base_test import BaseTestClass
 from acts.signals import TestPass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.bt_test_utils import verify_server_and_client_connected
-from acts.test_utils.bt.bt_test_utils import write_read_verify_data
-from acts.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger
-from acts.test_utils.bt.loggers.protos import bluetooth_metric_pb2 as proto_module
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import verify_server_and_client_connected
+from acts_contrib.test_utils.bt.bt_test_utils import write_read_verify_data
+from acts_contrib.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger
+from acts_contrib.test_utils.bt.loggers.protos import bluetooth_metric_pb2 as proto_module
 from acts.utils import set_location_service
 
 
diff --git a/acts_tests/tests/google/experimental/BluetoothPairAndConnectTest.py b/acts_tests/tests/google/experimental/BluetoothPairAndConnectTest.py
index 2402fb5..2040af5 100644
--- a/acts_tests/tests/google/experimental/BluetoothPairAndConnectTest.py
+++ b/acts_tests/tests/google/experimental/BluetoothPairAndConnectTest.py
@@ -28,11 +28,11 @@
 from acts.signals import TestFailure
 from acts.signals import TestPass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import factory_reset_bluetooth
-from acts.test_utils.bt.bt_test_utils import enable_bluetooth
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import factory_reset_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import enable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger
 from acts.utils import set_location_service
 
 
diff --git a/acts_tests/tests/google/experimental/BluetoothReconnectTest.py b/acts_tests/tests/google/experimental/BluetoothReconnectTest.py
index 5339e51..dc66486 100644
--- a/acts_tests/tests/google/experimental/BluetoothReconnectTest.py
+++ b/acts_tests/tests/google/experimental/BluetoothReconnectTest.py
@@ -25,10 +25,10 @@
 from acts.signals import TestFailure
 from acts.signals import TestPass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import enable_bluetooth
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import enable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger
 from acts.utils import set_location_service
 
 # The number of reconnections to be attempted during the test
diff --git a/acts_tests/tests/google/experimental/BluetoothThroughputTest.py b/acts_tests/tests/google/experimental/BluetoothThroughputTest.py
index 1f53172..f1d8dba 100644
--- a/acts_tests/tests/google/experimental/BluetoothThroughputTest.py
+++ b/acts_tests/tests/google/experimental/BluetoothThroughputTest.py
@@ -19,12 +19,12 @@
 from acts.base_test import BaseTestClass
 from acts.signals import TestPass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.bt.bt_test_utils import verify_server_and_client_connected
-from acts.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger
-from acts.test_utils.bt.loggers.protos import bluetooth_metric_pb2 as proto_module
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import orchestrate_rfcomm_connection
+from acts_contrib.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
+from acts_contrib.test_utils.bt.bt_test_utils import verify_server_and_client_connected
+from acts_contrib.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger
+from acts_contrib.test_utils.bt.loggers.protos import bluetooth_metric_pb2 as proto_module
 from acts.utils import set_location_service
 
 
diff --git a/acts_tests/tests/google/fuchsia/backlight/BacklightTest.py b/acts_tests/tests/google/fuchsia/backlight/BacklightTest.py
index 9d15781..9cc6c0c 100644
--- a/acts_tests/tests/google/fuchsia/backlight/BacklightTest.py
+++ b/acts_tests/tests/google/fuchsia/backlight/BacklightTest.py
@@ -21,7 +21,7 @@
 from acts import asserts, signals
 from acts.base_test import BaseTestClass
 from acts.libs.proc.job import Error
-from acts.test_utils.tel.tel_test_utils import setup_droid_properties
+from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
 
 BRIGHTNESS_CHANGE_SLEEP_TIME_SECONDS = 2
 
diff --git a/acts_tests/tests/google/fuchsia/bt/BleFuchsiaAndroidTest.py b/acts_tests/tests/google/fuchsia/bt/BleFuchsiaAndroidTest.py
index e799991..636db87 100644
--- a/acts_tests/tests/google/fuchsia/bt/BleFuchsiaAndroidTest.py
+++ b/acts_tests/tests/google/fuchsia/bt/BleFuchsiaAndroidTest.py
@@ -22,14 +22,14 @@
 import time
 
 from acts.controllers import android_device
-from acts.test_utils.fuchsia.bt_test_utils import le_scan_for_device_by_name
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes
-from acts.test_utils.bt.bt_constants import adv_succ
-from acts.test_utils.bt.bt_constants import ble_scan_settings_modes
-from acts.test_utils.bt.bt_constants import scan_result
-from acts.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
-from acts.test_utils.bt.bt_test_utils import reset_bluetooth
+from acts_contrib.test_utils.fuchsia.bt_test_utils import le_scan_for_device_by_name
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.bt_constants import ble_advertise_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import adv_succ
+from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes
+from acts_contrib.test_utils.bt.bt_constants import scan_result
+from acts_contrib.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
+from acts_contrib.test_utils.bt.bt_test_utils import reset_bluetooth
 
 
 class BleFuchsiaAndroidTest(BluetoothBaseTest):
diff --git a/acts_tests/tests/google/fuchsia/bt/BleFuchsiaTest.py b/acts_tests/tests/google/fuchsia/bt/BleFuchsiaTest.py
index e0efc77..a74db66 100644
--- a/acts_tests/tests/google/fuchsia/bt/BleFuchsiaTest.py
+++ b/acts_tests/tests/google/fuchsia/bt/BleFuchsiaTest.py
@@ -21,7 +21,7 @@
 import time
 
 from acts.base_test import BaseTestClass
-from acts.test_utils.fuchsia.bt_test_utils import le_scan_for_device_by_name
+from acts_contrib.test_utils.fuchsia.bt_test_utils import le_scan_for_device_by_name
 
 
 class BleFuchsiaTest(BaseTestClass):
diff --git a/acts_tests/tests/google/fuchsia/bt/FuchsiaBtMacAddressTest.py b/acts_tests/tests/google/fuchsia/bt/FuchsiaBtMacAddressTest.py
index e368127..2ce6b54 100644
--- a/acts_tests/tests/google/fuchsia/bt/FuchsiaBtMacAddressTest.py
+++ b/acts_tests/tests/google/fuchsia/bt/FuchsiaBtMacAddressTest.py
@@ -26,7 +26,7 @@
 from acts import signals
 from acts.base_test import BaseTestClass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.bt_test_utils import generate_id_by_size
+from acts_contrib.test_utils.bt.bt_test_utils import generate_id_by_size
 
 
 class FuchsiaBtMacAddressTest(BaseTestClass):
diff --git a/acts_tests/tests/google/fuchsia/bt/FuchsiaBtScanTest.py b/acts_tests/tests/google/fuchsia/bt/FuchsiaBtScanTest.py
index 72103e9..074d3e5 100644
--- a/acts_tests/tests/google/fuchsia/bt/FuchsiaBtScanTest.py
+++ b/acts_tests/tests/google/fuchsia/bt/FuchsiaBtScanTest.py
@@ -26,7 +26,7 @@
 from acts import signals
 from acts.base_test import BaseTestClass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.bt_test_utils import generate_id_by_size
+from acts_contrib.test_utils.bt.bt_test_utils import generate_id_by_size
 
 
 class FuchsiaBtScanTest(BaseTestClass):
diff --git a/acts_tests/tests/google/fuchsia/bt/command_input.py b/acts_tests/tests/google/fuchsia/bt/command_input.py
index 872a896..75d904c 100644
--- a/acts_tests/tests/google/fuchsia/bt/command_input.py
+++ b/acts_tests/tests/google/fuchsia/bt/command_input.py
@@ -43,13 +43,13 @@
 
 """
 
-from acts.test_utils.abstract_devices.bluetooth_device import create_bluetooth_device
-from acts.test_utils.bt.bt_constants import bt_attribute_values
-from acts.test_utils.bt.bt_constants import sig_appearance_constants
-from acts.test_utils.bt.bt_constants import sig_uuid_constants
-from acts.test_utils.fuchsia.sdp_records import sdp_pts_record_list
+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
+from acts_contrib.test_utils.bt.bt_constants import sig_uuid_constants
+from acts_contrib.test_utils.fuchsia.sdp_records import sdp_pts_record_list
 
-import acts.test_utils.bt.gatt_test_database as gatt_test_database
+import acts_contrib.test_utils.bt.gatt_test_database as gatt_test_database
 
 import cmd
 import pprint
@@ -1391,7 +1391,7 @@
             Supports Tab Autocomplete.
         Input(s):
             descriptor_db_name: The descriptor db name that matches one in
-                acts.test_utils.bt.gatt_test_database
+                acts_contrib.test_utils.bt.gatt_test_database
         Usage:
           Examples:
             gatts_setup_database LARGE_DB_1
diff --git a/acts_tests/tests/google/fuchsia/bt/gatt/GattConnectionStressTest.py b/acts_tests/tests/google/fuchsia/bt/gatt/GattConnectionStressTest.py
index 2f2e666..da7d641 100644
--- a/acts_tests/tests/google/fuchsia/bt/gatt/GattConnectionStressTest.py
+++ b/acts_tests/tests/google/fuchsia/bt/gatt/GattConnectionStressTest.py
@@ -30,8 +30,8 @@
 from acts import signals
 from acts.base_test import BaseTestClass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.bt_test_utils import generate_id_by_size
-from acts.test_utils.fuchsia.bt_test_utils import le_scan_for_device_by_name
+from acts_contrib.test_utils.bt.bt_test_utils import generate_id_by_size
+from acts_contrib.test_utils.fuchsia.bt_test_utils import le_scan_for_device_by_name
 import time
 
 
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 c554405..c854281 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
@@ -18,12 +18,12 @@
 GATT server dictionaries which will be setup in various tests.
 """
 
-from acts.test_utils.bt.bt_constants import gatt_characteristic
-from acts.test_utils.bt.bt_constants import gatt_descriptor
-from acts.test_utils.bt.bt_constants import gatt_service_types
-from acts.test_utils.bt.bt_constants import gatt_char_types
-from acts.test_utils.bt.bt_constants import gatt_characteristic_value_format
-from acts.test_utils.bt.bt_constants import gatt_char_desc_uuids
+from acts_contrib.test_utils.bt.bt_constants import gatt_characteristic
+from acts_contrib.test_utils.bt.bt_constants import gatt_descriptor
+from acts_contrib.test_utils.bt.bt_constants import gatt_service_types
+from acts_contrib.test_utils.bt.bt_constants import gatt_char_types
+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 = {
diff --git a/acts_tests/tests/google/fuchsia/examples/Sl4fSanityTest.py b/acts_tests/tests/google/fuchsia/examples/Sl4fSanityTest.py
index d2116a9..d7491dd 100644
--- a/acts_tests/tests/google/fuchsia/examples/Sl4fSanityTest.py
+++ b/acts_tests/tests/google/fuchsia/examples/Sl4fSanityTest.py
@@ -24,7 +24,7 @@
 import uuid
 
 from acts import signals
-from acts.test_utils.tel.tel_test_utils import setup_droid_properties
+from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
 
 
 class Sl4fSanityTest(BaseTestClass):
diff --git a/acts_tests/tests/google/fuchsia/netstack/NetstackIxiaTest.py b/acts_tests/tests/google/fuchsia/netstack/NetstackIxiaTest.py
index 81d69bf..129d28f 100644
--- a/acts_tests/tests/google/fuchsia/netstack/NetstackIxiaTest.py
+++ b/acts_tests/tests/google/fuchsia/netstack/NetstackIxiaTest.py
@@ -20,7 +20,7 @@
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_security
 
-from acts.test_utils.net.NetstackBaseTest import NetstackBaseTest
+from acts_contrib.test_utils.net.NetstackBaseTest import NetstackBaseTest
 
 from acts.utils import rand_ascii_str
 
diff --git a/acts_tests/tests/google/fuchsia/ram/RamTest.py b/acts_tests/tests/google/fuchsia/ram/RamTest.py
index b9bd75c..2b11e01 100644
--- a/acts_tests/tests/google/fuchsia/ram/RamTest.py
+++ b/acts_tests/tests/google/fuchsia/ram/RamTest.py
@@ -21,7 +21,7 @@
 from acts import asserts, signals
 from acts.base_test import BaseTestClass
 from acts.libs.proc.job import Error
-from acts.test_utils.tel.tel_test_utils import setup_droid_properties
+from acts_contrib.test_utils.tel.tel_test_utils import setup_droid_properties
 
 # Some number bigger than the minimum 512k:
 MEMORY_CYCLES_TO_MEASURE = 1024 * 1024
diff --git a/acts_tests/tests/google/fuchsia/wlan/BeaconLossTest.py b/acts_tests/tests/google/fuchsia/wlan/BeaconLossTest.py
index 2944707..00e61b3 100644
--- a/acts_tests/tests/google/fuchsia/wlan/BeaconLossTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/BeaconLossTest.py
@@ -31,11 +31,11 @@
 from acts import utils
 from acts.base_test import BaseTestClass
 from acts.controllers.ap_lib import hostapd_constants
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import disconnect
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import associate
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import disconnect
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import associate
+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.utils import rand_ascii_str
 
 
diff --git a/acts_tests/tests/google/fuchsia/wlan/ChannelSweepTest.py b/acts_tests/tests/google/fuchsia/wlan/ChannelSweepTest.py
index 431a2a3..57ecbb0 100644
--- a/acts_tests/tests/google/fuchsia/wlan/ChannelSweepTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/ChannelSweepTest.py
@@ -33,9 +33,9 @@
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib.hostapd_security import Security
 from acts.controllers.iperf_server import IPerfResult
-from acts.test_utils.abstract_devices.utils_lib import wlan_utils
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.abstract_devices.utils_lib import wlan_utils
+from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 N_CAPABILITIES_DEFAULT = [
     hostapd_constants.N_CAPABILITY_LDPC, hostapd_constants.N_CAPABILITY_SGI20,
diff --git a/acts_tests/tests/google/fuchsia/wlan/ConnectionStressTest.py b/acts_tests/tests/google/fuchsia/wlan/ConnectionStressTest.py
index 836846e..7ad2fdb 100644
--- a/acts_tests/tests/google/fuchsia/wlan/ConnectionStressTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/ConnectionStressTest.py
@@ -26,13 +26,13 @@
 from acts import signals
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_security
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import associate
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import disconnect
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
-from acts.test_utils.fuchsia import utils
-from acts.test_utils.tel.tel_test_utils import setup_droid_properties
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import associate
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import disconnect
+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
 
 
diff --git a/acts_tests/tests/google/fuchsia/wlan/DownloadStressTest.py b/acts_tests/tests/google/fuchsia/wlan/DownloadStressTest.py
index f3f3803..5fa4fbb 100644
--- a/acts_tests/tests/google/fuchsia/wlan/DownloadStressTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/DownloadStressTest.py
@@ -24,10 +24,10 @@
 from acts.base_test import BaseTestClass
 from acts import signals
 from acts.controllers.ap_lib import hostapd_constants
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap_and_associate
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.fuchsia import utils
-from acts.test_utils.tel.tel_test_utils import setup_droid_properties
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap_and_associate
+from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+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
 
 
diff --git a/acts_tests/tests/google/fuchsia/wlan/PingStressTest.py b/acts_tests/tests/google/fuchsia/wlan/PingStressTest.py
index 8577f49..ef860a9 100644
--- a/acts_tests/tests/google/fuchsia/wlan/PingStressTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/PingStressTest.py
@@ -25,10 +25,10 @@
 
 from acts import signals
 from acts.controllers.ap_lib import hostapd_constants
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap_and_associate
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.tel.tel_test_utils import setup_droid_properties
-from acts.test_utils.fuchsia import utils
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap_and_associate
+from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+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
 
 
diff --git a/acts_tests/tests/google/fuchsia/wlan/SoftApTest.py b/acts_tests/tests/google/fuchsia/wlan/SoftApTest.py
index 0f2e697..c364481 100644
--- a/acts_tests/tests/google/fuchsia/wlan/SoftApTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/SoftApTest.py
@@ -26,9 +26,9 @@
 from acts.controllers import iperf_client
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_security
-from acts.test_utils.abstract_devices.utils_lib import wlan_utils
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
+from acts_contrib.test_utils.abstract_devices.utils_lib import wlan_utils
+from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
 
 ANDROID_DEFAULT_WLAN_INTERFACE = 'wlan0'
 CONNECTIVITY_MODE_LOCAL = 'local_only'
diff --git a/acts_tests/tests/google/fuchsia/wlan/VapeInteropTest.py b/acts_tests/tests/google/fuchsia/wlan/VapeInteropTest.py
index e9e216c..dd6374d 100644
--- a/acts_tests/tests/google/fuchsia/wlan/VapeInteropTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/VapeInteropTest.py
@@ -18,10 +18,10 @@
 from acts.controllers.ap_lib import hostapd_ap_preset
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib.hostapd_security import Security
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 
 class VapeInteropTest(AbstractDeviceWlanDeviceBaseTest):
diff --git a/acts_tests/tests/google/fuchsia/wlan/WlanInterfaceTest.py b/acts_tests/tests/google/fuchsia/wlan/WlanInterfaceTest.py
index d470bf7..4994dd2 100644
--- a/acts_tests/tests/google/fuchsia/wlan/WlanInterfaceTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/WlanInterfaceTest.py
@@ -17,8 +17,8 @@
 from acts import signals
 
 from acts.base_test import BaseTestClass
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
+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
 
 
 class WlanInterfaceTest(AbstractDeviceWlanDeviceBaseTest):
diff --git a/acts_tests/tests/google/fuchsia/wlan/WlanPhyCompliance11ACTest.py b/acts_tests/tests/google/fuchsia/wlan/WlanPhyCompliance11ACTest.py
index fde4e6d..72dcfe6 100644
--- a/acts_tests/tests/google/fuchsia/wlan/WlanPhyCompliance11ACTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/WlanPhyCompliance11ACTest.py
@@ -21,10 +21,10 @@
 from acts.controllers.ap_lib.hostapd_security import Security
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_config
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.utils import rand_ascii_str
 
 # AC Capabilities
diff --git a/acts_tests/tests/google/fuchsia/wlan/WlanPhyCompliance11NTest.py b/acts_tests/tests/google/fuchsia/wlan/WlanPhyCompliance11NTest.py
index f8cc874..f538242 100644
--- a/acts_tests/tests/google/fuchsia/wlan/WlanPhyCompliance11NTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/WlanPhyCompliance11NTest.py
@@ -21,10 +21,10 @@
 from acts.controllers.ap_lib.hostapd_security import Security
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_config
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.utils import rand_ascii_str
 
 FREQUENCY_24 = ['2.4GHz']
diff --git a/acts_tests/tests/google/fuchsia/wlan/WlanPhyComplianceABGTest.py b/acts_tests/tests/google/fuchsia/wlan/WlanPhyComplianceABGTest.py
index 8ef8591..df9931b 100644
--- a/acts_tests/tests/google/fuchsia/wlan/WlanPhyComplianceABGTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/WlanPhyComplianceABGTest.py
@@ -18,11 +18,11 @@
 
 from acts.controllers.ap_lib import hostapd_ap_preset
 from acts.controllers.ap_lib import hostapd_constants
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate
-from acts.test_utils.abstract_devices.utils_lib.wlan_policy_utils import setup_policy_tests, restore_state
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_policy_utils import setup_policy_tests, restore_state
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 
 class WlanPhyComplianceABGTest(AbstractDeviceWlanDeviceBaseTest):
diff --git a/acts_tests/tests/google/fuchsia/wlan/WlanRebootTest.py b/acts_tests/tests/google/fuchsia/wlan/WlanRebootTest.py
index 7c8bd21..d880924 100644
--- a/acts_tests/tests/google/fuchsia/wlan/WlanRebootTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/WlanRebootTest.py
@@ -30,9 +30,9 @@
 from acts.controllers.ap_lib.radvd import Radvd
 from acts.controllers.ap_lib import radvd_constants
 from acts.controllers.ap_lib.radvd_config import RadvdConfig
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.utils_lib import wlan_utils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.abstract_devices.wlan_device import create_wlan_device
+from acts_contrib.test_utils.abstract_devices.utils_lib import wlan_utils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 # Constants, for readibility
 AP = 'ap'
diff --git a/acts_tests/tests/google/fuchsia/wlan/WlanRvrTest.py b/acts_tests/tests/google/fuchsia/wlan/WlanRvrTest.py
index 87bf1a7..4eb07fc 100644
--- a/acts_tests/tests/google/fuchsia/wlan/WlanRvrTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/WlanRvrTest.py
@@ -24,11 +24,11 @@
 from acts.controllers.ap_lib.hostapd_security import Security
 from acts.controllers.attenuator import get_attenuators_for_device
 from acts.controllers.iperf_server import IPerfResult
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import associate
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import associate
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
+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
 from acts.utils import rand_ascii_str
 
 from bokeh.plotting import ColumnDataSource
diff --git a/acts_tests/tests/google/fuchsia/wlan/WlanScanTest.py b/acts_tests/tests/google/fuchsia/wlan/WlanScanTest.py
index 012e086..eca08cb 100644
--- a/acts_tests/tests/google/fuchsia/wlan/WlanScanTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/WlanScanTest.py
@@ -25,14 +25,14 @@
 import time
 
 import acts.base_test
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 from acts import signals
 from acts.controllers.ap_lib import hostapd_ap_preset
 from acts.controllers.ap_lib import hostapd_bss_settings
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_security
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 
 class WlanScanTest(WifiBaseTest):
diff --git a/acts_tests/tests/google/fuchsia/wlan/WlanSecurityComplianceABGTest.py b/acts_tests/tests/google/fuchsia/wlan/WlanSecurityComplianceABGTest.py
index 2f43085..79878fc 100644
--- a/acts_tests/tests/google/fuchsia/wlan/WlanSecurityComplianceABGTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan/WlanSecurityComplianceABGTest.py
@@ -19,11 +19,11 @@
 
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib.hostapd_security import Security
-from acts.test_utils.abstract_devices.wlan_device import create_wlan_device
-from acts.test_utils.abstract_devices.wlan_device_lib.AbstractDeviceWlanDeviceBaseTest import AbstractDeviceWlanDeviceBaseTest
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate
-from acts.test_utils.abstract_devices.utils_lib.wlan_policy_utils import setup_policy_tests, restore_state
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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.abstract_devices.utils_lib.wlan_utils import validate_setup_ap_and_associate
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_policy_utils import setup_policy_tests, restore_state
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.utils import rand_ascii_str
 from acts.utils import rand_hex_str
 
diff --git a/acts_tests/tests/google/fuchsia/wlan_policy/HiddenNetworksTest.py b/acts_tests/tests/google/fuchsia/wlan_policy/HiddenNetworksTest.py
index 670aec0..ad911ca 100644
--- a/acts_tests/tests/google/fuchsia/wlan_policy/HiddenNetworksTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan_policy/HiddenNetworksTest.py
@@ -16,9 +16,9 @@
 from acts import signals
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_security
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
-from acts.test_utils.abstract_devices.utils_lib.wlan_policy_utils import reboot_device, restore_state, save_network, setup_policy_tests
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_policy_utils import reboot_device, restore_state, save_network, setup_policy_tests
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.utils import rand_ascii_str
 import time
 
diff --git a/acts_tests/tests/google/fuchsia/wlan_policy/PolicyScanTest.py b/acts_tests/tests/google/fuchsia/wlan_policy/PolicyScanTest.py
index a1c0d7b..beb3fc2 100644
--- a/acts_tests/tests/google/fuchsia/wlan_policy/PolicyScanTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan_policy/PolicyScanTest.py
@@ -21,7 +21,7 @@
 from acts import signals, utils
 from acts.controllers.ap_lib import (hostapd_ap_preset, hostapd_bss_settings,
                                      hostapd_constants, hostapd_security)
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 
 class PolicyScanTest(WifiBaseTest):
diff --git a/acts_tests/tests/google/fuchsia/wlan_policy/SavedNetworksTest.py b/acts_tests/tests/google/fuchsia/wlan_policy/SavedNetworksTest.py
index e16c4f6..1a8bc42 100644
--- a/acts_tests/tests/google/fuchsia/wlan_policy/SavedNetworksTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan_policy/SavedNetworksTest.py
@@ -22,9 +22,9 @@
 from acts.controllers.ap_lib import hostapd_ap_preset
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_security
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
-from acts.test_utils.abstract_devices.utils_lib.wlan_policy_utils import reboot_device, restore_state, save_network, setup_policy_tests
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_policy_utils import reboot_device, restore_state, save_network, setup_policy_tests
 from acts.utils import rand_ascii_str, rand_hex_str, timeout
 import requests
 import time
diff --git a/acts_tests/tests/google/fuchsia/wlan_policy/StartStopClientConnectionsTest.py b/acts_tests/tests/google/fuchsia/wlan_policy/StartStopClientConnectionsTest.py
index 7156a80..5ca70f4 100644
--- a/acts_tests/tests/google/fuchsia/wlan_policy/StartStopClientConnectionsTest.py
+++ b/acts_tests/tests/google/fuchsia/wlan_policy/StartStopClientConnectionsTest.py
@@ -17,9 +17,9 @@
 from acts import signals
 from acts.controllers.ap_lib import hostapd_constants
 from acts.controllers.ap_lib import hostapd_security
-from acts.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
-from acts.test_utils.abstract_devices.utils_lib.wlan_policy_utils import setup_policy_tests, restore_state, save_network, start_connections, stop_connections
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_utils import setup_ap
+from acts_contrib.test_utils.abstract_devices.utils_lib.wlan_policy_utils import setup_policy_tests, restore_state, save_network, start_connections, stop_connections
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.utils import rand_ascii_str
 
 DISCONNECTED = "Disconnected"
diff --git a/acts_tests/tests/google/fugu/AndroidFuguRemotePairingTest.py b/acts_tests/tests/google/fugu/AndroidFuguRemotePairingTest.py
index a41f9fc..78b05d4 100644
--- a/acts_tests/tests/google/fugu/AndroidFuguRemotePairingTest.py
+++ b/acts_tests/tests/google/fugu/AndroidFuguRemotePairingTest.py
@@ -19,7 +19,7 @@
 import time
 
 from acts.controllers.relay_lib.relay import SynchronizeRelays
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
 
 class AndroidFuguRemotePairingTest(BluetoothBaseTest):
     def setup_class(self):
diff --git a/acts_tests/tests/google/gnss/AGNSSPerformanceTest.py b/acts_tests/tests/google/gnss/AGNSSPerformanceTest.py
index 57d4c35..803e735 100644
--- a/acts_tests/tests/google/gnss/AGNSSPerformanceTest.py
+++ b/acts_tests/tests/google/gnss/AGNSSPerformanceTest.py
@@ -19,7 +19,7 @@
 from acts import base_test
 from acts import asserts
 from acts.controllers.rohdeschwarz_lib import contest
-from acts.test_utils.tel import tel_test_utils
+from acts_contrib.test_utils.tel import tel_test_utils
 from acts.metrics.loggers import blackbox
 
 import json
diff --git a/acts_tests/tests/google/gnss/FlpTtffTest.py b/acts_tests/tests/google/gnss/FlpTtffTest.py
index 6d73b99..59b19b5 100644
--- a/acts_tests/tests/google/gnss/FlpTtffTest.py
+++ b/acts_tests/tests/google/gnss/FlpTtffTest.py
@@ -20,29 +20,29 @@
 from acts.base_test import BaseTestClass
 from acts.test_decorators import test_tracker_info
 from acts.utils import get_current_epoch_time
-from acts.test_utils.wifi.wifi_test_utils import wifi_toggle_state
-from acts.test_utils.tel.tel_test_utils import start_qxdm_logger
-from acts.test_utils.tel.tel_test_utils import stop_qxdm_logger
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import abort_all_tests
-from acts.test_utils.gnss.gnss_test_utils import get_baseband_and_gms_version
-from acts.test_utils.gnss.gnss_test_utils import _init_device
-from acts.test_utils.gnss.gnss_test_utils import check_location_service
-from acts.test_utils.gnss.gnss_test_utils import clear_logd_gnss_qxdm_log
-from acts.test_utils.gnss.gnss_test_utils import set_mobile_data
-from acts.test_utils.gnss.gnss_test_utils import get_gnss_qxdm_log
-from acts.test_utils.gnss.gnss_test_utils import set_wifi_and_bt_scanning
-from acts.test_utils.gnss.gnss_test_utils import process_gnss_by_gtw_gpstool
-from acts.test_utils.gnss.gnss_test_utils import start_ttff_by_gtw_gpstool
-from acts.test_utils.gnss.gnss_test_utils import process_ttff_by_gtw_gpstool
-from acts.test_utils.gnss.gnss_test_utils import check_ttff_data
-from acts.test_utils.gnss.gnss_test_utils import set_attenuator_gnss_signal
-from acts.test_utils.gnss.gnss_test_utils import connect_to_wifi_network
-from acts.test_utils.gnss.gnss_test_utils import gnss_tracking_via_gtw_gpstool
-from acts.test_utils.gnss.gnss_test_utils import parse_gtw_gpstool_log
-from acts.test_utils.tel.tel_test_utils import start_adb_tcpdump
-from acts.test_utils.tel.tel_test_utils import stop_adb_tcpdump
-from acts.test_utils.tel.tel_test_utils import get_tcpdump_log
+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_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
+from acts_contrib.test_utils.gnss.gnss_test_utils import set_wifi_and_bt_scanning
+from acts_contrib.test_utils.gnss.gnss_test_utils import process_gnss_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import start_ttff_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import process_ttff_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import check_ttff_data
+from acts_contrib.test_utils.gnss.gnss_test_utils import set_attenuator_gnss_signal
+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/GnssFunctionTest.py b/acts_tests/tests/google/gnss/GnssFunctionTest.py
index b1de836..7bc8f44 100644
--- a/acts_tests/tests/google/gnss/GnssFunctionTest.py
+++ b/acts_tests/tests/google/gnss/GnssFunctionTest.py
@@ -24,58 +24,58 @@
 from acts import signals
 from acts.base_test import BaseTestClass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.tel import tel_test_utils as tutils
-from acts.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.gnss import gnss_test_utils as gutils
 from acts.utils import get_current_epoch_time
 from acts.utils import unzip_maintain_permissions
 from acts.utils import force_airplane_mode
-from acts.test_utils.wifi.wifi_test_utils import wifi_toggle_state
-from acts.test_utils.tel.tel_test_utils import flash_radio
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import abort_all_tests
-from acts.test_utils.tel.tel_test_utils import stop_qxdm_logger
-from acts.test_utils.tel.tel_test_utils import check_call_state_connected_by_adb
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import http_file_download_by_sl4a
-from acts.test_utils.tel.tel_test_utils import start_qxdm_logger
-from acts.test_utils.tel.tel_test_utils import trigger_modem_crash
-from acts.test_utils.gnss.gnss_test_utils import get_baseband_and_gms_version
-from acts.test_utils.gnss.gnss_test_utils import set_attenuator_gnss_signal
-from acts.test_utils.gnss.gnss_test_utils import _init_device
-from acts.test_utils.gnss.gnss_test_utils import check_location_service
-from acts.test_utils.gnss.gnss_test_utils import clear_logd_gnss_qxdm_log
-from acts.test_utils.gnss.gnss_test_utils import set_mobile_data
-from acts.test_utils.gnss.gnss_test_utils import set_wifi_and_bt_scanning
-from acts.test_utils.gnss.gnss_test_utils import get_gnss_qxdm_log
-from acts.test_utils.gnss.gnss_test_utils import remount_device
-from acts.test_utils.gnss.gnss_test_utils import reboot
-from acts.test_utils.gnss.gnss_test_utils import check_network_location
-from acts.test_utils.gnss.gnss_test_utils import launch_google_map
-from acts.test_utils.gnss.gnss_test_utils import check_location_api
-from acts.test_utils.gnss.gnss_test_utils import set_battery_saver_mode
-from acts.test_utils.gnss.gnss_test_utils import kill_xtra_daemon
-from acts.test_utils.gnss.gnss_test_utils import start_gnss_by_gtw_gpstool
-from acts.test_utils.gnss.gnss_test_utils import process_gnss_by_gtw_gpstool
-from acts.test_utils.gnss.gnss_test_utils import start_ttff_by_gtw_gpstool
-from acts.test_utils.gnss.gnss_test_utils import process_ttff_by_gtw_gpstool
-from acts.test_utils.gnss.gnss_test_utils import check_ttff_data
-from acts.test_utils.gnss.gnss_test_utils import start_youtube_video
-from acts.test_utils.gnss.gnss_test_utils import fastboot_factory_reset
-from acts.test_utils.gnss.gnss_test_utils import gnss_trigger_modem_ssr_by_adb
-from acts.test_utils.gnss.gnss_test_utils import gnss_trigger_modem_ssr_by_mds
-from acts.test_utils.gnss.gnss_test_utils import disable_supl_mode
-from acts.test_utils.gnss.gnss_test_utils import connect_to_wifi_network
-from acts.test_utils.gnss.gnss_test_utils import check_xtra_download
-from acts.test_utils.gnss.gnss_test_utils import gnss_tracking_via_gtw_gpstool
-from acts.test_utils.gnss.gnss_test_utils import parse_gtw_gpstool_log
-from acts.test_utils.gnss.gnss_test_utils import enable_supl_mode
-from acts.test_utils.gnss.gnss_test_utils import start_toggle_gnss_by_gtw_gpstool
-from acts.test_utils.gnss.gnss_test_utils import grant_location_permission
-from acts.test_utils.tel.tel_test_utils import start_adb_tcpdump
-from acts.test_utils.tel.tel_test_utils import stop_adb_tcpdump
-from acts.test_utils.tel.tel_test_utils import get_tcpdump_log
+from acts_contrib.test_utils.wifi.wifi_test_utils import wifi_toggle_state
+from acts_contrib.test_utils.tel.tel_test_utils import flash_radio
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+from acts_contrib.test_utils.tel.tel_test_utils import abort_all_tests
+from acts_contrib.test_utils.tel.tel_test_utils import stop_qxdm_logger
+from acts_contrib.test_utils.tel.tel_test_utils import check_call_state_connected_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import initiate_call
+from acts_contrib.test_utils.tel.tel_test_utils import hangup_call
+from acts_contrib.test_utils.tel.tel_test_utils import http_file_download_by_sl4a
+from acts_contrib.test_utils.tel.tel_test_utils import start_qxdm_logger
+from acts_contrib.test_utils.tel.tel_test_utils import trigger_modem_crash
+from acts_contrib.test_utils.gnss.gnss_test_utils import get_baseband_and_gms_version
+from acts_contrib.test_utils.gnss.gnss_test_utils import set_attenuator_gnss_signal
+from acts_contrib.test_utils.gnss.gnss_test_utils import _init_device
+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 set_wifi_and_bt_scanning
+from acts_contrib.test_utils.gnss.gnss_test_utils import get_gnss_qxdm_log
+from acts_contrib.test_utils.gnss.gnss_test_utils import remount_device
+from acts_contrib.test_utils.gnss.gnss_test_utils import reboot
+from acts_contrib.test_utils.gnss.gnss_test_utils import check_network_location
+from acts_contrib.test_utils.gnss.gnss_test_utils import launch_google_map
+from acts_contrib.test_utils.gnss.gnss_test_utils import check_location_api
+from acts_contrib.test_utils.gnss.gnss_test_utils import set_battery_saver_mode
+from acts_contrib.test_utils.gnss.gnss_test_utils import kill_xtra_daemon
+from acts_contrib.test_utils.gnss.gnss_test_utils import start_gnss_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import process_gnss_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import start_ttff_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import process_ttff_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import check_ttff_data
+from acts_contrib.test_utils.gnss.gnss_test_utils import start_youtube_video
+from acts_contrib.test_utils.gnss.gnss_test_utils import fastboot_factory_reset
+from acts_contrib.test_utils.gnss.gnss_test_utils import gnss_trigger_modem_ssr_by_adb
+from acts_contrib.test_utils.gnss.gnss_test_utils import gnss_trigger_modem_ssr_by_mds
+from acts_contrib.test_utils.gnss.gnss_test_utils import disable_supl_mode
+from acts_contrib.test_utils.gnss.gnss_test_utils import connect_to_wifi_network
+from acts_contrib.test_utils.gnss.gnss_test_utils import check_xtra_download
+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.gnss.gnss_test_utils import enable_supl_mode
+from acts_contrib.test_utils.gnss.gnss_test_utils import start_toggle_gnss_by_gtw_gpstool
+from acts_contrib.test_utils.gnss.gnss_test_utils import grant_location_permission
+from acts_contrib.test_utils.tel.tel_test_utils import start_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_test_utils import stop_adb_tcpdump
+from acts_contrib.test_utils.tel.tel_test_utils import get_tcpdump_log
 
 
 class GnssFunctionTest(BaseTestClass):
diff --git a/acts_tests/tests/google/gnss/GnssPowerAGPSTest.py b/acts_tests/tests/google/gnss/GnssPowerAGPSTest.py
index f5a3dcc..e0f70a5 100644
--- a/acts_tests/tests/google/gnss/GnssPowerAGPSTest.py
+++ b/acts_tests/tests/google/gnss/GnssPowerAGPSTest.py
@@ -15,9 +15,9 @@
 #   limitations under the License.
 
 from acts import utils
-from acts.test_utils.power.PowerGTWGnssBaseTest import PowerGTWGnssBaseTest
-from acts.test_utils.gnss import gnss_test_utils as gutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.power.PowerGTWGnssBaseTest import PowerGTWGnssBaseTest
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 
 class GnssPowerAGPSTest(PowerGTWGnssBaseTest):
diff --git a/acts_tests/tests/google/gnss/GnssPowerBasicTest.py b/acts_tests/tests/google/gnss/GnssPowerBasicTest.py
index a6c0066..cd60c48 100644
--- a/acts_tests/tests/google/gnss/GnssPowerBasicTest.py
+++ b/acts_tests/tests/google/gnss/GnssPowerBasicTest.py
@@ -15,9 +15,9 @@
 #   limitations under the License.
 
 from acts import utils
-from acts.test_utils.power.PowerGTWGnssBaseTest import PowerGTWGnssBaseTest
-from acts.test_utils.gnss import gnss_test_utils as gutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.power.PowerGTWGnssBaseTest import PowerGTWGnssBaseTest
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 
 class GnssPowerBasicTest(PowerGTWGnssBaseTest):
diff --git a/acts_tests/tests/google/gnss/GnssPowerLongIntervalTest.py b/acts_tests/tests/google/gnss/GnssPowerLongIntervalTest.py
index 100b6bf..ec4733f 100644
--- a/acts_tests/tests/google/gnss/GnssPowerLongIntervalTest.py
+++ b/acts_tests/tests/google/gnss/GnssPowerLongIntervalTest.py
@@ -15,9 +15,9 @@
 #   limitations under the License.
 
 from acts import utils
-from acts.test_utils.power.PowerGTWGnssBaseTest import PowerGTWGnssBaseTest
-from acts.test_utils.gnss import gnss_test_utils as gutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.power.PowerGTWGnssBaseTest import PowerGTWGnssBaseTest
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 
 class GnssPowerLongIntervalTest(PowerGTWGnssBaseTest):
diff --git a/acts_tests/tests/google/gnss/GnssPowerLowPowerTest.py b/acts_tests/tests/google/gnss/GnssPowerLowPowerTest.py
index 4e64be2..8a2b99d 100644
--- a/acts_tests/tests/google/gnss/GnssPowerLowPowerTest.py
+++ b/acts_tests/tests/google/gnss/GnssPowerLowPowerTest.py
@@ -15,9 +15,9 @@
 #   limitations under the License.
 
 from acts import utils
-from acts.test_utils.power.PowerGTWGnssBaseTest import PowerGTWGnssBaseTest
-from acts.test_utils.gnss import gnss_test_utils as gutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.power.PowerGTWGnssBaseTest import PowerGTWGnssBaseTest
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 
 class GnssPowerLowPowerTest(PowerGTWGnssBaseTest):
diff --git a/acts_tests/tests/google/gnss/GnssSimInventoryTest.py b/acts_tests/tests/google/gnss/GnssSimInventoryTest.py
index 8844cbc..cabd82d 100644
--- a/acts_tests/tests/google/gnss/GnssSimInventoryTest.py
+++ b/acts_tests/tests/google/gnss/GnssSimInventoryTest.py
@@ -5,9 +5,9 @@
 from acts import utils
 from acts import signals
 from acts.base_test import BaseTestClass
-from acts.test_utils.tel.tel_defines import EventSmsSentSuccess
-from acts.test_utils.tel.tel_test_utils import get_iccid_by_adb
-from acts.test_utils.tel.tel_test_utils import is_sim_ready_by_adb
+from acts_contrib.test_utils.tel.tel_defines import EventSmsSentSuccess
+from acts_contrib.test_utils.tel.tel_test_utils import get_iccid_by_adb
+from acts_contrib.test_utils.tel.tel_test_utils import is_sim_ready_by_adb
 
 
 class GnssSimInventoryTest(BaseTestClass):
diff --git a/acts_tests/tests/google/gnss/LocationPlatinumTest.py b/acts_tests/tests/google/gnss/LocationPlatinumTest.py
index 73b628f..110748f 100644
--- a/acts_tests/tests/google/gnss/LocationPlatinumTest.py
+++ b/acts_tests/tests/google/gnss/LocationPlatinumTest.py
@@ -20,9 +20,9 @@
 from acts import signals
 from acts import utils
 from acts.base_test import BaseTestClass
-from acts.test_utils.gnss import gnss_test_utils as gutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.tel import tel_test_utils as tutils
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.tel import tel_test_utils as tutils
 
 BACKGROUND_LOCATION_PERMISSION = 'android.permission.ACCESS_BACKGROUND_LOCATION'
 APP_CLEAN_UP_TIME = 60
diff --git a/acts_tests/tests/google/native/NativeTest.py b/acts_tests/tests/google/native/NativeTest.py
index 90ebceb..b62b456 100644
--- a/acts_tests/tests/google/native/NativeTest.py
+++ b/acts_tests/tests/google/native/NativeTest.py
@@ -16,8 +16,8 @@
 
 import time
 from acts.base_test import BaseTestClass
-from acts.test_utils.bt.native_bt_test_utils import setup_native_bluetooth
-from acts.test_utils.bt.bt_test_utils import generate_id_by_size
+from acts_contrib.test_utils.bt.native_bt_test_utils import setup_native_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import generate_id_by_size
 
 class NativeTest(BaseTestClass):
     tests = None
diff --git a/acts_tests/tests/google/native/bt/BtNativeTest.py b/acts_tests/tests/google/native/bt/BtNativeTest.py
index 55674bc..abb8f17 100644
--- a/acts_tests/tests/google/native/bt/BtNativeTest.py
+++ b/acts_tests/tests/google/native/bt/BtNativeTest.py
@@ -1,7 +1,7 @@
 from acts.base_test import BaseTestClass
 from acts.controllers import native_android_device
-from acts.test_utils.bt.native_bt_test_utils import setup_native_bluetooth
-from acts.test_utils.bt.bt_test_utils import generate_id_by_size
+from acts_contrib.test_utils.bt.native_bt_test_utils import setup_native_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import generate_id_by_size
 
 
 class BtNativeTest(BaseTestClass):
diff --git a/acts_tests/tests/google/net/ApfCountersTest.py b/acts_tests/tests/google/net/ApfCountersTest.py
index 4033377..0b4f3dc 100755
--- a/acts_tests/tests/google/net/ApfCountersTest.py
+++ b/acts_tests/tests/google/net/ApfCountersTest.py
@@ -15,17 +15,17 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net.net_test_utils import start_tcpdump
-from acts.test_utils.net.net_test_utils import stop_tcpdump
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.net.net_test_utils import start_tcpdump
+from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 from scapy.all import ICMPv6ND_RA
 from scapy.all import rdpcap
 from scapy.all import Scapy_Exception
 
 import acts.base_test
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 import copy
 import os
diff --git a/acts_tests/tests/google/net/BluetoothTetheringTest.py b/acts_tests/tests/google/net/BluetoothTetheringTest.py
index df7bad5..e4d3c67 100644
--- a/acts_tests/tests/google/net/BluetoothTetheringTest.py
+++ b/acts_tests/tests/google/net/BluetoothTetheringTest.py
@@ -18,12 +18,12 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
-from acts.test_utils.bt.bt_test_utils import orchestrate_and_verify_pan_connection
-from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+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.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 7df4372..5b03ea2 100644
--- a/acts_tests/tests/google/net/CaptivePortalTest.py
+++ b/acts_tests/tests/google/net/CaptivePortalTest.py
@@ -18,11 +18,11 @@
 from acts import asserts
 from acts import base_test
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const as cconst
-from acts.test_utils.net import connectivity_test_utils as cutils
-from acts.test_utils.net import ui_utils as uutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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 ui_utils as uutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 IFACE = "InterfaceName"
diff --git a/acts_tests/tests/google/net/CoreNetworkingOTATest.py b/acts_tests/tests/google/net/CoreNetworkingOTATest.py
index 5b350f8..019409a 100755
--- a/acts_tests/tests/google/net/CoreNetworkingOTATest.py
+++ b/acts_tests/tests/google/net/CoreNetworkingOTATest.py
@@ -26,10 +26,10 @@
 from acts.base_test import BaseTestClass
 from acts.libs.ota import ota_updater
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const as cconst
+from acts_contrib.test_utils.net import connectivity_const as cconst
 
-import acts.test_utils.net.net_test_utils as nutils
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.net.net_test_utils as nutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 VPN_CONST = cconst.VpnProfile
 VPN_TYPE = cconst.VpnProfileType
diff --git a/acts_tests/tests/google/net/CoreNetworkingTest.py b/acts_tests/tests/google/net/CoreNetworkingTest.py
index 308beda..fc32f92 100644
--- a/acts_tests/tests/google/net/CoreNetworkingTest.py
+++ b/acts_tests/tests/google/net/CoreNetworkingTest.py
@@ -17,8 +17,8 @@
 from acts import base_test
 from acts.controllers import adb
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.net import net_test_utils as nutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 dum_class = "com.android.tests.connectivity.uid.ConnectivityTestActivity"
 
diff --git a/acts_tests/tests/google/net/DataCostTest.py b/acts_tests/tests/google/net/DataCostTest.py
index a07fdcd..6174009 100644
--- a/acts_tests/tests/google/net/DataCostTest.py
+++ b/acts_tests/tests/google/net/DataCostTest.py
@@ -27,14 +27,14 @@
 from acts import test_runner
 from acts.controllers import adb
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.tel.tel_test_utils import _check_file_existance
-from acts.test_utils.tel.tel_test_utils import _generate_file_directory_and_file_name
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_NONE as NONE
-from acts.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_HANDOVER as HANDOVER
-from acts.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_RELIABILITY as RELIABILITY
-from acts.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_PERFORMANCE as PERFORMANCE
+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 _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
+from acts_contrib.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_HANDOVER as HANDOVER
+from acts_contrib.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_RELIABILITY as RELIABILITY
+from acts_contrib.test_utils.net.connectivity_const import MULTIPATH_PREFERENCE_PERFORMANCE as PERFORMANCE
 
 DOWNLOAD_PATH = "/sdcard/Download/"
 RELIABLE = RELIABILITY | HANDOVER
diff --git a/acts_tests/tests/google/net/DataUsageTest.py b/acts_tests/tests/google/net/DataUsageTest.py
index 12a21c6..1582886 100644
--- a/acts_tests/tests/google/net/DataUsageTest.py
+++ b/acts_tests/tests/google/net/DataUsageTest.py
@@ -23,15 +23,15 @@
 from acts.controllers.adb_lib.error import AdbError
 from acts.controllers.ap_lib import hostapd_constants
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const as cconst
-from acts.test_utils.net import connectivity_test_utils as cutils
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.net.net_test_utils import start_tcpdump
-from acts.test_utils.net.net_test_utils import stop_tcpdump
-from acts.test_utils.tel import tel_test_utils as ttutils
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_test_utils import http_file_download_by_chrome
-from acts.test_utils.wifi import wifi_test_utils as wutils
+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.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 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.wifi import wifi_test_utils as wutils
 import queue
 from queue import Empty
 
diff --git a/acts_tests/tests/google/net/DnsOverTlsTest.py b/acts_tests/tests/google/net/DnsOverTlsTest.py
index 5ab964b..13d8fe1 100644
--- a/acts_tests/tests/google/net/DnsOverTlsTest.py
+++ b/acts_tests/tests/google/net/DnsOverTlsTest.py
@@ -18,16 +18,16 @@
 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.test_utils.net import connectivity_const as cconst
-from acts.test_utils.net import connectivity_test_utils as cutils
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.net.net_test_utils import start_tcpdump
-from acts.test_utils.net.net_test_utils import stop_tcpdump
-from acts.test_utils.tel.tel_defines import WFC_MODE_DISABLED
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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.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
 from scapy.all import Scapy_Exception
 from scapy.all import TCP
diff --git a/acts_tests/tests/google/net/IKEv2VpnOverLTETest.py b/acts_tests/tests/google/net/IKEv2VpnOverLTETest.py
index cd9c41a..e0fb160 100644
--- a/acts_tests/tests/google/net/IKEv2VpnOverLTETest.py
+++ b/acts_tests/tests/google/net/IKEv2VpnOverLTETest.py
@@ -16,9 +16,9 @@
 
 from acts import base_test
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
+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
 
 VPN_CONST = connectivity_const.VpnProfile
 VPN_TYPE = connectivity_const.VpnProfileType
diff --git a/acts_tests/tests/google/net/IKEv2VpnOverWifiTest.py b/acts_tests/tests/google/net/IKEv2VpnOverWifiTest.py
index 1f36e9a..6196f61 100644
--- a/acts_tests/tests/google/net/IKEv2VpnOverWifiTest.py
+++ b/acts_tests/tests/google/net/IKEv2VpnOverWifiTest.py
@@ -16,9 +16,9 @@
 
 from acts import base_test
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
+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
 
 VPN_CONST = connectivity_const.VpnProfile
 VPN_TYPE = connectivity_const.VpnProfileType
diff --git a/acts_tests/tests/google/net/IpSecTest.py b/acts_tests/tests/google/net/IpSecTest.py
index 1b4a144..4c61b3e 100644
--- a/acts_tests/tests/google/net/IpSecTest.py
+++ b/acts_tests/tests/google/net/IpSecTest.py
@@ -17,12 +17,12 @@
 from acts import base_test
 from acts.controllers import adb
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const as cconst
-from acts.test_utils.net import ipsec_test_utils as iutils
-from acts.test_utils.net import socket_test_utils as sutils
-from acts.test_utils.net.net_test_utils import start_tcpdump
-from acts.test_utils.net.net_test_utils import stop_tcpdump
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.net import connectivity_const as cconst
+from acts_contrib.test_utils.net import ipsec_test_utils as iutils
+from acts_contrib.test_utils.net import socket_test_utils as sutils
+from acts_contrib.test_utils.net.net_test_utils import start_tcpdump
+from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 import random
 import time
diff --git a/acts_tests/tests/google/net/LegacyVpnTest.py b/acts_tests/tests/google/net/LegacyVpnTest.py
index a4bfc52..9a8465a 100644
--- a/acts_tests/tests/google/net/LegacyVpnTest.py
+++ b/acts_tests/tests/google/net/LegacyVpnTest.py
@@ -25,10 +25,10 @@
 from acts import test_runner
 from acts.controllers import adb
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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
diff --git a/acts_tests/tests/google/net/ProxyTest.py b/acts_tests/tests/google/net/ProxyTest.py
index 4162e82..0511632 100644
--- a/acts_tests/tests/google/net/ProxyTest.py
+++ b/acts_tests/tests/google/net/ProxyTest.py
@@ -16,9 +16,9 @@
 from acts import asserts
 from acts import base_test
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net.net_test_utils import start_tcpdump
-from acts.test_utils.net.net_test_utils import stop_tcpdump
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.net.net_test_utils import start_tcpdump
+from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 
 from scapy.all import IP
diff --git a/acts_tests/tests/google/net/SocketKeepaliveTest.py b/acts_tests/tests/google/net/SocketKeepaliveTest.py
index 5c63497..d348b72 100644
--- a/acts_tests/tests/google/net/SocketKeepaliveTest.py
+++ b/acts_tests/tests/google/net/SocketKeepaliveTest.py
@@ -19,12 +19,12 @@
 from acts import asserts
 from acts import base_test
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_test_utils as cutils
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.net import socket_test_utils as sutils
-from acts.test_utils.net.net_test_utils import start_tcpdump
-from acts.test_utils.net.net_test_utils import stop_tcpdump
-from acts.test_utils.wifi import wifi_test_utils as wutils
+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.net import socket_test_utils as sutils
+from acts_contrib.test_utils.net.net_test_utils import start_tcpdump
+from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from scapy.all import rdpcap
 from scapy.all import Scapy_Exception
 from scapy.all import TCP
diff --git a/acts_tests/tests/google/net/UsbTetheringTest.py b/acts_tests/tests/google/net/UsbTetheringTest.py
index 8df51f5..e02611e 100644
--- a/acts_tests/tests/google/net/UsbTetheringTest.py
+++ b/acts_tests/tests/google/net/UsbTetheringTest.py
@@ -1,8 +1,8 @@
 from acts import asserts
 from acts import base_test
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
+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
 
diff --git a/acts_tests/tests/google/net/VpnOverLTETest.py b/acts_tests/tests/google/net/VpnOverLTETest.py
index a7e4172..fdb7e26 100644
--- a/acts_tests/tests/google/net/VpnOverLTETest.py
+++ b/acts_tests/tests/google/net/VpnOverLTETest.py
@@ -15,10 +15,10 @@
 
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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
diff --git a/acts_tests/tests/google/power/PowerBaselineTest.py b/acts_tests/tests/google/power/PowerBaselineTest.py
index f1904e3..e056371 100644
--- a/acts_tests/tests/google/power/PowerBaselineTest.py
+++ b/acts_tests/tests/google/power/PowerBaselineTest.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.power.PowerBaseTest import PowerBaseTest
+from acts_contrib.test_utils.power.PowerBaseTest import PowerBaseTest
 
 
 class PowerBaselineTest(PowerBaseTest):
diff --git a/acts_tests/tests/google/power/bt/PowerBLEadvertiseTest.py b/acts_tests/tests/google/power/bt/PowerBLEadvertiseTest.py
index 1933699..4f42ae0 100644
--- a/acts_tests/tests/google/power/bt/PowerBLEadvertiseTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBLEadvertiseTest.py
@@ -15,9 +15,9 @@
 #   limitations under the License.
 
 import time
-import acts.test_utils.bt.BleEnum as bleenum
-import acts.test_utils.bt.bt_power_test_utils as btputils
-import acts.test_utils.power.PowerBTBaseTest as PBtBT
+import acts_contrib.test_utils.bt.BleEnum as bleenum
+import acts_contrib.test_utils.bt.bt_power_test_utils as btputils
+import acts_contrib.test_utils.power.PowerBTBaseTest as PBtBT
 
 BLE_LOCATION_SCAN_ENABLE = 'settings put secure location_mode 3'
 EXTRA_ADV_TIME = 3
diff --git a/acts_tests/tests/google/power/bt/PowerBLEscanTest.py b/acts_tests/tests/google/power/bt/PowerBLEscanTest.py
index f859f0c..0aa5ad9 100644
--- a/acts_tests/tests/google/power/bt/PowerBLEscanTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBLEscanTest.py
@@ -15,9 +15,9 @@
 #   limitations under the License.
 
 import time
-import acts.test_utils.bt.BleEnum as bleenum
-import acts.test_utils.bt.bt_power_test_utils as btputils
-import acts.test_utils.power.PowerBTBaseTest as PBtBT
+import acts_contrib.test_utils.bt.BleEnum as bleenum
+import acts_contrib.test_utils.bt.bt_power_test_utils as btputils
+import acts_contrib.test_utils.power.PowerBTBaseTest as PBtBT
 
 BLE_LOCATION_SCAN_ENABLE = 'settings put secure location_mode 3'
 EXTRA_SCAN_TIME = 3
diff --git a/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py b/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py
index b6d60f1..2d0bc9d 100644
--- a/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBTa2dpTest.py
@@ -15,10 +15,10 @@
 #   limitations under the License.
 
 import time
-import acts.test_utils.bt.bt_test_utils as btutils
-import acts.test_utils.power.PowerBTBaseTest as PBtBT
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
+import acts_contrib.test_utils.power.PowerBTBaseTest as PBtBT
 from acts import asserts
-from acts.test_utils.bt import BtEnum
+from acts_contrib.test_utils.bt import BtEnum
 
 EXTRA_PLAY_TIME = 10
 
diff --git a/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py b/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py
index 4b14f17..13b3f39 100644
--- a/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBTcalibrationTest.py
@@ -17,8 +17,8 @@
 import csv
 import os
 import time
-import acts.test_utils.bt.bt_test_utils as btutils
-import acts.test_utils.power.PowerBTBaseTest as PBtBT
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
+import acts_contrib.test_utils.power.PowerBTBaseTest as PBtBT
 
 EXTRA_PLAY_TIME = 30
 
diff --git a/acts_tests/tests/google/power/bt/PowerBTidleTest.py b/acts_tests/tests/google/power/bt/PowerBTidleTest.py
index bab79d0..79831e7 100644
--- a/acts_tests/tests/google/power/bt/PowerBTidleTest.py
+++ b/acts_tests/tests/google/power/bt/PowerBTidleTest.py
@@ -15,8 +15,8 @@
 #   limitations under the License.
 
 import time
-import acts.test_utils.power.PowerBTBaseTest as PBtBT
-import acts.test_utils.bt.bt_test_utils as btutils
+import acts_contrib.test_utils.power.PowerBTBaseTest as PBtBT
+import acts_contrib.test_utils.bt.bt_test_utils as btutils
 
 SCREEN_OFF_WAIT_TIME = 2
 
diff --git a/acts_tests/tests/google/power/coex/PowerCoexbaselineTest.py b/acts_tests/tests/google/power/coex/PowerCoexbaselineTest.py
index 19d7763..5b6c8c1 100644
--- a/acts_tests/tests/google/power/coex/PowerCoexbaselineTest.py
+++ b/acts_tests/tests/google/power/coex/PowerCoexbaselineTest.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.PowerCoexBaseTest as PCoBT
+import acts_contrib.test_utils.power.PowerCoexBaseTest as PCoBT
 from acts.test_decorators import test_tracker_info
 
 
diff --git a/acts_tests/tests/google/power/coex/PowerCoexscanTest.py b/acts_tests/tests/google/power/coex/PowerCoexscanTest.py
index 3d1f1e3..f15f76b 100644
--- a/acts_tests/tests/google/power/coex/PowerCoexscanTest.py
+++ b/acts_tests/tests/google/power/coex/PowerCoexscanTest.py
@@ -15,7 +15,7 @@
 #   limitations under the License.
 
 import math
-import acts.test_utils.power.PowerCoexBaseTest as PCoBT
+import acts_contrib.test_utils.power.PowerCoexBaseTest as PCoBT
 from acts.test_decorators import test_tracker_info
 
 
diff --git a/acts_tests/tests/google/power/gnss/PowerGnssDpoSimTest.py b/acts_tests/tests/google/power/gnss/PowerGnssDpoSimTest.py
index 9c2ce9a..14b36e3 100644
--- a/acts_tests/tests/google/power/gnss/PowerGnssDpoSimTest.py
+++ b/acts_tests/tests/google/power/gnss/PowerGnssDpoSimTest.py
@@ -14,9 +14,9 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.PowerGnssBaseTest as GBT
-from acts.test_utils.gnss import dut_log_test_utils as diaglog
-from acts.test_utils.gnss import gnss_test_utils as gutil
+import acts_contrib.test_utils.power.PowerGnssBaseTest as GBT
+from acts_contrib.test_utils.gnss import dut_log_test_utils as diaglog
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutil
 import time
 import os
 from acts import utils
diff --git a/acts_tests/tests/google/power/tel/PowerTelHotspot_LTE_Test.py b/acts_tests/tests/google/power/tel/PowerTelHotspot_LTE_Test.py
index dd9502e..ad98e85 100644
--- a/acts_tests/tests/google/power/tel/PowerTelHotspot_LTE_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelHotspot_LTE_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_hotspot_traffic_power_test as chtpw
+import acts_contrib.test_utils.power.cellular.cellular_hotspot_traffic_power_test as chtpw
 
 
 class PowerTelHotspot_LTE_Test(chtpw.PowerTelHotspotTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelIdle_LTE_Test.py b/acts_tests/tests/google/power/tel/PowerTelIdle_LTE_Test.py
index 47151aa..12c9973 100644
--- a/acts_tests/tests/google/power/tel/PowerTelIdle_LTE_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelIdle_LTE_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_idle_power_test as cipt
+import acts_contrib.test_utils.power.cellular.cellular_idle_power_test as cipt
 
 
 class PowerTelIdle_LTE_Test(cipt.PowerTelIdleTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelIdle_Modem_Test.py b/acts_tests/tests/google/power/tel/PowerTelIdle_Modem_Test.py
index 40095fb..5ba1674 100644
--- a/acts_tests/tests/google/power/tel/PowerTelIdle_Modem_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelIdle_Modem_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_idle_power_test as cipt
+import acts_contrib.test_utils.power.cellular.cellular_idle_power_test as cipt
 
 
 class PowerTelIdle_Modem_Test(cipt.PowerTelIdleTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelIdle_UMTS_Test.py b/acts_tests/tests/google/power/tel/PowerTelIdle_UMTS_Test.py
index 07617be..7af7fea 100644
--- a/acts_tests/tests/google/power/tel/PowerTelIdle_UMTS_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelIdle_UMTS_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_idle_power_test as cipt
+import acts_contrib.test_utils.power.cellular.cellular_idle_power_test as cipt
 
 
 class PowerTelIdle_UMTS_Test(cipt.PowerTelIdleTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelMac_Modem_Test.py b/acts_tests/tests/google/power/tel/PowerTelMac_Modem_Test.py
index 0ceade6..2ee5c5b 100644
--- a/acts_tests/tests/google/power/tel/PowerTelMac_Modem_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelMac_Modem_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_pdcch_power_test as cppt
+import acts_contrib.test_utils.power.cellular.cellular_pdcch_power_test as cppt
 
 
 class PowerTelMac_Modem_Test(cppt.PowerTelPDCCHTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelPdcch_Modem_Test.py b/acts_tests/tests/google/power/tel/PowerTelPdcch_Modem_Test.py
index 47b987a..18dcf0e 100644
--- a/acts_tests/tests/google/power/tel/PowerTelPdcch_Modem_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelPdcch_Modem_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_pdcch_power_test as cppt
+import acts_contrib.test_utils.power.cellular.cellular_pdcch_power_test as cppt
 
 
 class PowerTelPdcch_Modem_Test(cppt.PowerTelPDCCHTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelStandby_LTE_Test.py b/acts_tests/tests/google/power/tel/PowerTelStandby_LTE_Test.py
index 36e2021..7836f6b 100644
--- a/acts_tests/tests/google/power/tel/PowerTelStandby_LTE_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelStandby_LTE_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_idle_power_test as cipt
+import acts_contrib.test_utils.power.cellular.cellular_idle_power_test as cipt
 
 
 class PowerTelStandby_LTE_Test(cipt.PowerTelIdleTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelTraffic_GSM_Test.py b/acts_tests/tests/google/power/tel/PowerTelTraffic_GSM_Test.py
index b99aba0..934d827 100644
--- a/acts_tests/tests/google/power/tel/PowerTelTraffic_GSM_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelTraffic_GSM_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_traffic_power_test as ctpt
+import acts_contrib.test_utils.power.cellular.cellular_traffic_power_test as ctpt
 
 
 class PowerTelTraffic_GSM_Test(ctpt.PowerTelTrafficTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelTraffic_LTECA_Test.py b/acts_tests/tests/google/power/tel/PowerTelTraffic_LTECA_Test.py
index 1e94c3b..71d5840 100644
--- a/acts_tests/tests/google/power/tel/PowerTelTraffic_LTECA_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelTraffic_LTECA_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_traffic_power_test as ctpt
+import acts_contrib.test_utils.power.cellular.cellular_traffic_power_test as ctpt
 
 
 class PowerTelTraffic_LTECA_Test(ctpt.PowerTelTrafficTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelTraffic_LTE_Test.py b/acts_tests/tests/google/power/tel/PowerTelTraffic_LTE_Test.py
index 7facc34..3fb2fa7 100644
--- a/acts_tests/tests/google/power/tel/PowerTelTraffic_LTE_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelTraffic_LTE_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_traffic_power_test as ctpt
+import acts_contrib.test_utils.power.cellular.cellular_traffic_power_test as ctpt
 
 
 class PowerTelTraffic_LTE_Test(ctpt.PowerTelTrafficTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelTraffic_Modem_Test.py b/acts_tests/tests/google/power/tel/PowerTelTraffic_Modem_Test.py
index 1206639..61897ee 100644
--- a/acts_tests/tests/google/power/tel/PowerTelTraffic_Modem_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelTraffic_Modem_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_traffic_power_test as ctpt
+import acts_contrib.test_utils.power.cellular.cellular_traffic_power_test as ctpt
 
 
 class PowerTelTraffic_Modem_Test(ctpt.PowerTelTrafficTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelTraffic_TxSweep_Test.py b/acts_tests/tests/google/power/tel/PowerTelTraffic_TxSweep_Test.py
index ee4740c..09af00c 100644
--- a/acts_tests/tests/google/power/tel/PowerTelTraffic_TxSweep_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelTraffic_TxSweep_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_traffic_power_test as ctpt
+import acts_contrib.test_utils.power.cellular.cellular_traffic_power_test as ctpt
 
 
 class PowerTelTraffic_TxSweep_Test(ctpt.PowerTelTxPowerSweepTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelTraffic_UMTS_Test.py b/acts_tests/tests/google/power/tel/PowerTelTraffic_UMTS_Test.py
index acc2048..406b643 100644
--- a/acts_tests/tests/google/power/tel/PowerTelTraffic_UMTS_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelTraffic_UMTS_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_traffic_power_test as ctpt
+import acts_contrib.test_utils.power.cellular.cellular_traffic_power_test as ctpt
 
 
 class PowerTelTraffic_UMTS_Test(ctpt.PowerTelTrafficTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelVoiceCall_LTE_Test.py b/acts_tests/tests/google/power/tel/PowerTelVoiceCall_LTE_Test.py
index f352418..206004f 100644
--- a/acts_tests/tests/google/power/tel/PowerTelVoiceCall_LTE_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelVoiceCall_LTE_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_volte_power_test as cvltept
+import acts_contrib.test_utils.power.cellular.cellular_volte_power_test as cvltept
 
 
 class PowerTelVoiceCall_LTE_Test(cvltept.PowerTelVoLTECallTest):
diff --git a/acts_tests/tests/google/power/tel/PowerTelVoiceCall_UMTS_Test.py b/acts_tests/tests/google/power/tel/PowerTelVoiceCall_UMTS_Test.py
index 3201cb9..6a92da9 100644
--- a/acts_tests/tests/google/power/tel/PowerTelVoiceCall_UMTS_Test.py
+++ b/acts_tests/tests/google/power/tel/PowerTelVoiceCall_UMTS_Test.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.power.cellular.cellular_voice_call_power_test as cvcpt
+import acts_contrib.test_utils.power.cellular.cellular_voice_call_power_test as cvcpt
 
 
 class PowerTelVoiceCall_UMTS_Test(cvcpt.PowerTelVoiceCallTest):
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py b/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py
index 78388e6..74106c9 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFiHotspotTest.py
@@ -16,12 +16,12 @@
 
 import time
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.power import PowerWiFiBaseTest as PWBT
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi import wifi_power_test_utils as wputils
-from acts.test_utils.power.IperfHelper import IperfHelper
+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.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
 
 
 class PowerWiFiHotspotTest(PWBT.PowerWiFiBaseTest):
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFibaselineTest.py b/acts_tests/tests/google/power/wifi/PowerWiFibaselineTest.py
index 213c81a..8e442c4 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFibaselineTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFibaselineTest.py
@@ -14,8 +14,8 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.power import PowerWiFiBaseTest as PWBT
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.power import PowerWiFiBaseTest as PWBT
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from acts.test_decorators import test_tracker_info
 
 
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFidtimTest.py b/acts_tests/tests/google/power/wifi/PowerWiFidtimTest.py
index 013931d..2d6eec1 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFidtimTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFidtimTest.py
@@ -16,8 +16,8 @@
 
 import time
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.power import PowerWiFiBaseTest as PWBT
-from acts.test_utils.wifi import wifi_power_test_utils as wputils
+from acts_contrib.test_utils.power import PowerWiFiBaseTest as PWBT
+from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils
 
 
 class PowerWiFidtimTest(PWBT.PowerWiFiBaseTest):
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFimulticastTest.py b/acts_tests/tests/google/power/wifi/PowerWiFimulticastTest.py
index 8832469..8b7e063 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFimulticastTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFimulticastTest.py
@@ -16,8 +16,8 @@
 
 import time
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.power import PowerWiFiBaseTest as PWBT
-from acts.test_utils.wifi import wifi_power_test_utils as wputils
+from acts_contrib.test_utils.power import PowerWiFiBaseTest as PWBT
+from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils
 from acts.controllers import packet_sender as pkt_utils
 
 RA_SHORT_LIFETIME = 3
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFiroamingTest.py b/acts_tests/tests/google/power/wifi/PowerWiFiroamingTest.py
index 2a56a84..b366584 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFiroamingTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFiroamingTest.py
@@ -19,10 +19,10 @@
 from acts import utils
 from acts.controllers.ap_lib import hostapd_constants as hc
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.power import PowerWiFiBaseTest as PWBT
-from acts.test_utils.wifi import wifi_constants as wc
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.power import plot_utils
+from acts_contrib.test_utils.power import PowerWiFiBaseTest as PWBT
+from acts_contrib.test_utils.wifi import wifi_constants as wc
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.power import plot_utils
 
 PHONE_BATTERY_VOLTAGE = 4.2
 
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFiscanTest.py b/acts_tests/tests/google/power/wifi/PowerWiFiscanTest.py
index e1153c1..72f733f 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFiscanTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFiscanTest.py
@@ -16,8 +16,8 @@
 
 import time
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.power import PowerWiFiBaseTest as PWBT
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.power import PowerWiFiBaseTest as PWBT
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 UNLOCK_SCREEN = 'input keyevent 82'
 LOCATION_ON = 'settings put secure location_mode 3'
diff --git a/acts_tests/tests/google/power/wifi/PowerWiFitrafficTest.py b/acts_tests/tests/google/power/wifi/PowerWiFitrafficTest.py
index c6b1a3e..7ba9c35 100644
--- a/acts_tests/tests/google/power/wifi/PowerWiFitrafficTest.py
+++ b/acts_tests/tests/google/power/wifi/PowerWiFitrafficTest.py
@@ -16,8 +16,8 @@
 
 import time
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.power import PowerWiFiBaseTest as PWBT
-from acts.test_utils.wifi import wifi_power_test_utils as wputils
+from acts_contrib.test_utils.power import PowerWiFiBaseTest as PWBT
+from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils
 
 TEMP_FILE = '/sdcard/Download/tmp.log'
 
diff --git a/acts_tests/tests/google/tel/etc/manage_sim.py b/acts_tests/tests/google/tel/etc/manage_sim.py
index 1d51cb0..91a3887 100755
--- a/acts_tests/tests/google/tel/etc/manage_sim.py
+++ b/acts_tests/tests/google/tel/etc/manage_sim.py
@@ -23,9 +23,9 @@
 import argparse
 import json
 import acts.controllers.android_device as android_device
-import acts.test_utils.tel.tel_defines as tel_defines
-import acts.test_utils.tel.tel_lookup_tables as tel_lookup_tables
-import acts.test_utils.tel.tel_test_utils as tel_test_utils
+import acts_contrib.test_utils.tel.tel_defines as tel_defines
+import acts_contrib.test_utils.tel.tel_lookup_tables as tel_lookup_tables
+import acts_contrib.test_utils.tel.tel_test_utils as tel_test_utils
 
 
 def get_active_sim_list(verbose_warnings=False):
diff --git a/acts_tests/tests/google/tel/lab/TelLabCmasTest.py b/acts_tests/tests/google/tel/lab/TelLabCmasTest.py
index 906c0a9..4292c87 100644
--- a/acts_tests/tests/google/tel/lab/TelLabCmasTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabCmasTest.py
@@ -22,46 +22,46 @@
 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.test_utils.tel.anritsu_utils import CMAS_C2K_CATEGORY_AMBER
-from acts.test_utils.tel.anritsu_utils import CMAS_C2K_CATEGORY_EXTREME
-from acts.test_utils.tel.anritsu_utils import CMAS_C2K_CATEGORY_PRESIDENTIAL
-from acts.test_utils.tel.anritsu_utils import CMAS_C2K_CERTIANTY_LIKELY
-from acts.test_utils.tel.anritsu_utils import CMAS_C2K_RESPONSETYPE_EVACUATE
-from acts.test_utils.tel.anritsu_utils import CMAS_C2K_RESPONSETYPE_MONITOR
-from acts.test_utils.tel.anritsu_utils import CMAS_C2K_RESPONSETYPE_SHELTER
-from acts.test_utils.tel.anritsu_utils import CMAS_C2K_SEVERITY_EXTREME
-from acts.test_utils.tel.anritsu_utils import CMAS_C2K_URGENCY_IMMEDIATE
-from acts.test_utils.tel.anritsu_utils import CMAS_C2K_CERTIANTY_OBSERVED
-from acts.test_utils.tel.anritsu_utils import CMAS_MESSAGE_CHILD_ABDUCTION_EMERGENCY
-from acts.test_utils.tel.anritsu_utils import CMAS_MESSAGE_EXTREME_IMMEDIATE_LIKELY
-from acts.test_utils.tel.anritsu_utils import CMAS_MESSAGE_PRESIDENTIAL_ALERT
-from acts.test_utils.tel.anritsu_utils import cb_serial_number
-from acts.test_utils.tel.anritsu_utils import cmas_receive_verify_message_cdma1x
-from acts.test_utils.tel.anritsu_utils import cmas_receive_verify_message_lte_wcdma
-from acts.test_utils.tel.anritsu_utils import set_system_model_1x
-from acts.test_utils.tel.anritsu_utils import set_system_model_1x_evdo
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte
-from acts.test_utils.tel.anritsu_utils import set_system_model_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_wcdma
-from acts.test_utils.tel.anritsu_utils import set_usim_parameters
-from acts.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_1XRTT
-from acts.test_utils.tel.tel_defines import RAT_LTE
-from acts.test_utils.tel.tel_defines import RAT_GSM
-from acts.test_utils.tel.tel_defines import RAT_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_UMTS
-from acts.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_C2K_CATEGORY_AMBER
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_C2K_CATEGORY_EXTREME
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_C2K_CATEGORY_PRESIDENTIAL
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_C2K_CERTIANTY_LIKELY
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_C2K_RESPONSETYPE_EVACUATE
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_C2K_RESPONSETYPE_MONITOR
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_C2K_RESPONSETYPE_SHELTER
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_C2K_SEVERITY_EXTREME
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_C2K_URGENCY_IMMEDIATE
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_C2K_CERTIANTY_OBSERVED
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_MESSAGE_CHILD_ABDUCTION_EMERGENCY
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_MESSAGE_EXTREME_IMMEDIATE_LIKELY
+from acts_contrib.test_utils.tel.anritsu_utils import CMAS_MESSAGE_PRESIDENTIAL_ALERT
+from acts_contrib.test_utils.tel.anritsu_utils import cb_serial_number
+from acts_contrib.test_utils.tel.anritsu_utils import cmas_receive_verify_message_cdma1x
+from acts_contrib.test_utils.tel.anritsu_utils import cmas_receive_verify_message_lte_wcdma
+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_lte
+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_wcdma
+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 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 RAT_1XRTT
+from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
+from acts_contrib.test_utils.tel.tel_defines import RAT_GSM
+from acts_contrib.test_utils.tel.tel_defines import RAT_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
+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_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
 
 WAIT_TIME_BETWEEN_REG_AND_MSG = 15  # default 15 sec
diff --git a/acts_tests/tests/google/tel/lab/TelLabDataRoamingTest.py b/acts_tests/tests/google/tel/lab/TelLabDataRoamingTest.py
index 8f09fea..af4ca6c 100644
--- a/acts_tests/tests/google/tel/lab/TelLabDataRoamingTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabDataRoamingTest.py
@@ -23,18 +23,18 @@
 from acts.controllers.anritsu_lib.md8475a import MD8475A
 from acts.controllers.anritsu_lib.md8475a import BtsServiceState
 from acts.controllers.anritsu_lib.md8475a import BtsPacketRate
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_wcdma
-from acts.test_utils.tel.anritsu_utils import set_usim_parameters
-from acts.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import toggle_cell_data_roaming
-from acts.test_utils.tel.tel_test_utils import set_preferred_apn_by_adb
-from acts.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.anritsu_utils import set_system_model_lte_wcdma
+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 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_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 c48446c..9a1355d 100644
--- a/acts_tests/tests/google/tel/lab/TelLabDataTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabDataTest.py
@@ -27,54 +27,54 @@
 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.test_utils.tel.anritsu_utils import cb_serial_number
-from acts.test_utils.tel.anritsu_utils import set_system_model_1x
-from acts.test_utils.tel.anritsu_utils import set_system_model_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_wcdma
-from acts.test_utils.tel.anritsu_utils import set_system_model_wcdma
-from acts.test_utils.tel.anritsu_utils import sms_mo_send
-from acts.test_utils.tel.anritsu_utils import sms_mt_receive_verify
-from acts.test_utils.tel.anritsu_utils import set_usim_parameters
-from acts.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO
-from acts.test_utils.tel.tel_defines import RAT_1XRTT
-from acts.test_utils.tel.tel_defines import RAT_GSM
-from acts.test_utils.tel.tel_defines import RAT_LTE
-from acts.test_utils.tel.tel_defines import RAT_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import POWER_LEVEL_OUT_OF_SERVICE
-from acts.test_utils.tel.tel_defines import POWER_LEVEL_FULL_SERVICE
-from acts.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts.test_utils.tel.tel_test_utils import get_host_ip_address
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import iperf_test_by_adb
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import check_data_stall_detection
-from acts.test_utils.tel.tel_test_utils import check_network_validation_fail
-from acts.test_utils.tel.tel_test_utils import check_data_stall_recovery
-from acts.test_utils.tel.tel_test_utils import get_device_epoch_time
-from acts.test_utils.tel.tel_test_utils import break_internet_except_sl4a_port
-from acts.test_utils.tel.tel_test_utils import resume_internet_with_sl4a_port
-from acts.test_utils.tel.tel_test_utils import \
+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
+from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
+from acts_contrib.test_utils.tel.tel_defines import RAT_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
+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_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.test_utils.tel.tel_test_utils import \
+from acts_contrib.test_utils.tel.tel_test_utils import \
     test_data_browsing_failure_using_sl4a
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+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
diff --git a/acts_tests/tests/google/tel/lab/TelLabEmergencyCallTest.py b/acts_tests/tests/google/tel/lab/TelLabEmergencyCallTest.py
index 4ea6e7f..d83faa3 100644
--- a/acts_tests/tests/google/tel/lab/TelLabEmergencyCallTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabEmergencyCallTest.py
@@ -23,47 +23,47 @@
 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.test_utils.tel.anritsu_utils import WAIT_TIME_ANRITSU_REG_AND_CALL
-from acts.test_utils.tel.anritsu_utils import call_mo_setup_teardown
-from acts.test_utils.tel.anritsu_utils import ims_call_cs_teardown
-from acts.test_utils.tel.anritsu_utils import call_mt_setup_teardown
-from acts.test_utils.tel.anritsu_utils import set_system_model_1x
-from acts.test_utils.tel.anritsu_utils import set_system_model_1x_evdo
-from acts.test_utils.tel.anritsu_utils import set_system_model_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_1x
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_wcdma
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_wcdma
-from acts.test_utils.tel.anritsu_utils import set_usim_parameters
-from acts.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts.test_utils.tel.tel_defines import CALL_TEARDOWN_PHONE
-from acts.test_utils.tel.tel_defines import DEFAULT_EMERGENCY_CALL_NUMBER
-from acts.test_utils.tel.tel_defines import EMERGENCY_CALL_NUMBERS
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_UMTS
-from acts.test_utils.tel.tel_defines import RAT_1XRTT
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
-from acts.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts.test_utils.tel.tel_test_utils import ensure_phone_default_state
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts.test_utils.tel.tel_test_utils import toggle_volte
-from acts.test_utils.tel.tel_test_utils import check_apm_mode_on_by_serial
-from acts.test_utils.tel.tel_test_utils import set_apm_mode_on_by_serial
-from acts.test_utils.tel.tel_test_utils import set_preferred_apn_by_adb
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.tel_voice_utils import phone_idle_volte
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+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
+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_1x
+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_wcdma
+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
+from acts_contrib.test_utils.tel.tel_defines import DEFAULT_EMERGENCY_CALL_NUMBER
+from acts_contrib.test_utils.tel.tel_defines import EMERGENCY_CALL_NUMBERS
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
+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
+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_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_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 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 1a1fbf9..3683173 100644
--- a/acts_tests/tests/google/tel/lab/TelLabEtwsTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabEtwsTest.py
@@ -22,32 +22,32 @@
 from acts.controllers.anritsu_lib.md8475a import MD8475A
 from acts.controllers.anritsu_lib.md8475a import CBCHSetup
 from acts.controllers.anritsu_lib.md8475a import CTCHSetup
-from acts.test_utils.tel.anritsu_utils import ETWS_WARNING_EARTHQUAKETSUNAMI
-from acts.test_utils.tel.anritsu_utils import ETWS_WARNING_OTHER_EMERGENCY
-from acts.test_utils.tel.anritsu_utils import cb_serial_number
-from acts.test_utils.tel.anritsu_utils import etws_receive_verify_message_lte_wcdma
-from acts.test_utils.tel.anritsu_utils import set_system_model_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte
-from acts.test_utils.tel.anritsu_utils import set_system_model_wcdma
-from acts.test_utils.tel.anritsu_utils import set_usim_parameters
-from acts.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_1XRTT
-from acts.test_utils.tel.tel_defines import RAT_GSM
-from acts.test_utils.tel.tel_defines import RAT_LTE
-from acts.test_utils.tel.tel_defines import RAT_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_UMTS
-from acts.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.anritsu_utils import ETWS_WARNING_EARTHQUAKETSUNAMI
+from acts_contrib.test_utils.tel.anritsu_utils import ETWS_WARNING_OTHER_EMERGENCY
+from acts_contrib.test_utils.tel.anritsu_utils import cb_serial_number
+from acts_contrib.test_utils.tel.anritsu_utils import etws_receive_verify_message_lte_wcdma
+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_wcdma
+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 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 RAT_1XRTT
+from acts_contrib.test_utils.tel.tel_defines import RAT_GSM
+from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
+from acts_contrib.test_utils.tel.tel_defines import RAT_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
+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_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
 
 WAIT_TIME_BETWEEN_REG_AND_MSG = 15  # default 15 sec
diff --git a/acts_tests/tests/google/tel/lab/TelLabMobilityTest.py b/acts_tests/tests/google/tel/lab/TelLabMobilityTest.py
index f620826..aba648b 100644
--- a/acts_tests/tests/google/tel/lab/TelLabMobilityTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabMobilityTest.py
@@ -22,42 +22,42 @@
 from acts.controllers.anritsu_lib._anritsu_utils import AnritsuError
 from acts.controllers.anritsu_lib.md8475a import MD8475A
 from acts.controllers.anritsu_lib.md8475a import BtsNumber
-from acts.test_utils.tel.anritsu_utils import WAIT_TIME_ANRITSU_REG_AND_CALL
-from acts.test_utils.tel.anritsu_utils import handover_tc
-from acts.test_utils.tel.anritsu_utils import make_ims_call
-from acts.test_utils.tel.anritsu_utils import tear_down_call
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_lte
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_wcdma
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_1x
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_evdo
-from acts.test_utils.tel.anritsu_utils import set_usim_parameters
-from acts.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts.test_utils.tel.tel_defines import CALL_TEARDOWN_PHONE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_UMTS
-from acts.test_utils.tel.tel_defines import RAT_1XRTT
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
-from acts.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import get_host_ip_address
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts.test_utils.tel.tel_test_utils import toggle_volte
-from acts.test_utils.tel.tel_test_utils import run_multithread_func
-from acts.test_utils.tel.tel_test_utils import iperf_test_by_adb
-from acts.test_utils.tel.tel_test_utils import set_preferred_apn_by_adb
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.tel_voice_utils import phone_idle_volte
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.anritsu_utils import WAIT_TIME_ANRITSU_REG_AND_CALL
+from acts_contrib.test_utils.tel.anritsu_utils import handover_tc
+from acts_contrib.test_utils.tel.anritsu_utils import make_ims_call
+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
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
+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
+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_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_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.TelephonyBaseTest import TelephonyBaseTest
 from acts.utils import adb_shell_ping
 from acts.utils import rand_ascii_str
 from acts.controllers import iperf_server
diff --git a/acts_tests/tests/google/tel/lab/TelLabNeighborCellTest.py b/acts_tests/tests/google/tel/lab/TelLabNeighborCellTest.py
index ae3ac1d..8690ae7 100644
--- a/acts_tests/tests/google/tel/lab/TelLabNeighborCellTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabNeighborCellTest.py
@@ -27,27 +27,27 @@
 from acts.controllers.anritsu_lib.md8475a import BtsServiceState
 from acts.controllers.anritsu_lib.md8475a import MD8475A
 from acts.controllers.anritsu_lib.mg3710a import MG3710A
-from acts.test_utils.tel.anritsu_utils import LTE_BAND_2
-from acts.test_utils.tel.anritsu_utils import set_system_model_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_lte
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_wcdma
-from acts.test_utils.tel.anritsu_utils import set_system_model_wcdma
-from acts.test_utils.tel.anritsu_utils import set_system_model_wcdma_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_wcdma_wcdma
-from acts.test_utils.tel.anritsu_utils import set_usim_parameters
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_UMTS
-from acts.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.anritsu_utils import LTE_BAND_2
+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_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 set_system_model_wcdma_gsm
+from acts_contrib.test_utils.tel.anritsu_utils import set_system_model_wcdma_wcdma
+from acts_contrib.test_utils.tel.anritsu_utils import set_usim_parameters
+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 RAT_FAMILY_CDMA2000
+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_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 from acts.controllers.anritsu_lib.cell_configurations import \
     gsm_band850_ch128_fr869_cid58_cell
 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 b5ccd08..4208689 100644
--- a/acts_tests/tests/google/tel/lab/TelLabProjectFiTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabProjectFiTest.py
@@ -21,37 +21,37 @@
 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.test_utils.tel.anritsu_utils import cb_serial_number
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte
-from acts.test_utils.tel.anritsu_utils import set_usim_parameters
-from acts.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts.test_utils.tel.tel_test_utils import \
+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.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
+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
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_defines import RAT_LTE
-from acts.test_utils.tel.tel_defines import CARRIER_SPT
-from acts.test_utils.tel.tel_defines import CARRIER_TMO
-from acts.test_utils.tel.tel_defines import CARRIER_USCC
-from acts.test_utils.tel.tel_lookup_tables import operator_name_from_plmn_id
-from acts.test_utils.tel.tel_test_utils import abort_all_tests
-from acts.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import is_sim_ready
-from acts.test_utils.tel.tel_test_utils import log_screen_shot
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import reboot_device
-from acts.test_utils.tel.tel_test_utils import refresh_droid_config
-from acts.test_utils.tel.tel_test_utils import send_dialer_secret_code
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts.test_utils.tel.tel_test_utils import wait_for_state
-from acts.test_utils.tel.tel_test_utils import add_google_account
-from acts.test_utils.tel.tel_test_utils import remove_google_account
+from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
+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_lookup_tables import operator_name_from_plmn_id
+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_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
 
 WAIT_TIME_BETWEEN_REG_AND_MSG = 15  # default 15 sec
 CARRIER = None
diff --git a/acts_tests/tests/google/tel/lab/TelLabSmsTest.py b/acts_tests/tests/google/tel/lab/TelLabSmsTest.py
index 47270c9..2f2bc68 100644
--- a/acts_tests/tests/google/tel/lab/TelLabSmsTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabSmsTest.py
@@ -22,45 +22,45 @@
 from acts.controllers.anritsu_lib._anritsu_utils import AnritsuError
 from acts.controllers.anritsu_lib.md8475a import MD8475A
 from acts.controllers.anritsu_lib.md8475a import VirtualPhoneStatus
-from acts.test_utils.tel.anritsu_utils import set_system_model_1x
-from acts.test_utils.tel.anritsu_utils import set_system_model_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte
-from acts.test_utils.tel.anritsu_utils import set_system_model_wcdma
-from acts.test_utils.tel.anritsu_utils import sms_mo_send
-from acts.test_utils.tel.anritsu_utils import sms_mt_receive_verify
-from acts.test_utils.tel.anritsu_utils import set_usim_parameters
-from acts.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_1XRTT
-from acts.test_utils.tel.tel_defines import RAT_GSM
-from acts.test_utils.tel.tel_defines import RAT_LTE
-from acts.test_utils.tel.tel_defines import RAT_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_UMTS
-from acts.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_test_utils import toggle_volte
-from acts.test_utils.tel.tel_test_utils import set_preferred_apn_by_adb
-from acts.test_utils.tel.tel_defines import CALL_TEARDOWN_PHONE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_UMTS
-from acts.test_utils.tel.tel_defines import RAT_1XRTT
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
+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_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 RAT_1XRTT
+from acts_contrib.test_utils.tel.tel_defines import RAT_GSM
+from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
+from acts_contrib.test_utils.tel.tel_defines import RAT_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
+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.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
+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
+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_CDMA_EVDO_GSM_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
 from acts.utils import rand_ascii_str
 
 SINGLE_PART_LEN = 40
diff --git a/acts_tests/tests/google/tel/lab/TelLabUeIdentityTest.py b/acts_tests/tests/google/tel/lab/TelLabUeIdentityTest.py
index 3a9bb77..e7858ce 100644
--- a/acts_tests/tests/google/tel/lab/TelLabUeIdentityTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabUeIdentityTest.py
@@ -20,27 +20,27 @@
 from acts.controllers.anritsu_lib._anritsu_utils import AnritsuError
 from acts.controllers.anritsu_lib.md8475a import MD8475A
 from acts.controllers.anritsu_lib.md8475a import UEIdentityType
-from acts.test_utils.tel.anritsu_utils import WAIT_TIME_ANRITSU_REG_AND_OPER
-from acts.test_utils.tel.anritsu_utils import read_ue_identity
-from acts.test_utils.tel.anritsu_utils import set_system_model_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte
-from acts.test_utils.tel.anritsu_utils import set_system_model_wcdma
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_1XRTT
-from acts.test_utils.tel.tel_defines import RAT_GSM
-from acts.test_utils.tel.tel_defines import RAT_LTE
-from acts.test_utils.tel.tel_defines import RAT_WCDMA
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_UMTS
-from acts.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.anritsu_utils import WAIT_TIME_ANRITSU_REG_AND_OPER
+from acts_contrib.test_utils.tel.anritsu_utils import read_ue_identity
+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_wcdma
+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 RAT_1XRTT
+from acts_contrib.test_utils.tel.tel_defines import RAT_GSM
+from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
+from acts_contrib.test_utils.tel.tel_defines import RAT_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
+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_test_utils import toggle_airplane_mode
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 
 
 class TelLabUeIdentityTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/lab/TelLabVoiceTest.py b/acts_tests/tests/google/tel/lab/TelLabVoiceTest.py
index 1a638f9..f2417dd 100644
--- a/acts_tests/tests/google/tel/lab/TelLabVoiceTest.py
+++ b/acts_tests/tests/google/tel/lab/TelLabVoiceTest.py
@@ -24,42 +24,42 @@
 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.test_utils.tel.anritsu_utils import WAIT_TIME_ANRITSU_REG_AND_CALL
-from acts.test_utils.tel.anritsu_utils import call_mo_setup_teardown
-from acts.test_utils.tel.anritsu_utils import ims_call_cs_teardown
-from acts.test_utils.tel.anritsu_utils import call_mt_setup_teardown
-from acts.test_utils.tel.anritsu_utils import set_system_model_1x
-from acts.test_utils.tel.anritsu_utils import set_system_model_1x_evdo
-from acts.test_utils.tel.anritsu_utils import set_system_model_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_1x
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_wcdma
-from acts.test_utils.tel.anritsu_utils import set_system_model_lte_gsm
-from acts.test_utils.tel.anritsu_utils import set_system_model_wcdma
-from acts.test_utils.tel.anritsu_utils import set_usim_parameters
-from acts.test_utils.tel.anritsu_utils import set_post_sim_params
-from acts.test_utils.tel.tel_defines import CALL_TEARDOWN_PHONE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_GSM
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_LTE
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_UMTS
-from acts.test_utils.tel.tel_defines import RAT_1XRTT
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_UMTS
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
-from acts.test_utils.tel.tel_test_utils import ensure_network_rat
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts.test_utils.tel.tel_test_utils import toggle_volte
-from acts.test_utils.tel.tel_test_utils import set_preferred_apn_by_adb
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.tel_voice_utils import phone_idle_volte
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+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
+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_1x
+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_wcdma
+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
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_CDMA2000
+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
+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_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_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.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 5269bd6..7b63288 100644
--- a/acts_tests/tests/google/tel/live/TelLiveCBRSTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveCBRSTest.py
@@ -21,52 +21,52 @@
 import collections
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
-from acts.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_REG_AND_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_FOR_CBRS_DATA_SWITCH
-from acts.test_utils.tel.tel_defines import EventActiveDataSubIdChanged
-from acts.test_utils.tel.tel_defines import NetworkCallbackAvailable
-from acts.test_utils.tel.tel_defines import EventNetworkCallback
-from acts.test_utils.tel.tel_test_utils import get_phone_number
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import hangup_call_by_adb
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import is_phone_not_in_call
-from acts.test_utils.tel.tel_test_utils import wait_and_answer_call
-from acts.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.tel_test_utils import load_scone_cat_simulate_data
-from acts.test_utils.tel.tel_test_utils import test_data_browsing_success_using_sl4a
-from acts.test_utils.tel.tel_test_utils import test_data_browsing_failure_using_sl4a
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_test_utils import is_current_data_on_cbrs
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import STORY_LINE
-from acts.test_utils.tel.tel_test_utils import get_device_epoch_time
-from acts.test_utils.tel.tel_test_utils import start_qxdm_logger
-from acts.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_not_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte_for_subscription
-from acts.test_utils.tel.tel_voice_utils import phone_setup_cdma
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
-from acts.test_utils.tel.tel_voice_utils import phone_idle_3g
-from acts.test_utils.tel.tel_voice_utils import phone_idle_2g
-from acts.test_utils.tel.tel_voice_utils import phone_idle_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_idle_not_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_idle_volte
-from acts.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import get_operatorname_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import get_cbrs_and_default_sub_id
+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 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_FOR_CBRS_DATA_SWITCH
+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_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
+from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
+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 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 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.utils import get_current_epoch_time
 from queue import Empty
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveCellInfoTest.py b/acts_tests/tests/google/tel/live/TelLiveCellInfoTest.py
index 037f260..5895d10 100644
--- a/acts_tests/tests/google/tel/live/TelLiveCellInfoTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveCellInfoTest.py
@@ -18,10 +18,10 @@
 
 import time
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected, \
+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.test_utils.wifi import wifi_test_utils
+from acts_contrib.test_utils.wifi import wifi_test_utils
 from acts.utils import disable_usb_charging, enable_usb_charging
 
 NANO_TO_SEC = 1000000000
diff --git a/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorBaseTest.py b/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorBaseTest.py
index b7e4403..5ec5ddb 100644
--- a/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorBaseTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorBaseTest.py
@@ -22,44 +22,44 @@
 import time
 
 from acts import signals
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CAPABILITY_VOLTE
-from acts.test_utils.tel.tel_defines import CAPABILITY_VT
-from acts.test_utils.tel.tel_defines import CAPABILITY_WFC
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_FOR_STATE_CHANGE
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
-from acts.test_utils.tel.tel_test_utils import bring_up_connectivity_monitor
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts.test_utils.tel.tel_test_utils import get_device_epoch_time
-from acts.test_utils.tel.tel_test_utils import get_model_name
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_test_utils import get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import last_call_drop_reason
-from acts.test_utils.tel.tel_test_utils import reboot_device
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import toggle_volte
-from acts.test_utils.tel.tel_test_utils import toggle_wfc
-from acts.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts.test_utils.tel.tel_test_utils import trigger_modem_crash
-from acts.test_utils.tel.tel_test_utils import trigger_modem_crash_by_modem
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_video_utils import video_call_setup_teardown
-from acts.test_utils.tel.tel_video_utils import phone_setup_video
-from acts.test_utils.tel.tel_video_utils import \
+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_VT
+from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_WFC
+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_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_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
 
 CALL_DROP_CODE_MAPPING = {
diff --git a/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorMobilityTest.py b/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorMobilityTest.py
index 2cd6ad5..fd76813 100644
--- a/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorMobilityTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorMobilityTest.py
@@ -19,24 +19,24 @@
 
 import time
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_atten_utils import set_rssi
-from acts.test_utils.tel.tel_defines import CELL_WEAK_RSSI_VALUE
-from acts.test_utils.tel.tel_defines import INVALID_WIFI_RSSI
-from acts.test_utils.tel.tel_defines import MAX_RSSI_RESERVED_VALUE
-from acts.test_utils.tel.tel_defines import MIN_RSSI_RESERVED_VALUE
-from acts.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_SCREEN_ON
-from acts.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_WIFI_CONNECTED
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_defines import WIFI_WEAK_RSSI_VALUE
-from acts.test_utils.tel.tel_defines import SignalStrengthContainer
-from acts.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
+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 INVALID_WIFI_RSSI
+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 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_test_utils import verify_internet_connection
 from TelLiveConnectivityMonitorBaseTest import TelLiveConnectivityMonitorBaseTest
 
 # Attenuator name
diff --git a/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorTest.py b/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorTest.py
index c0f0953..995c292 100644
--- a/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveConnectivityMonitorTest.py
@@ -18,8 +18,8 @@
 """
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CAPABILITY_VOLTE
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.tel_defines import CAPABILITY_VOLTE
 from TelLiveConnectivityMonitorBaseTest import TelLiveConnectivityMonitorBaseTest
 from TelLiveConnectivityMonitorBaseTest import ACTIONS
 from TelLiveConnectivityMonitorBaseTest import TROUBLES
diff --git a/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py b/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py
index c979c7a..b547132 100644
--- a/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveDSDSVoiceTest.py
@@ -23,103 +23,103 @@
 
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
-from acts.test_utils.tel.tel_defines import GEN_3G
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import INVALID_WIFI_RSSI
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALL_DROP
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_NW_SELECTION
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
-from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND
-from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND
-from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING
-from acts.test_utils.tel.tel_defines import RAT_LTE
-from acts.test_utils.tel.tel_defines import RAT_IWLAN
-from acts.test_utils.tel.tel_defines import RAT_WCDMA
-from acts.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_REG_AND_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_SCREEN_ON
-from acts.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_WIFI_CONNECTED
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_DISABLED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_ONLY
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_defines import WIFI_WEAK_RSSI_VALUE
-from acts.test_utils.tel.tel_defines import EventNetworkCallback
-from acts.test_utils.tel.tel_defines import NetworkCallbackAvailable
-from acts.test_utils.tel.tel_defines import NetworkCallbackLost
-from acts.test_utils.tel.tel_defines import SignalStrengthContainer
-from acts.test_utils.tel.tel_defines import WAIT_TIME_CHANGE_DATA_SUB_ID
-from acts.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import get_network_rat
-from acts.test_utils.tel.tel_test_utils import get_phone_number
-from acts.test_utils.tel.tel_test_utils import get_phone_number_for_subscription
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import hangup_call_by_adb
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import is_network_call_back_event_match
-from acts.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts.test_utils.tel.tel_test_utils import is_phone_not_in_call
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import toggle_volte
-from acts.test_utils.tel.tel_test_utils import wait_and_answer_call
-from acts.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_droid_not_in_call
-from acts.test_utils.tel.tel_test_utils import wait_for_wfc_disabled
-from acts.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import get_telephony_signal_strength
-from acts.test_utils.tel.tel_test_utils import get_lte_rsrp
-from acts.test_utils.tel.tel_test_utils import get_wifi_signal_strength
-from acts.test_utils.tel.tel_test_utils import wait_for_state
-from acts.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.tel_test_utils import start_qxdm_logger
-from acts.test_utils.tel.tel_test_utils import active_file_download_test
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import test_data_browsing_success_using_sl4a
-from acts.test_utils.tel.tel_test_utils import test_data_browsing_failure_using_sl4a
-from acts.test_utils.tel.tel_test_utils import sms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import mms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_not_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
-from acts.test_utils.tel.tel_voice_utils import phone_idle_3g
-from acts.test_utils.tel.tel_voice_utils import phone_idle_3g_for_subscription
-from acts.test_utils.tel.tel_voice_utils import phone_idle_2g
-from acts.test_utils.tel.tel_voice_utils import phone_idle_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_idle_not_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_idle_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_2g
-from acts.test_utils.tel.tel_subscription_utils import set_subid_for_outgoing_call
-from acts.test_utils.tel.tel_subscription_utils import set_incoming_voice_sub_id
-from acts.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import get_operatorname_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import get_default_data_sub_id
-from acts.test_utils.tel.tel_subscription_utils import perform_dds_switch
-from acts.test_utils.tel.tel_subscription_utils import set_subid_for_data
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-from acts.test_utils.tel.tel_subscription_utils import set_subid_for_message
-from acts.test_utils.tel.tel_subscription_utils import set_slways_allow_mms_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
+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_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_slways_allow_mms_data
 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 efa1e9b..6c8726d 100755
--- a/acts_tests/tests/google/tel/live/TelLiveDataTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveDataTest.py
@@ -25,117 +25,117 @@
 
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_subscription_utils import \
+from acts_contrib.test_utils.tel.tel_subscription_utils import \
     get_subid_from_slot_index
-from acts.test_utils.bt.bt_test_utils import bluetooth_enabled_check
-from acts.test_utils.bt.bt_test_utils import disable_bluetooth
-from acts.test_utils.bt.bt_test_utils import pair_pri_to_sec
-from acts.test_utils.tel.tel_subscription_utils import set_subid_for_data
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
-from acts.test_utils.tel.tel_defines import DATA_STATE_CONNECTED
-from acts.test_utils.tel.tel_defines import FAKE_DATE_TIME
-from acts.test_utils.tel.tel_defines import FAKE_YEAR
-from acts.test_utils.tel.tel_defines import EventNetworkCallback
-from acts.test_utils.tel.tel_defines import NetworkCallbackCapabilitiesChanged
-from acts.test_utils.tel.tel_defines import NetworkCallbackLost
-from acts.test_utils.tel.tel_defines import GEN_2G
-from acts.test_utils.tel.tel_defines import GEN_3G
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts.test_utils.tel.tel_defines import RAT_2G
-from acts.test_utils.tel.tel_defines import RAT_3G
-from acts.test_utils.tel.tel_defines import RAT_4G
-from acts.test_utils.tel.tel_defines import RAT_5G
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_WCDMA_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_NR_LTE_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import SIM1_SLOT_INDEX
-from acts.test_utils.tel.tel_defines import SIM2_SLOT_INDEX
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_NW_SELECTION
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_TETHERING_ENTITLEMENT_CHECK
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_WIFI_CONNECTION
-from acts.test_utils.tel.tel_defines import TETHERING_MODE_WIFI
-from acts.test_utils.tel.tel_defines import WAIT_TIME_AFTER_REBOOT
-from acts.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_STATE_CHECK
-from acts.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_USER_PLANE_DATA
-from acts.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_REG_AND_CALL
-from acts.test_utils.tel.tel_defines import \
+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_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
+from acts_contrib.test_utils.tel.tel_defines import DATA_STATE_CONNECTED
+from acts_contrib.test_utils.tel.tel_defines import FAKE_DATE_TIME
+from acts_contrib.test_utils.tel.tel_defines import FAKE_YEAR
+from acts_contrib.test_utils.tel.tel_defines import EventNetworkCallback
+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 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 NETWORK_SERVICE_DATA
+from acts_contrib.test_utils.tel.tel_defines import RAT_2G
+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 NETWORK_MODE_WCDMA_ONLY
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_NR_LTE_GSM_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import SIM1_SLOT_INDEX
+from acts_contrib.test_utils.tel.tel_defines import SIM2_SLOT_INDEX
+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 MAX_WAIT_TIME_WIFI_CONNECTION
+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_BETWEEN_STATE_CHECK
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_USER_PLANE_DATA
+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_DATA_STATUS_CHANGE_DURING_WIFI_TETHERING
-from acts.test_utils.tel.tel_defines import WAIT_TIME_TETHERING_AFTER_REBOOT
-from acts.test_utils.tel.tel_data_utils import airplane_mode_test
-from acts.test_utils.tel.tel_data_utils import browsing_test
-from acts.test_utils.tel.tel_data_utils import reboot_test
-from acts.test_utils.tel.tel_data_utils import change_data_sim_and_verify_data
-from acts.test_utils.tel.tel_data_utils import data_connectivity_single_bearer
-from acts.test_utils.tel.tel_data_utils import tethering_check_internet_connection
-from acts.test_utils.tel.tel_data_utils import wifi_cell_switching
-from acts.test_utils.tel.tel_data_utils import wifi_tethering_cleanup
-from acts.test_utils.tel.tel_data_utils import wifi_tethering_setup_teardown
-from acts.test_utils.tel.tel_test_utils import active_file_download_test
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import check_is_wifi_connected
-from acts.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts.test_utils.tel.tel_test_utils import \
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_TETHERING_AFTER_REBOOT
+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 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 data_connectivity_single_bearer
+from acts_contrib.test_utils.tel.tel_data_utils import tethering_check_internet_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 wifi_tethering_setup_teardown
+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_phones_idle
+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.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import get_mobile_data_usage
-from acts.test_utils.tel.tel_test_utils import get_slot_index_from_subid
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import reboot_device
-from acts.test_utils.tel.tel_test_utils import remove_mobile_data_usage_limit
-from acts.test_utils.tel.tel_test_utils import set_mobile_data_usage_limit
-from acts.test_utils.tel.tel_test_utils import stop_wifi_tethering
-from acts.test_utils.tel.tel_test_utils import start_wifi_tethering
-from acts.test_utils.tel.tel_test_utils import is_current_network_5g_nsa
-from acts.test_utils.tel.tel_test_utils import set_preferred_mode_for_5g
-from acts.test_utils.tel.tel_test_utils import get_current_override_network_type
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import verify_incall_state
-from acts.test_utils.tel.tel_test_utils import wait_for_network_generation
-from acts.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_network_rat
-from acts.test_utils.tel.tel_test_utils import \
+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 multithread_func
+from acts_contrib.test_utils.tel.tel_test_utils import reboot_device
+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 is_current_network_5g_nsa
+from acts_contrib.test_utils.tel.tel_test_utils import set_preferred_mode_for_5g
+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 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 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_network_generation
+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_rat
+from acts_contrib.test_utils.tel.tel_test_utils import \
     wait_for_voice_attach_for_subscription
-from acts.test_utils.tel.tel_test_utils import \
+from acts_contrib.test_utils.tel.tel_test_utils import \
     wait_for_data_attach_for_subscription
-from acts.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts.test_utils.tel.tel_test_utils import wifi_reset
-from acts.test_utils.tel.tel_test_utils import wait_for_state
-from acts.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.tel.tel_test_utils import WIFI_SSID_KEY
-from acts.test_utils.tel.tel_test_utils import check_data_stall_detection
-from acts.test_utils.tel.tel_test_utils import check_network_validation_fail
-from acts.test_utils.tel.tel_test_utils import break_internet_except_sl4a_port
-from acts.test_utils.tel.tel_test_utils import resume_internet_with_sl4a_port
-from acts.test_utils.tel.tel_test_utils import set_preferred_network_mode_pref
-from acts.test_utils.tel.tel_test_utils import get_device_epoch_time
-from acts.test_utils.tel.tel_test_utils import check_data_stall_recovery
-from acts.test_utils.tel.tel_test_utils import \
+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 wait_for_state
+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 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 set_preferred_network_mode_pref
+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.test_utils.tel.tel_test_utils import \
+from acts_contrib.test_utils.tel.tel_test_utils import \
     test_data_browsing_failure_using_sl4a
-from acts.test_utils.tel.tel_test_utils import set_time_sync_from_network
-from acts.test_utils.tel.tel_test_utils import datetime_handle
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_4g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_5g
+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 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_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_4g
+from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_5g
 from acts.utils import disable_doze
 from acts.utils import enable_doze
 from acts.utils import rand_ascii_str
diff --git a/acts_tests/tests/google/tel/live/TelLiveEmergencyBase.py b/acts_tests/tests/google/tel/live/TelLiveEmergencyBase.py
index e1ec0e2..edb8b39 100644
--- a/acts_tests/tests/google/tel/live/TelLiveEmergencyBase.py
+++ b/acts_tests/tests/google/tel/live/TelLiveEmergencyBase.py
@@ -21,36 +21,36 @@
 import time
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CAPABILITY_WFC
-from acts.test_utils.tel.tel_defines import DEFAULT_DEVICE_PASSWORD
-from acts.test_utils.tel.tel_defines import PHONE_TYPE_CDMA
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_lookup_tables import operator_capabilities
-from acts.test_utils.tel.tel_test_utils import abort_all_tests
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import dumpsys_last_call_info
-from acts.test_utils.tel.tel_test_utils import dumpsys_last_call_number
-from acts.test_utils.tel.tel_test_utils import dumpsys_new_call_info
-from acts.test_utils.tel.tel_test_utils import ensure_phone_default_state
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_test_utils import get_service_state_by_adb
-from acts.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts.test_utils.tel.tel_test_utils import hangup_call_by_adb
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import is_sim_lock_enabled
-from acts.test_utils.tel.tel_test_utils import initiate_emergency_dialer_call_by_adb
-from acts.test_utils.tel.tel_test_utils import last_call_drop_reason
-from acts.test_utils.tel.tel_test_utils import reset_device_password
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts.test_utils.tel.tel_test_utils import unlock_sim
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_sim_ready_by_adb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
+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_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
 
 CARRIER_OVERRIDE_CMD = (
diff --git a/acts_tests/tests/google/tel/live/TelLiveEmergencyTest.py b/acts_tests/tests/google/tel/live/TelLiveEmergencyTest.py
index 4f21ee5..e29ed7a 100644
--- a/acts_tests/tests/google/tel/live/TelLiveEmergencyTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveEmergencyTest.py
@@ -19,19 +19,19 @@
 
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CAPABILITY_WFC
-from acts.test_utils.tel.tel_defines import DEFAULT_DEVICE_PASSWORD
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_lookup_tables import operator_capabilities
-from acts.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts.test_utils.tel.tel_test_utils import reset_device_password
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts.test_utils.tel.tel_test_utils import wait_for_sim_ready_by_adb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
+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 WFC_MODE_WIFI_PREFERRED
+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_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 dc53fd6..2734fed 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSDDSSwitchTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSDDSSwitchTest.py
@@ -20,54 +20,54 @@
 from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.loggers.protos.telephony_metric_pb2 import \
+from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import \
     TelephonyVoiceTestResult
-from acts.test_utils.tel.loggers.telephony_metric_logger import \
+from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import \
     TelephonyMetricLogger
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_RECEIVE
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
-from acts.test_utils.tel.tel_defines import INVALID_SUB_ID
-from acts.test_utils.tel.tel_defines import WFC_MODE_DISABLED
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_ONLY
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_data_utils import reboot_test
-from acts.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import get_default_data_sub_id
-from acts.test_utils.tel.tel_subscription_utils import set_message_subid
-from acts.test_utils.tel.tel_subscription_utils import set_subid_for_data
-from acts.test_utils.tel.tel_subscription_utils import set_voice_sub_id
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-from acts.test_utils.tel.tel_subscription_utils import \
+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_subscription_utils import get_subid_from_slot_index
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_default_data_sub_id
+from acts_contrib.test_utils.tel.tel_subscription_utils import set_message_subid
+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.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import start_youtube_video
-from acts.test_utils.tel.tel_test_utils import \
+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.test_utils.tel.tel_test_utils import toggle_volte_for_subscription
-from acts.test_utils.tel.tel_test_utils import toggle_wfc_for_subscription
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode_for_subscription
-from acts.test_utils.tel.tel_test_utils import \
+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.test_utils.tel.tel_test_utils import mms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import log_messaging_screen_shot
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import get_slot_index_from_subid
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import is_volte_enabled
-from acts.test_utils.tel.tel_test_utils import check_is_wifi_connected
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts.test_utils.tel.tel_voice_utils import \
+from acts_contrib.test_utils.tel.tel_test_utils import mms_send_receive_verify
+from acts_contrib.test_utils.tel.tel_test_utils import verify_http_connection
+from acts_contrib.test_utils.tel.tel_test_utils import verify_internet_connection
+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.test_utils.tel.tel_voice_utils import phone_setup_on_rat
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_on_rat
-from acts.test_utils.tel.tel_voice_utils import two_phone_call_msim_for_slot
+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.utils import rand_ascii_str
 
 CallResult = TelephonyVoiceTestResult.CallResult.Value
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py
index 82ef7ca..b735184 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSMessageTest.py
@@ -19,45 +19,45 @@
 from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.loggers.protos.telephony_metric_pb2 import \
+from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import \
     TelephonyVoiceTestResult
-from acts.test_utils.tel.loggers.telephony_metric_logger import \
+from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import \
     TelephonyMetricLogger
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_RECEIVE
-from acts.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
-from acts.test_utils.tel.tel_defines import INVALID_SUB_ID
-from acts.test_utils.tel.tel_subscription_utils import \
+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.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import \
+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.test_utils.tel.tel_subscription_utils import get_default_data_sub_id
-from acts.test_utils.tel.tel_subscription_utils import set_message_subid
-from acts.test_utils.tel.tel_subscription_utils import set_subid_for_data
-from acts.test_utils.tel.tel_subscription_utils import set_voice_sub_id
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-from acts.test_utils.tel.tel_subscription_utils import \
+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.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import \
+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.test_utils.tel.tel_test_utils import \
+from acts_contrib.test_utils.tel.tel_test_utils import \
     sms_in_collision_send_receive_verify_for_subscription
-from acts.test_utils.tel.tel_test_utils import \
+from acts_contrib.test_utils.tel.tel_test_utils import \
     sms_rx_power_off_multiple_send_receive_verify_for_subscription
-from acts.test_utils.tel.tel_test_utils import \
+from acts_contrib.test_utils.tel.tel_test_utils import \
     voice_call_in_collision_with_mt_sms_msim
-from acts.test_utils.tel.tel_test_utils import mms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import log_messaging_screen_shot
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import get_slot_index_from_subid
-from acts.test_utils.tel.tel_voice_utils import \
+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.test_utils.tel.tel_voice_utils import phone_setup_on_rat
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_on_rat
+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
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py
index f7af340..aae30c3 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSSupplementaryServiceTest.py
@@ -20,49 +20,49 @@
 from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.loggers.protos.telephony_metric_pb2 import \
+from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import \
     TelephonyVoiceTestResult
-from acts.test_utils.tel.loggers.telephony_metric_logger import \
+from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import \
     TelephonyMetricLogger
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_MANAGE_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_PROPERTY_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
-from acts.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import INVALID_SUB_ID
-from acts.test_utils.tel.tel_subscription_utils import \
+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.test_utils.tel.tel_subscription_utils import \
+from acts_contrib.test_utils.tel.tel_subscription_utils import \
     get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import set_voice_sub_id
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-from acts.test_utils.tel.tel_subscription_utils import \
+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.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import num_active_calls
-from acts.test_utils.tel.tel_test_utils import verify_incall_state
-from acts.test_utils.tel.tel_test_utils import get_capability_for_subscription
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import set_call_waiting
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import \
+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_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.test_utils.tel.tel_test_utils import erase_call_forwarding_by_mmi
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_voice_utils import get_cep_conference_call_id
-from acts.test_utils.tel.tel_voice_utils import \
+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.test_utils.tel.tel_voice_utils import \
+from acts_contrib.test_utils.tel.tel_voice_utils import \
     three_phone_call_waiting_short_seq
-from acts.test_utils.tel.tel_voice_utils import swap_calls
-from acts.test_utils.tel.tel_voice_utils import phone_setup_on_rat
-from acts.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 phone_setup_on_rat
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_on_rat
 
 CallResult = TelephonyVoiceTestResult.CallResult.Value
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py
index 43baf79..f984ed7 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSVoiceTest.py
@@ -17,35 +17,35 @@
 from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.loggers.protos.telephony_metric_pb2 import \
+from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import \
     TelephonyVoiceTestResult
-from acts.test_utils.tel.loggers.telephony_metric_logger import \
+from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import \
     TelephonyMetricLogger
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
-from acts.test_utils.tel.tel_defines import INVALID_SUB_ID
-from acts.test_utils.tel.tel_subscription_utils import \
+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.test_utils.tel.tel_subscription_utils import \
+from acts_contrib.test_utils.tel.tel_subscription_utils import \
     get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import set_voice_sub_id
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-from acts.test_utils.tel.tel_subscription_utils import \
+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.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import get_capability_for_subscription
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import get_slot_index_from_subid
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_voice_utils import \
+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.test_utils.tel.tel_voice_utils import phone_setup_on_rat
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_on_rat
-from acts.test_utils.tel.tel_voice_utils import two_phone_call_msim_for_slot
+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
 
 CallResult = TelephonyVoiceTestResult.CallResult.Value
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSWfcSupplementaryServiceTest.py b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSWfcSupplementaryServiceTest.py
index a5f10e5..acdb8eb 100644
--- a/acts_tests/tests/google/tel/live/TelLiveGFTDSDSWfcSupplementaryServiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveGFTDSDSWfcSupplementaryServiceTest.py
@@ -20,74 +20,74 @@
 from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.loggers.protos.telephony_metric_pb2 import \
+from acts_contrib.test_utils.tel.loggers.protos.telephony_metric_pb2 import \
     TelephonyVoiceTestResult
-from acts.test_utils.tel.loggers.telephony_metric_logger import \
+from acts_contrib.test_utils.tel.loggers.telephony_metric_logger import \
     TelephonyMetricLogger
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_MANAGE_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_PROPERTY_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
-from acts.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import INVALID_SUB_ID
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_subscription_utils import \
+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.test_utils.tel.tel_subscription_utils import \
+from acts_contrib.test_utils.tel.tel_subscription_utils import \
     get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import set_voice_sub_id
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_0
-from acts.test_utils.tel.tel_subscription_utils import set_dds_on_slot_1
-from acts.test_utils.tel.tel_subscription_utils import \
+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.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import num_active_calls
-from acts.test_utils.tel.tel_test_utils import verify_incall_state
-from acts.test_utils.tel.tel_test_utils import toggle_volte_for_subscription
-from acts.test_utils.tel.tel_test_utils import toggle_wfc_for_subscription
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode_for_subscription
-from acts.test_utils.tel.tel_test_utils import get_capability_for_subscription
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import set_call_forwarding_by_mmi
-from acts.test_utils.tel.tel_test_utils import erase_call_forwarding_by_mmi
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import set_call_waiting
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import \
+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_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.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_voice_utils import get_cep_conference_call_id
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan_for_subscription
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import \
+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.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import \
+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.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-from acts.test_utils.tel.tel_voice_utils import \
+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.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import \
+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.test_utils.tel.tel_voice_utils import \
+from acts_contrib.test_utils.tel.tel_voice_utils import \
     three_phone_call_forwarding_short_seq
-from acts.test_utils.tel.tel_voice_utils import \
+from acts_contrib.test_utils.tel.tel_voice_utils import \
     three_phone_call_waiting_short_seq
-from acts.test_utils.tel.tel_voice_utils import swap_calls
-from acts.test_utils.tel.tel_voice_utils import phone_setup_on_rat
-from acts.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 phone_setup_on_rat
+from acts_contrib.test_utils.tel.tel_voice_utils import is_phone_in_call_on_rat
 
 CallResult = TelephonyVoiceTestResult.CallResult.Value
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveImsSettingsTest.py b/acts_tests/tests/google/tel/live/TelLiveImsSettingsTest.py
index 4454be6..2e9c918 100644
--- a/acts_tests/tests/google/tel/live/TelLiveImsSettingsTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveImsSettingsTest.py
@@ -21,57 +21,57 @@
 
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CarrierConfigs
-from acts.test_utils.tel.tel_defines import CAPABILITY_VOLTE
-from acts.test_utils.tel.tel_defines import CAPABILITY_WFC
-from acts.test_utils.tel.tel_defines import CAPABILITY_WFC_MODE_CHANGE
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_WIFI_CONNECTION
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_IMS_REGISTRATION
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_WFC_ENABLED
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_WLAN
-from acts.test_utils.tel.tel_defines import RAT_LTE
-from acts.test_utils.tel.tel_defines import RAT_UNKNOWN
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_DISABLED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_ONLY
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import dumpsys_carrier_config
-from acts.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts.test_utils.tel.tel_test_utils import get_user_config_profile
-from acts.test_utils.tel.tel_test_utils import is_droid_in_rat_family
-from acts.test_utils.tel.tel_test_utils import revert_default_telephony_setting
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts.test_utils.tel.tel_test_utils import toggle_volte
-from acts.test_utils.tel.tel_test_utils import toggle_wfc
-from acts.test_utils.tel.tel_test_utils import verify_default_telephony_setting
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_ims_registered
-from acts.test_utils.tel.tel_test_utils import wait_for_network_rat
-from acts.test_utils.tel.tel_test_utils import wait_for_not_network_rat
-from acts.test_utils.tel.tel_test_utils import wait_for_state
-from acts.test_utils.tel.tel_test_utils import wait_for_voice_attach
-from acts.test_utils.tel.tel_test_utils import wait_for_volte_enabled
-from acts.test_utils.tel.tel_test_utils import wait_for_wfc_disabled
-from acts.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts.test_utils.tel.tel_test_utils import wifi_reset
-from acts.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_test_utils import WIFI_SSID_KEY
-from acts.test_utils.tel.tel_test_utils import WIFI_PWD_KEY
+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 CAPABILITY_WFC_MODE_CHANGE
+from acts_contrib.test_utils.tel.tel_defines import GEN_4G
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WIFI_CONNECTION
+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 MAX_WAIT_TIME_IMS_REGISTRATION
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_WFC_ENABLED
+from acts_contrib.test_utils.tel.tel_defines import RAT_FAMILY_WLAN
+from acts_contrib.test_utils.tel.tel_defines import RAT_LTE
+from acts_contrib.test_utils.tel.tel_defines import RAT_UNKNOWN
+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_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 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 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_user_config_profile
+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_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 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 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_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
 
 
 class TelLiveImsSettingsTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/live/TelLiveLockedSimTest.py b/acts_tests/tests/google/tel/live/TelLiveLockedSimTest.py
index 55c989c..83c192a 100644
--- a/acts_tests/tests/google/tel/live/TelLiveLockedSimTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveLockedSimTest.py
@@ -19,25 +19,25 @@
 
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CAPABILITY_WFC
-from acts.test_utils.tel.tel_defines import DEFAULT_DEVICE_PASSWORD
-from acts.test_utils.tel.tel_defines import GEN_2G
-from acts.test_utils.tel.tel_defines import GEN_3G
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_lookup_tables import \
+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 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 WFC_MODE_WIFI_PREFERRED
+from acts_contrib.test_utils.tel.tel_lookup_tables import \
     network_preference_for_generation
-from acts.test_utils.tel.tel_lookup_tables import operator_capabilities
-from acts.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts.test_utils.tel.tel_test_utils import get_sim_state
-from acts.test_utils.tel.tel_test_utils import is_sim_lock_enabled
-from acts.test_utils.tel.tel_test_utils import is_sim_locked
-from acts.test_utils.tel.tel_test_utils import reboot_device
-from acts.test_utils.tel.tel_test_utils import reset_device_password
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts.test_utils.tel.tel_test_utils import unlock_sim
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
+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 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 2120c04..0319763 100644
--- a/acts_tests/tests/google/tel/live/TelLiveMobilityStressTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveMobilityStressTest.py
@@ -23,41 +23,41 @@
 from acts.asserts import explicit_pass
 from acts.asserts import fail
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_atten_utils import set_rssi
-from acts.test_utils.tel.tel_defines import CELL_WEAK_RSSI_VALUE
-from acts.test_utils.tel.tel_defines import CELL_STRONG_RSSI_VALUE
-from acts.test_utils.tel.tel_defines import MAX_RSSI_RESERVED_VALUE
-from acts.test_utils.tel.tel_defines import MIN_RSSI_RESERVED_VALUE
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_DISABLED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WIFI_WEAK_RSSI_VALUE
-from acts.test_utils.tel.tel_test_utils import active_file_download_test
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import ensure_phone_default_state
-from acts.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import is_voice_attached
-from acts.test_utils.tel.tel_test_utils import run_multithread_func
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts.test_utils.tel.tel_test_utils import sms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.tel_test_utils import mms_send_receive_verify
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import get_current_voice_rat
+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_voice_utils import get_current_voice_rat
 
 from acts.logger import epoch_to_log_line_timestamp
 from acts.utils import get_current_epoch_time
diff --git a/acts_tests/tests/google/tel/live/TelLiveNoQXDMLogTest.py b/acts_tests/tests/google/tel/live/TelLiveNoQXDMLogTest.py
index c50403c..2ada2a8 100644
--- a/acts_tests/tests/google/tel/live/TelLiveNoQXDMLogTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveNoQXDMLogTest.py
@@ -29,44 +29,44 @@
 from acts.controllers.android_device import list_adb_devices
 from acts.controllers.android_device import list_fastboot_devices
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import WAIT_TIME_FOR_BOOT_COMPLETE
-from acts.test_utils.tel.tel_defines import WAIT_TIME_FOR_CARRIERCONFIG_CHANGE
-from acts.test_utils.tel.tel_defines import WAIT_TIME_FOR_CARRIERID_CHANGE
-from acts.test_utils.tel.tel_defines import VZW_CARRIER_CONFIG_VERSION
-from acts.test_utils.tel.tel_defines import ATT_CARRIER_CONFIG_VERSION
-from acts.test_utils.tel.tel_defines import CARRIER_ID_METADATA_URL
-from acts.test_utils.tel.tel_defines import CARRIER_ID_CONTENT_URL
-from acts.test_utils.tel.tel_defines import CARRIER_ID_VERSION
-from acts.test_utils.tel.tel_defines import ER_DB_ID_VERSION
-from acts.test_utils.tel.tel_defines import WAIT_TIME_FOR_ER_DB_CHANGE
-from acts.test_utils.tel.tel_defines import CARRIER_ID_METADATA_URL_P
-from acts.test_utils.tel.tel_defines import CARRIER_ID_CONTENT_URL_P
-from acts.test_utils.tel.tel_defines import CARRIER_ID_VERSION_P
-from acts.test_utils.tel.tel_lookup_tables import device_capabilities
-from acts.test_utils.tel.tel_lookup_tables import operator_capabilities
-from acts.test_utils.tel.tel_test_utils import lock_lte_band_by_mds
-from acts.test_utils.tel.tel_test_utils import get_model_name
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_test_utils import reboot_device
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import trigger_modem_crash_by_modem
-from acts.test_utils.tel.tel_test_utils import bring_up_sl4a
-from acts.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts.test_utils.tel.tel_test_utils import get_carrier_config_version
-from acts.test_utils.tel.tel_test_utils import get_carrier_id_version
-from acts.test_utils.tel.tel_test_utils import get_er_db_id_version
-from acts.test_utils.tel.tel_test_utils import get_database_content
-from acts.test_utils.tel.tel_test_utils import install_googleaccountutil_apk
-from acts.test_utils.tel.tel_test_utils import add_whitelisted_account
-from acts.test_utils.tel.tel_test_utils import adb_disable_verity
-from acts.test_utils.tel.tel_test_utils import install_carriersettings_apk
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import cleanup_configupdater
-from acts.test_utils.tel.tel_test_utils import pull_carrier_id_files
-from acts.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_subscription_utils import get_cbrs_and_default_sub_id
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_BOOT_COMPLETE
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_CARRIERCONFIG_CHANGE
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_CARRIERID_CHANGE
+from acts_contrib.test_utils.tel.tel_defines import VZW_CARRIER_CONFIG_VERSION
+from acts_contrib.test_utils.tel.tel_defines import ATT_CARRIER_CONFIG_VERSION
+from acts_contrib.test_utils.tel.tel_defines import CARRIER_ID_METADATA_URL
+from acts_contrib.test_utils.tel.tel_defines import CARRIER_ID_CONTENT_URL
+from acts_contrib.test_utils.tel.tel_defines import CARRIER_ID_VERSION
+from acts_contrib.test_utils.tel.tel_defines import ER_DB_ID_VERSION
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_FOR_ER_DB_CHANGE
+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_lookup_tables import device_capabilities
+from acts_contrib.test_utils.tel.tel_lookup_tables import operator_capabilities
+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
+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 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
+from acts_contrib.test_utils.tel.tel_test_utils import get_database_content
+from acts_contrib.test_utils.tel.tel_test_utils import install_googleaccountutil_apk
+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.utils import get_current_epoch_time
 from acts.keys import Config
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveNoSimTest.py b/acts_tests/tests/google/tel/live/TelLiveNoSimTest.py
index c8b2b7f..9e4cc07 100644
--- a/acts_tests/tests/google/tel/live/TelLiveNoSimTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveNoSimTest.py
@@ -19,19 +19,19 @@
 
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import DEFAULT_DEVICE_PASSWORD
-from acts.test_utils.tel.tel_defines import GEN_2G
-from acts.test_utils.tel.tel_defines import GEN_3G
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import SIM_STATE_ABSENT
-from acts.test_utils.tel.tel_defines import SIM_STATE_UNKNOWN
-from acts.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts.test_utils.tel.tel_test_utils import get_sim_state
-from acts.test_utils.tel.tel_lookup_tables import \
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.tel_defines import DEFAULT_DEVICE_PASSWORD
+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 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_test_utils import get_sim_state
+from acts_contrib.test_utils.tel.tel_lookup_tables import \
     network_preference_for_generation
-from acts.test_utils.tel.tel_test_utils import reset_device_password
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
+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/TelLivePostflightTest.py b/acts_tests/tests/google/tel/live/TelLivePostflightTest.py
index 4ccbc8a..6c7b33b 100644
--- a/acts_tests/tests/google/tel/live/TelLivePostflightTest.py
+++ b/acts_tests/tests/google/tel/live/TelLivePostflightTest.py
@@ -20,7 +20,7 @@
 from acts.asserts import fail
 from acts.base_test import BaseTestClass
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
 
 
 class TelLivePostflightTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/live/TelLivePreflightTest.py b/acts_tests/tests/google/tel/live/TelLivePreflightTest.py
index 18da880..134255e 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.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_WLAN
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_test_utils import abort_all_tests
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import get_user_config_profile
-from acts.test_utils.tel.tel_test_utils import is_sim_locked
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import unlock_sim
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_network_rat
-from acts.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan_cellular_preferred
+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_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 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
 
 
 class TelLivePreflightTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/live/TelLiveProjectFiTest.py b/acts_tests/tests/google/tel/live/TelLiveProjectFiTest.py
index a86939c..5bf765e 100644
--- a/acts_tests/tests/google/tel/live/TelLiveProjectFiTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveProjectFiTest.py
@@ -20,24 +20,24 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CARRIER_SPT
-from acts.test_utils.tel.tel_defines import CARRIER_TMO
-from acts.test_utils.tel.tel_defines import CARRIER_USCC
-from acts.test_utils.tel.tel_lookup_tables import operator_name_from_plmn_id
-from acts.test_utils.tel.tel_test_utils import abort_all_tests
-from acts.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import is_sim_ready
-from acts.test_utils.tel.tel_test_utils import log_screen_shot
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import reboot_device
-from acts.test_utils.tel.tel_test_utils import refresh_droid_config
-from acts.test_utils.tel.tel_test_utils import send_dialer_secret_code
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb
-from acts.test_utils.tel.tel_test_utils import wait_for_state
-from acts.test_utils.tel.tel_test_utils import add_google_account
-from acts.test_utils.tel.tel_test_utils import remove_google_account
+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_lookup_tables import operator_name_from_plmn_id
+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_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
 
 CARRIER_AUTO = "auto"
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py b/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py
index 1fd39eb..bbfea86 100644
--- a/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveRebootStressTest.py
@@ -23,57 +23,57 @@
 from acts import signals
 from acts.test_decorators import test_tracker_info
 from acts.controllers.sl4a_lib.sl4a_types import Sl4aNetworkInfo
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_data_utils import wifi_tethering_setup_teardown
-from acts.test_utils.tel.tel_defines import CAPABILITY_VOLTE
-from acts.test_utils.tel.tel_defines import CAPABILITY_VT
-from acts.test_utils.tel.tel_defines import CAPABILITY_WFC
-from acts.test_utils.tel.tel_defines import CAPABILITY_OMADM
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_TETHERING_ENTITLEMENT_CHECK
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import RAT_FAMILY_WLAN
-from acts.test_utils.tel.tel_defines import TETHERING_MODE_WIFI
-from acts.test_utils.tel.tel_defines import WAIT_TIME_AFTER_REBOOT
-from acts.test_utils.tel.tel_defines import WAIT_TIME_AFTER_CRASH
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts.test_utils.tel.tel_test_utils import get_model_name
-from acts.test_utils.tel.tel_test_utils import get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_test_utils import get_slot_index_from_subid
-from acts.test_utils.tel.tel_test_utils import is_droid_in_network_generation
-from acts.test_utils.tel.tel_test_utils import is_sim_locked
-from acts.test_utils.tel.tel_test_utils import mms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import power_off_sim
-from acts.test_utils.tel.tel_test_utils import power_on_sim
-from acts.test_utils.tel.tel_test_utils import reboot_device
-from acts.test_utils.tel.tel_test_utils import sms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import trigger_modem_crash
-from acts.test_utils.tel.tel_test_utils import trigger_modem_crash_by_modem
-from acts.test_utils.tel.tel_test_utils import unlock_sim
-from acts.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_network_generation
-from acts.test_utils.tel.tel_test_utils import wait_for_network_rat
-from acts.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_state
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_idle_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_video_utils import video_call_setup_teardown
-from acts.test_utils.tel.tel_video_utils import phone_setup_video
-from acts.test_utils.tel.tel_video_utils import \
+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_VT
+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 RAT_FAMILY_WLAN
+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_AFTER_CRASH
+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_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_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 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.utils import get_current_epoch_time
diff --git a/acts_tests/tests/google/tel/live/TelLiveSettingsTest.py b/acts_tests/tests/google/tel/live/TelLiveSettingsTest.py
index 10b045f..879353e 100644
--- a/acts_tests/tests/google/tel/live/TelLiveSettingsTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveSettingsTest.py
@@ -24,23 +24,23 @@
 from acts.keys import Config
 from acts.utils import unzip_maintain_permissions
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_FOR_STATE_CHANGE
-from acts.test_utils.tel.tel_test_utils import dumpsys_carrier_config
-from acts.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts.test_utils.tel.tel_test_utils import flash_radio
-from acts.test_utils.tel.tel_test_utils import get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_test_utils import get_slot_index_from_subid
-from acts.test_utils.tel.tel_test_utils import is_sim_locked
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import power_off_sim
-from acts.test_utils.tel.tel_test_utils import power_on_sim
-from acts.test_utils.tel.tel_test_utils import print_radio_info
-from acts.test_utils.tel.tel_test_utils import revert_default_telephony_setting
-from acts.test_utils.tel.tel_test_utils import set_qxdm_logger_command
-from acts.test_utils.tel.tel_test_utils import system_file_push
-from acts.test_utils.tel.tel_test_utils import unlock_sim
-from acts.test_utils.tel.tel_test_utils import verify_default_telephony_setting
+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_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
 
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveSmokeTest.py b/acts_tests/tests/google/tel/live/TelLiveSmokeTest.py
index ac536cf..0b8ce5d 100644
--- a/acts_tests/tests/google/tel/live/TelLiveSmokeTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveSmokeTest.py
@@ -19,35 +19,35 @@
 
 import time
 from acts.keys import Config
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_data_utils import airplane_mode_test
-from acts.test_utils.tel.tel_data_utils import wifi_cell_switching
-from acts.test_utils.tel.tel_data_utils import wifi_tethering_setup_teardown
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_TETHERING_ENTITLEMENT_CHECK
-from acts.test_utils.tel.tel_defines import TETHERING_MODE_WIFI
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_lookup_tables import is_rat_svd_capable
-from acts.test_utils.tel.tel_test_utils import stop_wifi_tethering
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts.test_utils.tel.tel_test_utils import get_network_rat
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import sms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
+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 wifi_cell_switching
+from acts_contrib.test_utils.tel.tel_data_utils import wifi_tethering_setup_teardown
+from acts_contrib.test_utils.tel.tel_defines import GEN_4G
+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 NETWORK_SERVICE_VOICE
+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_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_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.utils import rand_ascii_str
 
 SKIP = 'Skip'
diff --git a/acts_tests/tests/google/tel/live/TelLiveSmsTest.py b/acts_tests/tests/google/tel/live/TelLiveSmsTest.py
index 57c241c..3fa07b1 100644
--- a/acts_tests/tests/google/tel/live/TelLiveSmsTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveSmsTest.py
@@ -20,59 +20,59 @@
 import time
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import GEN_3G
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import PHONE_TYPE_GSM
-from acts.test_utils.tel.tel_defines import RAT_3G
-from acts.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
-from acts.test_utils.tel.tel_defines import WFC_MODE_DISABLED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_subscription_utils import get_outgoing_message_sub_id
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts.test_utils.tel.tel_test_utils import ensure_phone_default_state
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import get_mobile_data_usage
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_test_utils import remove_mobile_data_usage_limit
-from acts.test_utils.tel.tel_test_utils import mms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import mms_receive_verify_after_call_hangup
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import set_preferred_mode_for_5g
-from acts.test_utils.tel.tel_test_utils import is_current_network_5g_nsa
-from acts.test_utils.tel.tel_test_utils import set_call_state_listen_level
-from acts.test_utils.tel.tel_test_utils import set_mobile_data_usage_limit
-from acts.test_utils.tel.tel_test_utils import setup_sim
-from acts.test_utils.tel.tel_test_utils import sms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import \
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+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 PHONE_TYPE_GSM
+from acts_contrib.test_utils.tel.tel_defines import RAT_3G
+from acts_contrib.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
+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_subscription_utils import get_outgoing_message_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 ensure_network_generation
+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_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_preferred_mode_for_5g
+from acts_contrib.test_utils.tel.tel_test_utils import is_current_network_5g_nsa
+from acts_contrib.test_utils.tel.tel_test_utils import set_call_state_listen_level
+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 setup_sim
+from acts_contrib.test_utils.tel.tel_test_utils import sms_send_receive_verify
+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 \
     sms_in_collision_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import \
+from acts_contrib.test_utils.tel.tel_test_utils import \
     sms_rx_power_off_multiple_send_receive_verify
-from acts.test_utils.tel.tel_video_utils import phone_setup_video
-from acts.test_utils.tel.tel_video_utils import is_phone_in_call_video_bidirectional
-from acts.test_utils.tel.tel_video_utils import video_call_setup_teardown
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_1x
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_not_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_data_general
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan_cellular_preferred
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_general
+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_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_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_data_general
+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_volte
+from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_voice_general
 from acts.utils import rand_ascii_str
 
 SMS_OVER_WIFI_PROVIDERS = ("vzw", "tmo", "fi", "rogers", "rjio", "eeuk",
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py b/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py
index 3f30cd2..dc7a62e 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressCallTest.py
@@ -20,36 +20,36 @@
 import collections
 import time
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import last_call_drop_reason
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts.test_utils.tel.tel_test_utils import sms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.tel_test_utils import verify_incall_state
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts.test_utils.tel.tel_video_utils import phone_setup_video
-from acts.test_utils.tel.tel_video_utils import video_call_setup
-from acts.test_utils.tel.tel_video_utils import \
+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_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_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.utils import get_current_epoch_time
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressDataTest.py b/acts_tests/tests/google/tel/live/TelLiveStressDataTest.py
index b8012ae..81e3ece 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressDataTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressDataTest.py
@@ -17,9 +17,9 @@
     Test Script for Telephony Stress data Test
 """
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_test_utils import iperf_test_by_adb
-from acts.test_utils.tel.tel_test_utils import iperf_udp_test_by_adb
+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
 
 
 class TelLiveStressDataTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py b/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py
index ea0bbf8..46bef20 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressFdrTest.py
@@ -22,34 +22,34 @@
 
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_data_utils import wifi_tethering_setup_teardown
-from acts.test_utils.tel.tel_defines import CAPABILITY_VOLTE
-from acts.test_utils.tel.tel_defines import CAPABILITY_WFC
-from acts.test_utils.tel.tel_defines import CAPABILITY_OMADM
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_TETHERING_ENTITLEMENT_CHECK
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import TETHERING_MODE_WIFI
-from acts.test_utils.tel.tel_defines import WAIT_TIME_AFTER_FDR
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import ensure_phone_subscription
-from acts.test_utils.tel.tel_test_utils import get_model_name
-from acts.test_utils.tel.tel_test_utils import get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_test_utils import is_droid_in_network_generation
-from acts.test_utils.tel.tel_test_utils import mms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import fastboot_wipe
-from acts.test_utils.tel.tel_test_utils import sms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import wait_for_network_generation
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_state
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_idle_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
+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_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 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
diff --git a/acts_tests/tests/google/tel/live/TelLiveStressTest.py b/acts_tests/tests/google/tel/live/TelLiveStressTest.py
index 0bde255..497a44f 100644
--- a/acts_tests/tests/google/tel/live/TelLiveStressTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveStressTest.py
@@ -27,78 +27,78 @@
 from acts import signals
 from acts.libs.proc import job
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CAPABILITY_VOLTE
-from acts.test_utils.tel.tel_defines import CAPABILITY_WFC
-from acts.test_utils.tel.tel_defines import GEN_3G
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import INCALL_UI_DISPLAY_BACKGROUND
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_RECEIVE
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_WCDMA_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GLOBAL
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_CDMA
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_GSM_ONLY
-from acts.test_utils.tel.tel_defines import NETWORK_MODE_TDSCDMA_GSM_WCDMA
-from acts.test_utils.tel.tel_defines import WAIT_TIME_AFTER_MODE_CHANGE
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_defines import WAIT_TIME_CHANGE_MESSAGE_SUB_ID
-from acts.test_utils.tel.tel_defines import WAIT_TIME_CHANGE_VOICE_SUB_ID
-from acts.test_utils.tel.tel_defines import WAIT_TIME_FOR_CBRS_DATA_SWITCH
-from acts.test_utils.tel.tel_defines import CARRIER_SING
-from acts.test_utils.tel.tel_lookup_tables import is_rat_svd_capable
-from acts.test_utils.tel.tel_test_utils import STORY_LINE
-from acts.test_utils.tel.tel_test_utils import active_file_download_test
-from acts.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import ensure_network_generation_for_subscription
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import extract_test_log
-from acts.test_utils.tel.tel_test_utils import force_connectivity_metrics_upload
-from acts.test_utils.tel.tel_test_utils import get_device_epoch_time
-from acts.test_utils.tel.tel_test_utils import get_telephony_signal_strength
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import hangup_call_by_adb
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import last_call_drop_reason
-from acts.test_utils.tel.tel_test_utils import run_multithread_func
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts.test_utils.tel.tel_test_utils import sms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import start_qxdm_loggers
-from acts.test_utils.tel.tel_test_utils import start_sdm_loggers
-from acts.test_utils.tel.tel_test_utils import start_adb_tcpdump
-from acts.test_utils.tel.tel_test_utils import synchronize_device_time
-from acts.test_utils.tel.tel_test_utils import mms_send_receive_verify
-from acts.test_utils.tel.tel_test_utils import set_preferred_network_mode_pref
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection_by_ping
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_call_id_clearing
-from acts.test_utils.tel.tel_test_utils import wait_for_data_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_in_call_active
-from acts.test_utils.tel.tel_test_utils import is_current_data_on_cbrs
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_idle_volte
-from acts.test_utils.tel.tel_voice_utils import get_current_voice_rat
-from acts.test_utils.tel.tel_subscription_utils import get_subid_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import get_operatorname_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import get_carrierid_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import get_isopportunistic_from_slot_index
-from acts.test_utils.tel.tel_subscription_utils import set_subid_for_data
-from acts.test_utils.tel.tel_subscription_utils import set_subid_for_message
-from acts.test_utils.tel.tel_subscription_utils import set_subid_for_outgoing_call
-from acts.test_utils.tel.tel_subscription_utils import set_slways_allow_mms_data
+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_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 INCALL_UI_DISPLAY_BACKGROUND
+from acts_contrib.test_utils.tel.tel_defines import MAX_WAIT_TIME_SMS_RECEIVE
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_WCDMA_ONLY
+from acts_contrib.test_utils.tel.tel_defines import NETWORK_MODE_GLOBAL
+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_TDSCDMA_GSM_WCDMA
+from acts_contrib.test_utils.tel.tel_defines import WAIT_TIME_AFTER_MODE_CHANGE
+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 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_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_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_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
+from acts_contrib.test_utils.tel.tel_subscription_utils import get_isopportunistic_from_slot_index
+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_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_slways_allow_mms_data
 from acts.utils import get_current_epoch_time
 from acts.utils import rand_ascii_str
 
diff --git a/acts_tests/tests/google/tel/live/TelLiveVideoDataTest.py b/acts_tests/tests/google/tel/live/TelLiveVideoDataTest.py
index 18dd7f7..8f86f9f 100644
--- a/acts_tests/tests/google/tel/live/TelLiveVideoDataTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveVideoDataTest.py
@@ -16,15 +16,15 @@
 """
     Test Script for VT Data test
 """
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_video_utils import \
+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.test_utils.tel.tel_video_utils import phone_setup_video
-from acts.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 video_call_setup_teardown
 
 
 class TelLiveVideoDataTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/live/TelLiveVideoTest.py b/acts_tests/tests/google/tel/live/TelLiveVideoTest.py
index 107e05f..de2e85a 100644
--- a/acts_tests/tests/google/tel/live/TelLiveVideoTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveVideoTest.py
@@ -21,58 +21,58 @@
 from queue import Empty
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import AUDIO_ROUTE_EARPIECE
-from acts.test_utils.tel.tel_defines import AUDIO_ROUTE_SPEAKER
-from acts.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
-from acts.test_utils.tel.tel_defines import CALL_STATE_HOLDING
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_MANAGE_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_MERGE_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_SWAP_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_PROPERTY_CONFERENCE
-from acts.test_utils.tel.tel_defines import CAPABILITY_VT
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_VIDEO_SESSION_EVENT
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_VOLTE_ENABLED
-from acts.test_utils.tel.tel_defines import VT_STATE_AUDIO_ONLY
-from acts.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
-from acts.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL_PAUSED
-from acts.test_utils.tel.tel_defines import VT_VIDEO_QUALITY_DEFAULT
-from acts.test_utils.tel.tel_defines import VT_STATE_RX_ENABLED
-from acts.test_utils.tel.tel_defines import VT_STATE_TX_ENABLED
-from acts.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import EVENT_VIDEO_SESSION_EVENT
-from acts.test_utils.tel.tel_defines import EventTelecomVideoCallSessionEvent
-from acts.test_utils.tel.tel_defines import SESSION_EVENT_RX_PAUSE
-from acts.test_utils.tel.tel_defines import SESSION_EVENT_RX_RESUME
-from acts.test_utils.tel.tel_lookup_tables import operator_capabilities
-from acts.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import disconnect_call_by_id
-from acts.test_utils.tel.tel_test_utils import get_model_name
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import num_active_calls
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import verify_incall_state
-from acts.test_utils.tel.tel_test_utils import wait_for_video_enabled
-from acts.test_utils.tel.tel_test_utils import get_capability_for_subscription
-from acts.test_utils.tel.tel_video_utils import get_call_id_in_video_state
-from acts.test_utils.tel.tel_video_utils import \
+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
+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_lookup_tables import operator_capabilities
+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.test_utils.tel.tel_video_utils import is_phone_in_call_voice_hd
-from acts.test_utils.tel.tel_video_utils import phone_setup_video
-from acts.test_utils.tel.tel_video_utils import \
+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.test_utils.tel.tel_video_utils import video_call_downgrade
-from acts.test_utils.tel.tel_video_utils import video_call_modify_video
-from acts.test_utils.tel.tel_video_utils import video_call_setup_teardown
-from acts.test_utils.tel.tel_voice_utils import get_audio_route
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import set_audio_route
-from acts.test_utils.tel.tel_voice_utils import get_cep_conference_call_id
+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 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
 
 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 ae7e18f..ab7ab00 100644
--- a/acts_tests/tests/google/tel/live/TelLiveVoiceConfTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveVoiceConfTest.py
@@ -20,50 +20,50 @@
 import time
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_MANAGE_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_MERGE_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_SWAP_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_PROPERTY_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
-from acts.test_utils.tel.tel_defines import CALL_STATE_HOLDING
-from acts.test_utils.tel.tel_defines import CAPABILITY_CONFERENCE
-from acts.test_utils.tel.tel_defines import PHONE_TYPE_CDMA
-from acts.test_utils.tel.tel_defines import PHONE_TYPE_GSM
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_ONLY
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_subscription_utils import get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_test_utils import call_reject
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import get_call_uri
-from acts.test_utils.tel.tel_test_utils import get_phone_number
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import is_uri_equivalent
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import num_active_calls
-from acts.test_utils.tel.tel_test_utils import verify_incall_state
-from acts.test_utils.tel.tel_test_utils import wait_and_answer_call
-from acts.test_utils.tel.tel_test_utils import get_capability_for_subscription
-from acts.test_utils.tel.tel_test_utils import ensure_phones_idle
-from acts.test_utils.tel.tel_voice_utils import get_cep_conference_call_id
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_1x
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_wcdma
-from acts.test_utils.tel.tel_voice_utils import phone_setup_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import swap_calls
-from acts.test_utils.tel.tel_voice_utils import three_phone_call_forwarding_short_seq
-from acts.test_utils.tel.tel_voice_utils import three_phone_call_waiting_short_seq
+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_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 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_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_call_uri
+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 is_uri_equivalent
+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 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 get_cep_conference_call_id
+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_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
 
 
 class TelLiveVoiceConfTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/tel/live/TelLiveVoiceTest.py b/acts_tests/tests/google/tel/live/TelLiveVoiceTest.py
index 94d9146..a129398 100644
--- a/acts_tests/tests/google/tel/live/TelLiveVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelLiveVoiceTest.py
@@ -21,83 +21,83 @@
 
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.loggers.protos.telephony_metric_pb2 import TelephonyVoiceTestResult
-from acts.test_utils.tel.loggers.telephony_metric_logger import TelephonyMetricLogger
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_data_utils import wifi_cell_switching
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
-from acts.test_utils.tel.tel_defines import GEN_2G
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
-from acts.test_utils.tel.tel_defines import CALL_STATE_HOLDING
-from acts.test_utils.tel.tel_defines import GEN_3G
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_NW_SELECTION
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts.test_utils.tel.tel_defines import PHONE_TYPE_CDMA
-from acts.test_utils.tel.tel_defines import PHONE_TYPE_GSM
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL_FOR_IMS
-from acts.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_ONLY
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_subscription_utils import \
+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_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
+from acts_contrib.test_utils.tel.tel_defines import GEN_4G
+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 GEN_3G
+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 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 WAIT_TIME_IN_CALL_FOR_IMS
+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 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.test_utils.tel.tel_subscription_utils import \
+from acts_contrib.test_utils.tel.tel_subscription_utils import \
     get_outgoing_voice_sub_id
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import \
+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.test_utils.tel.tel_test_utils import active_file_download_task
+from acts_contrib.test_utils.tel.tel_test_utils import active_file_download_task
 from acts.utils import adb_shell_ping
-from acts.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts.test_utils.tel.tel_test_utils import get_mobile_data_usage
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import is_phone_in_call_active
-from acts.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import num_active_calls
-from acts.test_utils.tel.tel_test_utils import remove_mobile_data_usage_limit
-from acts.test_utils.tel.tel_test_utils import run_multithread_func
-from acts.test_utils.tel.tel_test_utils import set_mobile_data_usage_limit
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import verify_incall_state
-from acts.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_ringing_call
-from acts.test_utils.tel.tel_test_utils import wait_for_state
-from acts.test_utils.tel.tel_test_utils import start_youtube_video
-from acts.test_utils.tel.tel_test_utils import set_wifi_to_default
-from acts.test_utils.tel.tel_test_utils import STORY_LINE
-from acts.test_utils.tel.tel_test_utils import wait_for_in_call_active
-from acts.test_utils.tel.tel_test_utils import set_preferred_mode_for_5g
-from acts.test_utils.tel.tel_test_utils import is_current_network_5g_nsa
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_1x
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_2g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_not_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_wcdma
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
-from acts.test_utils.tel.tel_voice_utils import \
+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 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_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 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_cell_data_connection
+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_state
+from acts_contrib.test_utils.tel.tel_test_utils import start_youtube_video
+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_test_utils import set_preferred_mode_for_5g
+from acts_contrib.test_utils.tel.tel_test_utils import is_current_network_5g_nsa
+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_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_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 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.test_utils.tel.tel_voice_utils import phone_setup_voice_2g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_3g
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import phone_idle_2g
-from acts.test_utils.tel.tel_voice_utils import phone_idle_3g
-from acts.test_utils.tel.tel_voice_utils import phone_idle_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_idle_volte
-from acts.test_utils.tel.tel_voice_utils import two_phone_call_leave_voice_mail
-from acts.test_utils.tel.tel_voice_utils import two_phone_call_long_seq
-from acts.test_utils.tel.tel_voice_utils import two_phone_call_short_seq
+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 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
 
 DEFAULT_LONG_DURATION_CALL_TOTAL_DURATION = 1 * 60 * 60  # default value 1 hour
 DEFAULT_PING_DURATION = 120  # in seconds
diff --git a/acts_tests/tests/google/tel/live/TelWifiDataTest.py b/acts_tests/tests/google/tel/live/TelWifiDataTest.py
index fdc7333..4672feb 100644
--- a/acts_tests/tests/google/tel/live/TelWifiDataTest.py
+++ b/acts_tests/tests/google/tel/live/TelWifiDataTest.py
@@ -17,24 +17,24 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_atten_utils import set_rssi
-from acts.test_utils.tel.tel_defines import MAX_RSSI_RESERVED_VALUE
-from acts.test_utils.tel.tel_defines import MIN_RSSI_RESERVED_VALUE
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_NW_SELECTION
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import verify_internet_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts.test_utils.tel.tel_test_utils import run_multithread_func
-from acts.test_utils.tel.tel_test_utils import active_file_download_test
-from acts.test_utils.tel.tel_test_utils import get_telephony_signal_strength
-from acts.test_utils.tel.tel_test_utils import get_wifi_signal_strength
-from acts.test_utils.tel.tel_test_utils import reboot_device
+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 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 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_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.utils import adb_shell_ping
 
 # Attenuator name
diff --git a/acts_tests/tests/google/tel/live/TelWifiVideoTest.py b/acts_tests/tests/google/tel/live/TelWifiVideoTest.py
index 360b09c..8f32038 100644
--- a/acts_tests/tests/google/tel/live/TelWifiVideoTest.py
+++ b/acts_tests/tests/google/tel/live/TelWifiVideoTest.py
@@ -20,56 +20,56 @@
 import time
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_defines import AUDIO_ROUTE_EARPIECE
-from acts.test_utils.tel.tel_defines import AUDIO_ROUTE_SPEAKER
-from acts.test_utils.tel.tel_defines import CALL_STATE_ACTIVE
-from acts.test_utils.tel.tel_defines import CALL_STATE_HOLDING
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_MANAGE_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_MERGE_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_CAPABILITY_SWAP_CONFERENCE
-from acts.test_utils.tel.tel_defines import CALL_PROPERTY_CONFERENCE
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_VIDEO_SESSION_EVENT
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_VOLTE_ENABLED
-from acts.test_utils.tel.tel_defines import VT_STATE_AUDIO_ONLY
-from acts.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL
-from acts.test_utils.tel.tel_defines import VT_STATE_BIDIRECTIONAL_PAUSED
-from acts.test_utils.tel.tel_defines import VT_VIDEO_QUALITY_DEFAULT
-from acts.test_utils.tel.tel_defines import VT_STATE_RX_ENABLED
-from acts.test_utils.tel.tel_defines import VT_STATE_TX_ENABLED
-from acts.test_utils.tel.tel_defines import WAIT_TIME_ANDROID_STATE_SETTLING
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import EVENT_VIDEO_SESSION_EVENT
-from acts.test_utils.tel.tel_defines import EventTelecomVideoCallSessionEvent
-from acts.test_utils.tel.tel_defines import SESSION_EVENT_RX_PAUSE
-from acts.test_utils.tel.tel_defines import SESSION_EVENT_RX_RESUME
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_test_utils import call_setup_teardown
-from acts.test_utils.tel.tel_test_utils import disconnect_call_by_id
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_test_utils import num_active_calls
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import verify_incall_state
-from acts.test_utils.tel.tel_test_utils import wait_for_video_enabled
-from acts.test_utils.tel.tel_video_utils import get_call_id_in_video_state
-from acts.test_utils.tel.tel_video_utils import \
+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.test_utils.tel.tel_video_utils import \
+from acts_contrib.test_utils.tel.tel_video_utils import \
     is_phone_in_call_viwifi_bidirectional
-from acts.test_utils.tel.tel_video_utils import is_phone_in_call_voice_hd
-from acts.test_utils.tel.tel_video_utils import phone_setup_video
-from acts.test_utils.tel.tel_video_utils import \
+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.test_utils.tel.tel_video_utils import video_call_downgrade
-from acts.test_utils.tel.tel_video_utils import video_call_modify_video
-from acts.test_utils.tel.tel_video_utils import video_call_setup_teardown
-from acts.test_utils.tel.tel_voice_utils import get_audio_route
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_volte
-from acts.test_utils.tel.tel_voice_utils import set_audio_route
-from acts.test_utils.tel.tel_voice_utils import get_cep_conference_call_id
-from acts.test_utils.tel.tel_voice_utils import phone_setup_iwlan
+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 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 9d0e4c5..1b177836 100644
--- a/acts_tests/tests/google/tel/live/TelWifiVoiceTest.py
+++ b/acts_tests/tests/google/tel/live/TelWifiVoiceTest.py
@@ -20,75 +20,75 @@
 import time
 from queue import Empty
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_atten_utils import set_rssi
-from acts.test_utils.tel.tel_defines import CELL_STRONG_RSSI_VALUE
-from acts.test_utils.tel.tel_defines import CELL_WEAK_RSSI_VALUE
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_ORIGINATED
-from acts.test_utils.tel.tel_defines import DIRECTION_MOBILE_TERMINATED
-from acts.test_utils.tel.tel_defines import GEN_3G
-from acts.test_utils.tel.tel_defines import GEN_4G
-from acts.test_utils.tel.tel_defines import INVALID_WIFI_RSSI
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_CALL_DROP
-from acts.test_utils.tel.tel_defines import MAX_WAIT_TIME_NW_SELECTION
-from acts.test_utils.tel.tel_defines import MAX_RSSI_RESERVED_VALUE
-from acts.test_utils.tel.tel_defines import MIN_RSSI_RESERVED_VALUE
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_DATA
-from acts.test_utils.tel.tel_defines import NETWORK_SERVICE_VOICE
-from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_BACKGROUND
-from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_FOREGROUND
-from acts.test_utils.tel.tel_defines import PRECISE_CALL_STATE_LISTEN_LEVEL_RINGING
-from acts.test_utils.tel.tel_defines import RAT_LTE
-from acts.test_utils.tel.tel_defines import RAT_IWLAN
-from acts.test_utils.tel.tel_defines import RAT_WCDMA
-from acts.test_utils.tel.tel_defines import WAIT_TIME_BETWEEN_REG_AND_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_IN_CALL
-from acts.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_SCREEN_ON
-from acts.test_utils.tel.tel_defines import WAIT_TIME_WIFI_RSSI_CALIBRATION_WIFI_CONNECTED
-from acts.test_utils.tel.tel_defines import WFC_MODE_CELLULAR_PREFERRED
-from acts.test_utils.tel.tel_defines import WFC_MODE_DISABLED
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_ONLY
-from acts.test_utils.tel.tel_defines import WFC_MODE_WIFI_PREFERRED
-from acts.test_utils.tel.tel_defines import WIFI_WEAK_RSSI_VALUE
-from acts.test_utils.tel.tel_defines import EventNetworkCallback
-from acts.test_utils.tel.tel_defines import NetworkCallbackAvailable
-from acts.test_utils.tel.tel_defines import NetworkCallbackLost
-from acts.test_utils.tel.tel_defines import SignalStrengthContainer
-from acts.test_utils.tel.tel_test_utils import wifi_toggle_state
-from acts.test_utils.tel.tel_test_utils import ensure_network_generation
-from acts.test_utils.tel.tel_test_utils import ensure_phones_default_state
-from acts.test_utils.tel.tel_test_utils import ensure_wifi_connected
-from acts.test_utils.tel.tel_test_utils import get_network_rat
-from acts.test_utils.tel.tel_test_utils import get_phone_number
-from acts.test_utils.tel.tel_test_utils import hangup_call
-from acts.test_utils.tel.tel_test_utils import initiate_call
-from acts.test_utils.tel.tel_test_utils import is_network_call_back_event_match
-from acts.test_utils.tel.tel_test_utils import is_phone_in_call
-from acts.test_utils.tel.tel_test_utils import is_phone_not_in_call
-from acts.test_utils.tel.tel_test_utils import set_wfc_mode
-from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode
-from acts.test_utils.tel.tel_test_utils import toggle_volte
-from acts.test_utils.tel.tel_test_utils import wait_and_answer_call
-from acts.test_utils.tel.tel_test_utils import wait_for_cell_data_connection
-from acts.test_utils.tel.tel_test_utils import wait_for_droid_not_in_call
-from acts.test_utils.tel.tel_test_utils import wait_for_wfc_disabled
-from acts.test_utils.tel.tel_test_utils import wait_for_wfc_enabled
-from acts.test_utils.tel.tel_test_utils import wait_for_wifi_data_connection
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import get_telephony_signal_strength
-from acts.test_utils.tel.tel_test_utils import get_wifi_signal_strength
-from acts.test_utils.tel.tel_test_utils import wait_for_state
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_3g
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_csfb
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_not_iwlan
-from acts.test_utils.tel.tel_voice_utils import is_phone_in_call_volte
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-from acts.test_utils.tel.tel_voice_utils import phone_idle_3g
-from acts.test_utils.tel.tel_voice_utils import phone_idle_csfb
-from acts.test_utils.tel.tel_voice_utils import phone_idle_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_idle_not_iwlan
-from acts.test_utils.tel.tel_voice_utils import phone_idle_volte
+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 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
+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 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 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_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 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_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 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
 
 # Attenuator name
 ATTEN_NAME_FOR_WIFI_2G = 'wifi0'
diff --git a/acts_tests/tests/google/tel/live/msim/TelLiveMSIMSmsTest.py b/acts_tests/tests/google/tel/live/msim/TelLiveMSIMSmsTest.py
index 336ae01..ad6e713 100644
--- a/acts_tests/tests/google/tel/live/msim/TelLiveMSIMSmsTest.py
+++ b/acts_tests/tests/google/tel/live/msim/TelLiveMSIMSmsTest.py
@@ -15,16 +15,16 @@
 #   limitations under the License.
 
 import time
-from acts.test_utils.tel.tel_test_utils \
+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.test_utils.tel.tel_subscription_utils \
+from acts_contrib.test_utils.tel.tel_subscription_utils \
               import get_subid_from_slot_index, set_subid_for_message
-from acts.test_utils.tel.tel_defines \
+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.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_voice_utils \
+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
 
 
diff --git a/acts_tests/tests/google/tel/live/msim/TelLiveMSIMVoiceTest.py b/acts_tests/tests/google/tel/live/msim/TelLiveMSIMVoiceTest.py
index f4cb7ca..501b0d6 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.test_utils.tel.tel_voice_utils \
+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.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_test_utils import multithread_func
-from acts.test_utils.tel.tel_defines import MULTI_SIM_CONFIG
+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
 
 
 class TelLiveMSIMVoiceTest(TelephonyBaseTest):
diff --git a/acts_tests/tests/google/usb/UsbTetheringFunctionsTest.py b/acts_tests/tests/google/usb/UsbTetheringFunctionsTest.py
index 3f0393b..14eb381 100644
--- a/acts_tests/tests/google/usb/UsbTetheringFunctionsTest.py
+++ b/acts_tests/tests/google/usb/UsbTetheringFunctionsTest.py
@@ -23,11 +23,11 @@
 from acts.libs.proc import job
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.tel import tel_test_utils as tutils
-from acts.test_utils.tel import tel_defines
-from acts.test_utils.tel.anritsu_utils import wait_for_sms_sent_success
-from acts.test_utils.tel.tel_defines import EventMmsSentSuccess
+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 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
 
 # Time it takes for the usb tethering IP to
 # show up in ifconfig and function waiting.
diff --git a/acts_tests/tests/google/wifi/WifiAutoJoinTest.py b/acts_tests/tests/google/wifi/WifiAutoJoinTest.py
index 829fc6c..2fde995 100755
--- a/acts_tests/tests/google/wifi/WifiAutoJoinTest.py
+++ b/acts_tests/tests/google/wifi/WifiAutoJoinTest.py
@@ -18,8 +18,8 @@
 
 from acts import asserts
 from acts import base_test
-from acts.test_utils.wifi import wifi_constants
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 WifiEnums = wutils.WifiEnums
 NETWORK_ID_ERROR = "Network don't have ID"
diff --git a/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py b/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
index 8e20c81..04cc45a 100755
--- a/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
+++ b/acts_tests/tests/google/wifi/WifiAutoUpdateTest.py
@@ -20,9 +20,9 @@
 from acts.libs.ota import ota_updater
 import acts.signals as signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-import acts.test_utils.wifi.wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.tel.tel_test_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
 
 WifiEnums = wutils.WifiEnums
diff --git a/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py b/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py
index f76331e..5b6ab41 100644
--- a/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py
+++ b/acts_tests/tests/google/wifi/WifiChannelSwitchStressTest.py
@@ -18,12 +18,12 @@
 import random
 import re
 import logging
-import acts.test_utils.wifi.wifi_test_utils as wutils
-import acts.test_utils.tel.tel_test_utils as tel_utils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.tel.tel_test_utils as tel_utils
 import acts.utils as utils
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts import signals
 from acts.controllers import packet_capture
 from acts.controllers.ap_lib.hostapd_constants import BAND_2G
diff --git a/acts_tests/tests/google/wifi/WifiChaosTest.py b/acts_tests/tests/google/wifi/WifiChaosTest.py
index d0a3722..f73ca6c 100755
--- a/acts_tests/tests/google/wifi/WifiChaosTest.py
+++ b/acts_tests/tests/google/wifi/WifiChaosTest.py
@@ -21,15 +21,15 @@
 
 import acts.controllers.packet_capture as packet_capture
 import acts.signals as signals
-import acts.test_utils.wifi.rpm_controller_utils as rutils
-import acts.test_utils.wifi.wifi_datastore_utils as dutils
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.rpm_controller_utils as rutils
+import acts_contrib.test_utils.wifi.wifi_datastore_utils as dutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 from acts import asserts
 from acts.base_test import BaseTestClass
 from acts.controllers.ap_lib import hostapd_constants
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py b/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
index bd10a8b..98061fd 100644
--- a/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
+++ b/acts_tests/tests/google/wifi/WifiConnectedMacRandomizationTest.py
@@ -21,13 +21,13 @@
 
 import acts.base_test
 import acts.signals as signals
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-import acts.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils as utils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 # Default timeout used for reboot, toggle WiFi and Airplane mode,
diff --git a/acts_tests/tests/google/wifi/WifiCrashStressTest.py b/acts_tests/tests/google/wifi/WifiCrashStressTest.py
index 837112a..5f61c63 100644
--- a/acts_tests/tests/google/wifi/WifiCrashStressTest.py
+++ b/acts_tests/tests/google/wifi/WifiCrashStressTest.py
@@ -16,12 +16,12 @@
 
 import time
 import acts.signals as signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 from acts import asserts
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from acts.test_utils.tel.tel_test_utils import disable_qxdm_logger
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.tel.tel_test_utils import disable_qxdm_logger
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiCrashTest.py b/acts_tests/tests/google/wifi/WifiCrashTest.py
index f76b1ed..86b5e95 100644
--- a/acts_tests/tests/google/wifi/WifiCrashTest.py
+++ b/acts_tests/tests/google/wifi/WifiCrashTest.py
@@ -21,12 +21,12 @@
 
 import acts.base_test
 import acts.signals as signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 # Timeout used for crash recovery.
diff --git a/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py b/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py
index 8ef24bd..d72e066 100644
--- a/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py
+++ b/acts_tests/tests/google/wifi/WifiDiagnosticsTest.py
@@ -21,12 +21,12 @@
 
 import acts.base_test
 import acts.signals as signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiDppTest.py b/acts_tests/tests/google/wifi/WifiDppTest.py
index 36d265e..6252cbf 100644
--- a/acts_tests/tests/google/wifi/WifiDppTest.py
+++ b/acts_tests/tests/google/wifi/WifiDppTest.py
@@ -21,10 +21,10 @@
 from acts import asserts
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_constants
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from acts.test_utils.wifi.aware import aware_test_utils as autils
+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
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
 
 class WifiDppTest(WifiBaseTest):
   """This class tests the DPP API surface.
diff --git a/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py b/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py
index 0cebd42..70de1f5 100644
--- a/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py
+++ b/acts_tests/tests/google/wifi/WifiEnterpriseRoamingTest.py
@@ -20,8 +20,8 @@
 from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiEnterpriseTest.py b/acts_tests/tests/google/wifi/WifiEnterpriseTest.py
index 20f5e9f..f3a88d9 100644
--- a/acts_tests/tests/google/wifi/WifiEnterpriseTest.py
+++ b/acts_tests/tests/google/wifi/WifiEnterpriseTest.py
@@ -21,10 +21,10 @@
 from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net.net_test_utils import start_tcpdump
-from acts.test_utils.net.net_test_utils import stop_tcpdump
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.net.net_test_utils import start_tcpdump
+from acts_contrib.test_utils.net.net_test_utils import stop_tcpdump
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py b/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
index a68144d..9dd73c3 100644
--- a/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
+++ b/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
@@ -21,12 +21,12 @@
 
 import acts.base_test
 import acts.signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 
 class WifiHiddenSSIDTest(WifiBaseTest):
diff --git a/acts_tests/tests/google/wifi/WifiIFSTwTest.py b/acts_tests/tests/google/wifi/WifiIFSTwTest.py
index 5b1603d..29a131d 100644
--- a/acts_tests/tests/google/wifi/WifiIFSTwTest.py
+++ b/acts_tests/tests/google/wifi/WifiIFSTwTest.py
@@ -25,10 +25,10 @@
 from acts.controllers import attenuator
 from acts.controllers.sl4a_lib import rpc_client
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net.net_test_utils import start_tcpdump, stop_tcpdump
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.wifi_test_utils import WifiEnums
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.net.net_test_utils import start_tcpdump, stop_tcpdump
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.wifi_test_utils import WifiEnums
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.utils import stop_standing_subprocess
 
 TCPDUMP_PATH = '/data/local/tmp/tcpdump'
diff --git a/acts_tests/tests/google/wifi/WifiIOTTest.py b/acts_tests/tests/google/wifi/WifiIOTTest.py
index 1daf346..bbf3a71 100644
--- a/acts_tests/tests/google/wifi/WifiIOTTest.py
+++ b/acts_tests/tests/google/wifi/WifiIOTTest.py
@@ -19,11 +19,11 @@
 import time
 
 import acts.signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiIOTTwPkg1Test.py b/acts_tests/tests/google/wifi/WifiIOTTwPkg1Test.py
index c6f8c3d..c300804 100644
--- a/acts_tests/tests/google/wifi/WifiIOTTwPkg1Test.py
+++ b/acts_tests/tests/google/wifi/WifiIOTTwPkg1Test.py
@@ -19,11 +19,11 @@
 import time
 
 import acts.signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.controllers import iperf_server as ipf
 
 import json
diff --git a/acts_tests/tests/google/wifi/WifiIOTtpeTest.py b/acts_tests/tests/google/wifi/WifiIOTtpeTest.py
index fd141ff..98729f2 100644
--- a/acts_tests/tests/google/wifi/WifiIOTtpeTest.py
+++ b/acts_tests/tests/google/wifi/WifiIOTtpeTest.py
@@ -19,11 +19,11 @@
 import time
 
 import acts.signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiLinkProbeTest.py b/acts_tests/tests/google/wifi/WifiLinkProbeTest.py
index 5f00e6c..b0312ae 100644
--- a/acts_tests/tests/google/wifi/WifiLinkProbeTest.py
+++ b/acts_tests/tests/google/wifi/WifiLinkProbeTest.py
@@ -18,8 +18,8 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-import acts.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 WifiEnums = wutils.WifiEnums
 NUM_LINK_PROBES = 8
diff --git a/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py b/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
index 64990d3..878a0b9 100644
--- a/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
+++ b/acts_tests/tests/google/wifi/WifiMacRandomizationTest.py
@@ -22,14 +22,14 @@
 
 import acts.base_test
 import acts.signals as signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from 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.wifi.WifiBaseTest import WifiBaseTest
 from scapy.all import *
 from acts.controllers.ap_lib import hostapd_constants
 
diff --git a/acts_tests/tests/google/wifi/WifiManagerTest.py b/acts_tests/tests/google/wifi/WifiManagerTest.py
index 9d7f8a0..0f6c902 100644
--- a/acts_tests/tests/google/wifi/WifiManagerTest.py
+++ b/acts_tests/tests/google/wifi/WifiManagerTest.py
@@ -21,14 +21,14 @@
 
 import acts.base_test
 import acts.signals as signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.bt_test_utils import enable_bluetooth
-from acts.test_utils.bt.bt_test_utils import disable_bluetooth
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import enable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 # Default timeout used for reboot, toggle WiFi and Airplane mode,
diff --git a/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py b/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py
index 7a1d8bc..b3bccf4 100644
--- a/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py
+++ b/acts_tests/tests/google/wifi/WifiNetworkRequestTest.py
@@ -21,14 +21,14 @@
 
 import acts.base_test
 import acts.signals as signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 
 from acts import asserts
 from acts.controllers.android_device import SL4A_APK_NAME
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from acts.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi import wifi_constants
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py b/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py
index 41f723c..a50b17b 100644
--- a/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py
+++ b/acts_tests/tests/google/wifi/WifiNetworkSelectorTest.py
@@ -24,8 +24,8 @@
 from acts.controllers import android_device
 from acts.controllers import attenuator
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py b/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py
index 3f989b5..c6cba50 100644
--- a/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py
+++ b/acts_tests/tests/google/wifi/WifiNetworkSuggestionTest.py
@@ -21,14 +21,14 @@
 
 import acts.base_test
 import acts.signals as signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 
 from acts import asserts
 from acts.controllers.android_device import SL4A_APK_NAME
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from acts.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi import wifi_constants
 
 WifiEnums = wutils.WifiEnums
 # EAP Macros
diff --git a/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py b/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py
index b5ba899..6be4e5c 100644
--- a/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py
+++ b/acts_tests/tests/google/wifi/WifiNewSetupAutoJoinTest.py
@@ -19,9 +19,9 @@
 from acts import asserts
 from acts import base_test
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_constants
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 NETWORK_ID_ERROR = "Network don't have ID"
diff --git a/acts_tests/tests/google/wifi/WifiPasspointTest.py b/acts_tests/tests/google/wifi/WifiPasspointTest.py
index 54c13d5..ff757bf 100755
--- a/acts_tests/tests/google/wifi/WifiPasspointTest.py
+++ b/acts_tests/tests/google/wifi/WifiPasspointTest.py
@@ -20,7 +20,7 @@
 import time
 
 import acts.base_test
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 
 from acts import asserts
@@ -28,8 +28,8 @@
 from acts.libs.uicd.uicd_cli import UicdCli
 from acts.libs.uicd.uicd_cli import UicdError
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.utils import force_airplane_mode
 
 WifiEnums = wutils.WifiEnums
diff --git a/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py b/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py
index 35f2966..27703d2 100644
--- a/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py
+++ b/acts_tests/tests/google/wifi/WifiPerformancePreflightTest.py
@@ -16,8 +16,8 @@
 
 from acts import base_test
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.test_utils.wifi import wifi_performance_test_utils as wputils
-from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
 
 
 class WifiPerformancePreflightTest(base_test.BaseTestClass):
diff --git a/acts_tests/tests/google/wifi/WifiPingTest.py b/acts_tests/tests/google/wifi/WifiPingTest.py
index 1df52d5..0fb3823 100644
--- a/acts_tests/tests/google/wifi/WifiPingTest.py
+++ b/acts_tests/tests/google/wifi/WifiPingTest.py
@@ -26,11 +26,11 @@
 from acts import utils
 from acts.controllers.utils_lib import ssh
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.test_utils.wifi import ota_chamber
-from acts.test_utils.wifi import ota_sniffer
-from acts.test_utils.wifi import wifi_performance_test_utils as wputils
-from acts.test_utils.wifi import wifi_retail_ap as retail_ap
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import ota_chamber
+from acts_contrib.test_utils.wifi import ota_sniffer
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from functools import partial
 
 
diff --git a/acts_tests/tests/google/wifi/WifiPnoTest.py b/acts_tests/tests/google/wifi/WifiPnoTest.py
index ba2eb4e..cbbf3e4 100644
--- a/acts_tests/tests/google/wifi/WifiPnoTest.py
+++ b/acts_tests/tests/google/wifi/WifiPnoTest.py
@@ -18,8 +18,8 @@
 from acts import asserts
 from acts import base_test
 from acts.test_decorators import test_tracker_info
-import acts.test_utils.wifi.wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 MAX_ATTN = 95
diff --git a/acts_tests/tests/google/wifi/WifiPreFlightTest.py b/acts_tests/tests/google/wifi/WifiPreFlightTest.py
index 14a4190..3d0db2d 100644
--- a/acts_tests/tests/google/wifi/WifiPreFlightTest.py
+++ b/acts_tests/tests/google/wifi/WifiPreFlightTest.py
@@ -19,11 +19,11 @@
 import time
 
 import acts.base_test
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 SCAN_TIME = 30
 WAIT_TIME = 2
diff --git a/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py b/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
index bfc96d5..e011866 100644
--- a/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
+++ b/acts_tests/tests/google/wifi/WifiRoamingPerformanceTest.py
@@ -26,9 +26,9 @@
 from acts.controllers import iperf_server as ipf
 from acts.controllers.utils_lib import ssh
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.test_utils.wifi import wifi_performance_test_utils as wputils
-from acts.test_utils.wifi import wifi_retail_ap as retail_ap
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 SHORT_SLEEP = 1
 MED_SLEEP = 5
diff --git a/acts_tests/tests/google/wifi/WifiRoamingTest.py b/acts_tests/tests/google/wifi/WifiRoamingTest.py
index b0f1a71..ebbc664 100644
--- a/acts_tests/tests/google/wifi/WifiRoamingTest.py
+++ b/acts_tests/tests/google/wifi/WifiRoamingTest.py
@@ -23,8 +23,8 @@
 from acts import base_test
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 DEF_ATTN = 60
diff --git a/acts_tests/tests/google/wifi/WifiRssiTest.py b/acts_tests/tests/google/wifi/WifiRssiTest.py
index 9ff9afa..3055985 100644
--- a/acts_tests/tests/google/wifi/WifiRssiTest.py
+++ b/acts_tests/tests/google/wifi/WifiRssiTest.py
@@ -29,10 +29,10 @@
 from acts.controllers.utils_lib import ssh
 from acts.controllers import iperf_server as ipf
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.test_utils.wifi import ota_chamber
-from acts.test_utils.wifi import wifi_performance_test_utils as wputils
-from acts.test_utils.wifi import wifi_retail_ap as retail_ap
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import ota_chamber
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from concurrent.futures import ThreadPoolExecutor
 from functools import partial
 
diff --git a/acts_tests/tests/google/wifi/WifiRttManagerTest.py b/acts_tests/tests/google/wifi/WifiRttManagerTest.py
index f0985de..4d558e6 100644
--- a/acts_tests/tests/google/wifi/WifiRttManagerTest.py
+++ b/acts_tests/tests/google/wifi/WifiRttManagerTest.py
@@ -18,7 +18,7 @@
 import queue
 
 import acts.base_test
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 from acts import asserts
 from acts.controllers.sl4a_lib import rpc_client
diff --git a/acts_tests/tests/google/wifi/WifiRvrTest.py b/acts_tests/tests/google/wifi/WifiRvrTest.py
index 979ac76..ae839df 100644
--- a/acts_tests/tests/google/wifi/WifiRvrTest.py
+++ b/acts_tests/tests/google/wifi/WifiRvrTest.py
@@ -27,11 +27,11 @@
 from acts.controllers import iperf_server as ipf
 from acts.controllers.utils_lib import ssh
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.test_utils.wifi import ota_chamber
-from acts.test_utils.wifi import ota_sniffer
-from acts.test_utils.wifi import wifi_performance_test_utils as wputils
-from acts.test_utils.wifi import wifi_retail_ap as retail_ap
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import ota_chamber
+from acts_contrib.test_utils.wifi import ota_sniffer
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from functools import partial
 
 
diff --git a/acts_tests/tests/google/wifi/WifiRvrTwTest.py b/acts_tests/tests/google/wifi/WifiRvrTwTest.py
index 2f9dc12..e732b83 100644
--- a/acts_tests/tests/google/wifi/WifiRvrTwTest.py
+++ b/acts_tests/tests/google/wifi/WifiRvrTwTest.py
@@ -19,11 +19,11 @@
 import time
 
 import acts.signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from acts.controllers import iperf_server as ipf
 
 import json
diff --git a/acts_tests/tests/google/wifi/WifiScannerBssidTest.py b/acts_tests/tests/google/wifi/WifiScannerBssidTest.py
index e91c449..dd75444 100644
--- a/acts_tests/tests/google/wifi/WifiScannerBssidTest.py
+++ b/acts_tests/tests/google/wifi/WifiScannerBssidTest.py
@@ -21,7 +21,7 @@
 from acts import base_test
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 BSSID_EVENT_WAIT = 30
 
diff --git a/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py b/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py
index aebcaf7..548c6eb 100755
--- a/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py
+++ b/acts_tests/tests/google/wifi/WifiScannerMultiScanTest.py
@@ -21,8 +21,8 @@
 from acts import base_test
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiChannelUS = wutils.WifiChannelUS
 WifiEnums = wutils.WifiEnums
diff --git a/acts_tests/tests/google/wifi/WifiScannerScanTest.py b/acts_tests/tests/google/wifi/WifiScannerScanTest.py
index 89ce0d6..4034f76 100755
--- a/acts_tests/tests/google/wifi/WifiScannerScanTest.py
+++ b/acts_tests/tests/google/wifi/WifiScannerScanTest.py
@@ -23,9 +23,9 @@
 from acts import base_test
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_constants
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_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
 
 SCANTIME = 10000  #framework support only 10s as minimum scan interval
 NUMBSSIDPERSCAN = 8
diff --git a/acts_tests/tests/google/wifi/WifiSensitivityTest.py b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
index 7307850..3172aaa 100644
--- a/acts_tests/tests/google/wifi/WifiSensitivityTest.py
+++ b/acts_tests/tests/google/wifi/WifiSensitivityTest.py
@@ -27,10 +27,10 @@
 from acts.controllers import iperf_client
 from acts.controllers.utils_lib import ssh
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.test_utils.wifi import ota_chamber
-from acts.test_utils.wifi import wifi_performance_test_utils as wputils
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import ota_chamber
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
 from functools import partial
 from WifiRvrTest import WifiRvrTest
 from WifiPingTest import WifiPingTest
diff --git a/acts_tests/tests/google/wifi/WifiServiceApiTest.py b/acts_tests/tests/google/wifi/WifiServiceApiTest.py
index b5eed89..f34f3eb 100644
--- a/acts_tests/tests/google/wifi/WifiServiceApiTest.py
+++ b/acts_tests/tests/google/wifi/WifiServiceApiTest.py
@@ -23,8 +23,8 @@
 from acts import signals
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_constants
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 
 
 class WifiServiceApiTest(base_test.BaseTestClass):
diff --git a/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py b/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
index 22d08b1..26bb862 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
@@ -22,15 +22,15 @@
 
 import acts.base_test
 import acts.signals as signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils as utils
 
 from acts import asserts
 from acts.controllers.ap_lib import hostapd_constants
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from 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.wifi.WifiBaseTest import WifiBaseTest
 from threading import Thread
 
 WifiEnums = wutils.WifiEnums
diff --git a/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py b/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
index 37a2ca8..841eeff 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApPerformanceTest.py
@@ -22,10 +22,10 @@
 from acts.controllers import iperf_server as ipf
 from acts.controllers import iperf_client as ipc
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.test_utils.wifi import ota_sniffer
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi import wifi_performance_test_utils as wputils
-from acts.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import ota_sniffer
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
 from WifiRvrTest import WifiRvrTest
 
 AccessPointTuple = collections.namedtuple(('AccessPointTuple'),
diff --git a/acts_tests/tests/google/wifi/WifiSoftApTest.py b/acts_tests/tests/google/wifi/WifiSoftApTest.py
index 7c7840d..073c98f 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApTest.py
@@ -22,16 +22,16 @@
 from acts import asserts
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import arduino_test_utils as dutils
-from acts.test_utils.net import socket_test_utils as sutils
-from acts.test_utils.tel import tel_defines
-from acts.test_utils.tel import tel_test_utils as tel_utils
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_AUTO
-from acts.test_utils.wifi import wifi_constants
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.net import arduino_test_utils as dutils
+from acts_contrib.test_utils.net import socket_test_utils as sutils
+from acts_contrib.test_utils.tel import tel_defines
+from acts_contrib.test_utils.tel import tel_test_utils as tel_utils
+from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_AUTO
+from acts_contrib.test_utils.wifi import wifi_constants
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py b/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py
index f84c06c..7a10f03 100755
--- a/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py
+++ b/acts_tests/tests/google/wifi/WifiStaApConcurrencyStressTest.py
@@ -21,10 +21,10 @@
 from acts import signals
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from 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 WifiStaApConcurrencyTest import WifiStaApConcurrencyTest
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py b/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
index d44b7dc..fd8e27f 100644
--- a/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
+++ b/acts_tests/tests/google/wifi/WifiStaApConcurrencyTest.py
@@ -23,12 +23,12 @@
 from acts.controllers.ap_lib import hostapd_constants
 import acts.signals as signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-import acts.test_utils.wifi.wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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
+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
-import acts.test_utils.tel.tel_test_utils as tel_utils
+import acts_contrib.test_utils.tel.tel_test_utils as tel_utils
 
 
 WifiEnums = wutils.WifiEnums
diff --git a/acts_tests/tests/google/wifi/WifiStressTest.py b/acts_tests/tests/google/wifi/WifiStressTest.py
index e31adbb..d02d78a 100644
--- a/acts_tests/tests/google/wifi/WifiStressTest.py
+++ b/acts_tests/tests/google/wifi/WifiStressTest.py
@@ -20,16 +20,16 @@
 import time
 
 import acts.base_test
-import acts.test_utils.wifi.wifi_test_utils as wutils
-import acts.test_utils.tel.tel_test_utils as tutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.tel.tel_test_utils as tutils
 
 from acts import asserts
 from acts import signals
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.bt.bt_test_utils import enable_bluetooth
-from acts.test_utils.bt.bt_test_utils import disable_bluetooth
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.bt.bt_test_utils import enable_bluetooth
+from acts_contrib.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 WifiEnums = wutils.WifiEnums
 
 WAIT_FOR_AUTO_CONNECT = 40
diff --git a/acts_tests/tests/google/wifi/WifiTeleCoexTest.py b/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
index 6d5fef8..9e93a9d 100644
--- a/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
+++ b/acts_tests/tests/google/wifi/WifiTeleCoexTest.py
@@ -4,16 +4,16 @@
 import time
 
 import acts.base_test
-import acts.test_utils.wifi.wifi_test_utils as wifi_utils
-import acts.test_utils.tel.tel_test_utils as tele_utils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wifi_utils
+import acts_contrib.test_utils.tel.tel_test_utils as tele_utils
 import acts.utils
 
 from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
-from acts.test_utils.tel.tel_voice_utils import phone_setup_voice_general
-from acts.test_utils.tel.tel_voice_utils import two_phone_call_short_seq
+from acts_contrib.test_utils.tel.TelephonyBaseTest import TelephonyBaseTest
+from acts_contrib.test_utils.tel.tel_voice_utils import phone_setup_voice_general
+from acts_contrib.test_utils.tel.tel_voice_utils import two_phone_call_short_seq
 
 WifiEnums = wifi_utils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py b/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py
index 0716158..0790546 100755
--- a/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py
+++ b/acts_tests/tests/google/wifi/WifiTethering2GOpenOTATest.py
@@ -20,10 +20,10 @@
 from acts.base_test import BaseTestClass
 from acts.libs.ota import ota_updater
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
 
-import acts.test_utils.net.net_test_utils as nutils
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.net.net_test_utils as nutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 
 class WifiTethering2GOpenOTATest(BaseTestClass):
diff --git a/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py b/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py
index 7399e32..c0b6d28 100755
--- a/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py
+++ b/acts_tests/tests/google/wifi/WifiTethering2GPskOTATest.py
@@ -20,10 +20,10 @@
 from acts.base_test import BaseTestClass
 from acts.libs.ota import ota_updater
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
+from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
 
-import acts.test_utils.net.net_test_utils as nutils
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.net.net_test_utils as nutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 
 class WifiTethering2GPskOTATest(BaseTestClass):
diff --git a/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py b/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py
index 985e7a7..12f6824 100755
--- a/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py
+++ b/acts_tests/tests/google/wifi/WifiTethering5GOpenOTATest.py
@@ -20,10 +20,10 @@
 from acts.base_test import BaseTestClass
 from acts.libs.ota import ota_updater
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
 
-import acts.test_utils.net.net_test_utils as nutils
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.net.net_test_utils as nutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 
 class WifiTethering5GOpenOTATest(BaseTestClass):
diff --git a/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py b/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py
index 9e68f22..de0f901 100755
--- a/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py
+++ b/acts_tests/tests/google/wifi/WifiTethering5GPskOTATest.py
@@ -20,10 +20,10 @@
 from acts.base_test import BaseTestClass
 from acts.libs.ota import ota_updater
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
+from acts_contrib.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
 
-import acts.test_utils.net.net_test_utils as nutils
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.net.net_test_utils as nutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 
 
 class WifiTethering5GPskOTATest(BaseTestClass):
diff --git a/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py b/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py
index dd3cf74..577e100 100644
--- a/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py
+++ b/acts_tests/tests/google/wifi/WifiTetheringPowerTest.py
@@ -23,11 +23,11 @@
 from acts.controllers import adb
 from acts.controllers import monsoon
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.tel import tel_data_utils as tel_utils
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.tel.tel_test_utils import http_file_download_by_chrome
+from acts_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.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 2ad29e4..8a70d11 100644
--- a/acts_tests/tests/google/wifi/WifiTetheringTest.py
+++ b/acts_tests/tests/google/wifi/WifiTetheringTest.py
@@ -24,16 +24,16 @@
 from acts import utils
 from acts.controllers import adb
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import arduino_test_utils as dutils
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.net import socket_test_utils as sutils
-from acts.test_utils.tel import tel_defines
-from acts.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.net import arduino_test_utils as dutils
+from acts_contrib.test_utils.net import net_test_utils as nutils
+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.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.wifi import wifi_test_utils as wutils
 
 
 WAIT_TIME = 5
diff --git a/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py b/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
index 71ed011..44b4686 100644
--- a/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
+++ b/acts_tests/tests/google/wifi/WifiThroughputStabilityTest.py
@@ -28,10 +28,10 @@
 from acts.controllers import iperf_server as ipf
 from acts.controllers.utils_lib import ssh
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.test_utils.wifi import ota_chamber
-from acts.test_utils.wifi import wifi_performance_test_utils as wputils
-from acts.test_utils.wifi import wifi_retail_ap as retail_ap
-from acts.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import ota_chamber
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
 from functools import partial
 
 TEST_TIMEOUT = 10
diff --git a/acts_tests/tests/google/wifi/WifiWakeTest.py b/acts_tests/tests/google/wifi/WifiWakeTest.py
index 7495863..f16f44f 100644
--- a/acts_tests/tests/google/wifi/WifiWakeTest.py
+++ b/acts_tests/tests/google/wifi/WifiWakeTest.py
@@ -20,8 +20,8 @@
 from acts import asserts
 from acts.controllers.android_device import SL4A_APK_NAME
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-import acts.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 
 WifiEnums = wutils.WifiEnums
diff --git a/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py b/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py
index 8e1395b..4c2639e 100644
--- a/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py
+++ b/acts_tests/tests/google/wifi/WifiWpa3EnterpriseTest.py
@@ -16,8 +16,8 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-import acts.test_utils.wifi.wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/WifiWpa3OweTest.py b/acts_tests/tests/google/wifi/WifiWpa3OweTest.py
index ab0f23d..2fc0356 100644
--- a/acts_tests/tests/google/wifi/WifiWpa3OweTest.py
+++ b/acts_tests/tests/google/wifi/WifiWpa3OweTest.py
@@ -21,13 +21,13 @@
 
 import acts.base_test
 import acts.signals as signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 
 from acts import asserts
 from acts.controllers.ap_lib import hostapd_constants
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
 
diff --git a/acts_tests/tests/google/wifi/aware/functional/AttachTest.py b/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
index 0df82a1..89a9bdc 100644
--- a/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/AttachTest.py
@@ -19,10 +19,10 @@
 from acts import asserts
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class AttachTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/functional/CapabilitiesTest.py b/acts_tests/tests/google/wifi/aware/functional/CapabilitiesTest.py
index 3bed77f..6f7c10d 100644
--- a/acts_tests/tests/google/wifi/aware/functional/CapabilitiesTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/CapabilitiesTest.py
@@ -17,10 +17,10 @@
 from acts import asserts
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const as cconsts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.net import connectivity_const as cconsts
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class CapabilitiesTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py b/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py
index b8ac693..145b1a6 100644
--- a/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/DataPathTest.py
@@ -18,12 +18,12 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const as cconsts
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.net import connectivity_const as cconsts
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class DataPathTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py b/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
index 31255af..6c4e20f 100644
--- a/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/DiscoveryTest.py
@@ -19,10 +19,10 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 
 class DiscoveryTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py b/acts_tests/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py
index 3898832..d800755 100644
--- a/acts_tests/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py
@@ -16,14 +16,14 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const as cconsts
-from acts.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.net import connectivity_const as cconsts
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
 from acts.controllers.ap_lib.hostapd_constants import BAND_2G
 from acts.controllers.ap_lib.hostapd_constants import BAND_5G
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 from scapy.all import *
 
 
diff --git a/acts_tests/tests/google/wifi/aware/functional/MacRandomTest.py b/acts_tests/tests/google/wifi/aware/functional/MacRandomTest.py
index 6c8d5b1..93bf346 100644
--- a/acts_tests/tests/google/wifi/aware/functional/MacRandomTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/MacRandomTest.py
@@ -18,10 +18,10 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const as cconsts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.net import connectivity_const as cconsts
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class MacRandomTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/functional/MatchFilterTest.py b/acts_tests/tests/google/wifi/aware/functional/MatchFilterTest.py
index e008e12..50eb710 100644
--- a/acts_tests/tests/google/wifi/aware/functional/MatchFilterTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/MatchFilterTest.py
@@ -20,9 +20,9 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class MatchFilterTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/functional/MessageTest.py b/acts_tests/tests/google/wifi/aware/functional/MessageTest.py
index 040f4e4..1e013c5 100644
--- a/acts_tests/tests/google/wifi/aware/functional/MessageTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/MessageTest.py
@@ -19,10 +19,10 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class MessageTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py b/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py
index 51f4cae..56a6547 100644
--- a/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/NonConcurrencyTest.py
@@ -20,11 +20,11 @@
 from acts import asserts
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi import wifi_constants as wconsts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_constants as wconsts
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 # arbitrary timeout for events
 EVENT_TIMEOUT = 10
diff --git a/acts_tests/tests/google/wifi/aware/functional/ProtocolsTest.py b/acts_tests/tests/google/wifi/aware/functional/ProtocolsTest.py
index 15e84ff..9bdbebb 100644
--- a/acts_tests/tests/google/wifi/aware/functional/ProtocolsTest.py
+++ b/acts_tests/tests/google/wifi/aware/functional/ProtocolsTest.py
@@ -16,10 +16,10 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import nsd_const as nconsts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.net import nsd_const as nconsts
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class ProtocolsTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/ota/ServiceIdsTest.py b/acts_tests/tests/google/wifi/aware/ota/ServiceIdsTest.py
index e29cd71..81d8a49 100644
--- a/acts_tests/tests/google/wifi/aware/ota/ServiceIdsTest.py
+++ b/acts_tests/tests/google/wifi/aware/ota/ServiceIdsTest.py
@@ -17,9 +17,9 @@
 import time
 
 from acts import asserts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class ServiceIdsTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py b/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py
index 8ebff89..871602b 100644
--- a/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py
+++ b/acts_tests/tests/google/wifi/aware/performance/LatencyTest.py
@@ -18,10 +18,10 @@
 import time
 
 from acts import asserts
-from acts.test_utils.net import connectivity_const as cconsts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.net import connectivity_const as cconsts
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class LatencyTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/performance/ThroughputTest.py b/acts_tests/tests/google/wifi/aware/performance/ThroughputTest.py
index 2dab276..7ab4506 100644
--- a/acts_tests/tests/google/wifi/aware/performance/ThroughputTest.py
+++ b/acts_tests/tests/google/wifi/aware/performance/ThroughputTest.py
@@ -21,10 +21,10 @@
 import time
 
 from acts import asserts
-from acts.test_utils.net import connectivity_const as cconsts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.net import connectivity_const as cconsts
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class ThroughputTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py b/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py
index d2e95df..73e2ffa 100644
--- a/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py
+++ b/acts_tests/tests/google/wifi/aware/stress/DataPathStressTest.py
@@ -19,10 +19,10 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const as cconsts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.net import connectivity_const as cconsts
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class DataPathStressTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/stress/DiscoveryStressTest.py b/acts_tests/tests/google/wifi/aware/stress/DiscoveryStressTest.py
index 55545ea..5643755 100644
--- a/acts_tests/tests/google/wifi/aware/stress/DiscoveryStressTest.py
+++ b/acts_tests/tests/google/wifi/aware/stress/DiscoveryStressTest.py
@@ -18,9 +18,9 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class DiscoveryStressTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/stress/InfraAssociationStressTest.py b/acts_tests/tests/google/wifi/aware/stress/InfraAssociationStressTest.py
index 58e2c84..b37c908 100644
--- a/acts_tests/tests/google/wifi/aware/stress/InfraAssociationStressTest.py
+++ b/acts_tests/tests/google/wifi/aware/stress/InfraAssociationStressTest.py
@@ -18,10 +18,10 @@
 import threading
 
 from acts import asserts
-from acts.test_utils.wifi import wifi_constants as wconsts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi import wifi_constants as wconsts
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 
 class InfraAssociationStressTest(AwareBaseTest):
diff --git a/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py b/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py
index 153a81f..2112782 100644
--- a/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py
+++ b/acts_tests/tests/google/wifi/aware/stress/MessagesStressTest.py
@@ -18,9 +18,9 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
 
 KEY_ID = "id"
 KEY_TX_OK_COUNT = "tx_ok_count"
diff --git a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pGroupTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pGroupTest.py
index dd27f21..9253db5 100644
--- a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pGroupTest.py
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pGroupTest.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 import time
 
@@ -22,9 +22,9 @@
 from acts import utils
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
-from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
-from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+from acts_contrib.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
 
 WPS_PBC = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC
 WPS_DISPLAY = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY
diff --git a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pLocalServiceTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pLocalServiceTest.py
index b043eb9..874e295 100644
--- a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pLocalServiceTest.py
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pLocalServiceTest.py
@@ -17,9 +17,9 @@
 import time
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
-from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
-from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+from acts_contrib.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
 
 
 class WifiP2pLocalServiceTest(WifiP2pBaseTest):
diff --git a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pManagerTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pManagerTest.py
index 6bda400..d3aeadf 100644
--- a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pManagerTest.py
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pManagerTest.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 import time
 
@@ -22,9 +22,9 @@
 from acts import utils
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
-from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
-from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+from acts_contrib.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
 
 WPS_PBC = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC
 WPS_DISPLAY = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY
diff --git a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pMultiPeersTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pMultiPeersTest.py
index 2951be6..df57b60 100644
--- a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pMultiPeersTest.py
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pMultiPeersTest.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 import time
 
@@ -22,10 +22,10 @@
 from acts import utils
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
-from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
-from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
 
 WPS_PBC = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_PBC
 WPS_DISPLAY = wp2putils.WifiP2PEnums.WpsInfo.WIFI_WPS_INFO_DISPLAY
diff --git a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py
index 2db5273..fa4f53f 100644
--- a/acts_tests/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py
+++ b/acts_tests/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import acts.test_utils.wifi.wifi_test_utils as wutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 import time
 import re
@@ -23,9 +23,9 @@
 from acts import utils
 
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
-from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
-from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+from acts_contrib.test_utils.wifi.p2p.WifiP2pBaseTest import WifiP2pBaseTest
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
 from acts.controllers.ap_lib.hostapd_constants import BAND_2G
 from scapy.all import *
 
diff --git a/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py b/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py
index da0ba4b..5f3f91b 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/AwareDiscoveryWithRangingTest.py
@@ -19,13 +19,13 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import connectivity_const as cconsts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
-from acts.test_utils.wifi.rtt import rtt_const as rconsts
-from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
-from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+from acts_contrib.test_utils.net import connectivity_const as cconsts
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.rtt import rtt_const as rconsts
+from acts_contrib.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
 
 
 class AwareDiscoveryWithRangingTest(AwareBaseTest, RttBaseTest):
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeApMiscTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeApMiscTest.py
index c68ca92..b21ef55 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RangeApMiscTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeApMiscTest.py
@@ -15,10 +15,10 @@
 #   limitations under the License.
 
 from acts import asserts
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.rtt import rtt_const as rconsts
-from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
-from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+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
+from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
 
 
 class RangeApMiscTest(RttBaseTest):
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py
index 1ffbb02..a963779 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeApNonSupporting11McTest.py
@@ -16,10 +16,10 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
-from acts.test_utils.wifi.rtt import rtt_const as rconsts
-from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
-from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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
+from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
 
 
 class RangeApNonSupporting11McTest(WifiBaseTest, RttBaseTest):
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py
index c218898..020f6e2 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeApSupporting11McTest.py
@@ -19,11 +19,11 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.rtt import rtt_const as rconsts
-from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
-from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+from acts_contrib.test_utils.tel.tel_test_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
+from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
 
 
 class RangeApSupporting11McTest(RttBaseTest):
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
index 2e53c6d..eebfecd 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeAwareTest.py
@@ -19,12 +19,12 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
-from acts.test_utils.wifi.rtt import rtt_const as rconsts
-from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
-from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.rtt import rtt_const as rconsts
+from acts_contrib.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
 
 
 class RangeAwareTest(AwareBaseTest, RttBaseTest):
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py b/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py
index c23b5b0..6f7d5fe 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RangeSoftApTest.py
@@ -16,11 +16,11 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.rtt import rtt_const as rconsts
-from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
-from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+from acts_contrib.test_utils.tel.tel_test_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
+from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
 
 
 class RangeSoftApTest(RttBaseTest):
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RttDisableTest.py b/acts_tests/tests/google/wifi/rtt/functional/RttDisableTest.py
index 635837c..94ebb01 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RttDisableTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RttDisableTest.py
@@ -17,10 +17,10 @@
 from acts import asserts
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.rtt import rtt_const as rconsts
-from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
-from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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
+from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 
 class RttDisableTest(WifiBaseTest, RttBaseTest):
diff --git a/acts_tests/tests/google/wifi/rtt/functional/RttRequestManagementTest.py b/acts_tests/tests/google/wifi/rtt/functional/RttRequestManagementTest.py
index c91521d..f7c1f91 100644
--- a/acts_tests/tests/google/wifi/rtt/functional/RttRequestManagementTest.py
+++ b/acts_tests/tests/google/wifi/rtt/functional/RttRequestManagementTest.py
@@ -19,9 +19,9 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi.rtt import rtt_const as rconsts
-from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
-from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+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
+from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
 
 
 class RttRequestManagementTest(RttBaseTest):
diff --git a/acts_tests/tests/google/wifi/rtt/stress/StressRangeApTest.py b/acts_tests/tests/google/wifi/rtt/stress/StressRangeApTest.py
index d0e9fe9..d88fd26 100644
--- a/acts_tests/tests/google/wifi/rtt/stress/StressRangeApTest.py
+++ b/acts_tests/tests/google/wifi/rtt/stress/StressRangeApTest.py
@@ -16,8 +16,8 @@
 
 from acts import asserts
 from acts.base_test import BaseTestClass
-from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
-from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+from acts_contrib.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
 
 
 class StressRangeApTest(RttBaseTest):
diff --git a/acts_tests/tests/google/wifi/rtt/stress/StressRangeAwareTest.py b/acts_tests/tests/google/wifi/rtt/stress/StressRangeAwareTest.py
index ccf8b9d..b03ab5b 100644
--- a/acts_tests/tests/google/wifi/rtt/stress/StressRangeAwareTest.py
+++ b/acts_tests/tests/google/wifi/rtt/stress/StressRangeAwareTest.py
@@ -18,12 +18,12 @@
 import time
 
 from acts import asserts
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
-from acts.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
-from acts.test_utils.wifi.rtt import rtt_const as rconsts
-from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
-from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi.aware.AwareBaseTest import AwareBaseTest
+from acts_contrib.test_utils.wifi.rtt import rtt_const as rconsts
+from acts_contrib.test_utils.wifi.rtt import rtt_test_utils as rutils
+from acts_contrib.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
 
 
 class StressRangeAwareTest(AwareBaseTest, RttBaseTest):