Migrate test_utils from acts to acts_contrib

This change will allow the ACTS framework to be packaged independently
of its test_utils. This facilitates the usage of ACTS within test suites
outside of tools/test/connectivity.

Re-submission of ag/13029169.
This reverts commit a4913cd4087bb09bf192de6ef819657aa6e082bd.

Reason for revert: Submit once references in acts_power are fixed.

Change-Id: I2d60f8ccaf936a80820a7b4387c23bbce1293dcf
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..a685b65
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/gnss/dut_log_test_utils.py
@@ -0,0 +1,177 @@
+#!/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 &"
+DEVICE_GPSLOG_FOLDER = '/sdcard/Android/data/com.android.gpstool/files/'
+
+
+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")
+
+
+def get_gpstool_logs(ad, local_logpath, keep_logs=True):
+    """
+
+    Pulls gpstool Logs from android device
+
+       Args:
+           ad: the target android device, AndroidDevice object
+           local_logpath: Local file path to pull the gpstool logs
+           keep_logs: False, delete log files from the gpstool log path
+    """
+
+    gps_log_path = os.path.join(local_logpath, 'GPSLogs')
+    ad.adb.pull("{} {}".format(DEVICE_GPSLOG_FOLDER, gps_log_path))
+    ad.log.debug("gpstool logs are pulled from device")
+
+    if not keep_logs:
+        ad.adb.shell("rm -rf " + DEVICE_GPSLOG_FOLDER + "*.*")
+        ad.log.debug("gpstool logs are deleted from device")
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/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/config_wrapper.py b/acts_tests/acts_contrib/test_utils/instrumentation/config_wrapper.py
new file mode 100644
index 0000000..0a6950e
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/config_wrapper.py
@@ -0,0 +1,171 @@
+#!/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 collections
+import copy
+
+from acts import context
+from acts.error import ActsError
+from acts.event import event_bus
+
+
+class InvalidParamError(ActsError):
+    """Raised when the desired parameter has an invalid value."""
+
+
+def _is_dict(o):
+    return isinstance(o, dict) or isinstance(o, collections.UserDict)
+
+
+class ConfigWrapper(collections.UserDict):
+    """Class representing a test or preparer config."""
+
+    def __init__(self, config=None):
+        """Initialize a ConfigWrapper
+
+        Args:
+            config: A dict representing the preparer/test parameters
+        """
+        if config is None:
+            config = {}
+        super().__init__(
+            {
+                key: (ConfigWrapper(copy.deepcopy(val))
+                      if _is_dict(val) else val)
+                for key, val in config.items()
+            }
+        )
+
+    def get(self, param_name, default=None, verify_fn=lambda _: True,
+            failure_msg=''):
+        """Get parameter from config, verifying that the value is valid
+        with verify_fn.
+
+        Args:
+            param_name: Name of the param to fetch
+            default: Default value of param.
+            verify_fn: Callable to verify the param value. If it returns False,
+                an exception will be raised.
+            failure_msg: Exception message upon verify_fn failure.
+        """
+        result = self.data.get(param_name, default)
+        if not verify_fn(result):
+            raise InvalidParamError('Invalid value "%s" for param %s. %s'
+                                    % (result, param_name, failure_msg))
+        return result
+
+    def get_config(self, param_name):
+        """Get a sub-config from config. Returns an empty ConfigWrapper if no
+        such sub-config is found.
+        """
+        return ConfigWrapper(copy.deepcopy(self.get(param_name, default={})))
+
+    def get_int(self, param_name, default=0):
+        """Get integer parameter from config. Will raise an exception
+        if result is not of type int.
+        """
+        return self.get(param_name, default=default,
+                        verify_fn=lambda val: type(val) is int,
+                        failure_msg='Param must be of type int.')
+
+    def get_numeric(self, param_name, default=0):
+        """Get int or float parameter from config. Will raise an exception if
+        result is not of type int or float.
+        """
+        return self.get(param_name, default=default,
+                        verify_fn=lambda val: type(val) in (int, float),
+                        failure_msg='Param must be of type int or float.')
+
+
+def _for_current_context(config_wrapper):
+    current_context = context.get_current_context()
+    test_class_name = None
+    test_case_name = None
+
+    if isinstance(current_context, context.TestClassContext):
+        test_class_name = current_context.test_class_name
+    if isinstance(current_context, context.TestCaseContext):
+        test_class_name = current_context.test_class_name
+        test_case_name = current_context.test_case_name
+
+    class_config = config_wrapper.get_config(test_class_name)
+    test_config = class_config.get_config(test_case_name)
+
+    base_config_without_classes = {
+        k: v for (k, v) in config_wrapper.items() if not k.endswith('Test')
+    }
+
+    class_config_without_test_cases = {
+        k: v for (k, v) in class_config.items() if not k.startswith('test_')
+    }
+
+    result = merge(class_config_without_test_cases, test_config)
+    result = merge(base_config_without_classes, result)
+    return result
+
+
+class ContextualConfigWrapper(ConfigWrapper):
+    """An object ala ConfigWrapper that automatically restricts to the context
+    relevant portion of the original configuration.
+    """
+
+    def __init__(self, config=None):
+        """Instantiates a ContextualConfigWrapper.
+
+        Args:
+            config: A dict or collections.UserDict.
+        """
+        self._registration_for_context_change = None
+        self.original_config = ConfigWrapper(config)
+
+        def updater(_):
+            self.data = dict(_for_current_context(self.original_config))
+
+        self._registration_for_context_change = event_bus.register(
+            context.NewContextEvent, updater)
+        super().__init__(dict(_for_current_context(self.original_config)))
+
+    def __del__(self):
+        if self._registration_for_context_change is not None:
+            event_bus.unregister(self._registration_for_context_change)
+
+
+def merge(config_a, config_b):
+    """Merges dic_b into dic_a
+
+    Recursively updates the fields of config_a with the value that comes from
+    config_b.
+
+    For example:
+    config_a = {'dic': {'a': 0, 'c': 3, 'sub': {'x': 1}}}
+    config_b = {'dic': {'a': 2, 'b': 2, 'sub': {'y': 2}}}
+
+    would result in
+    {'dic': {'a': 2, 'b': 2, 'c': 3, 'sub': {'x': 1, 'y': 2}}}
+
+    Args:
+         config_a: A ConfigWrapper
+         config_b: A ConfigWrapper
+    Return:
+        A ConfigWrapper.
+    """
+    res = collections.UserDict(config_a)
+    for (key, value) in config_b.items():
+        if key in res and _is_dict(res[key]):
+            res[key] = merge(res[key], value)
+        else:
+            res[key] = copy.deepcopy(value)
+    return ConfigWrapper(res)
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/apps/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/app_installer.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/app_installer.py
new file mode 100644
index 0000000..f5aa818
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/app_installer.py
@@ -0,0 +1,130 @@
+#!/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
+
+from acts.error import ActsError
+from acts.libs.proc import job
+
+PKG_NAME_PATTERN = r"^package:\s+name='(?P<pkg_name>.*?)'"
+PM_PATH_PATTERN = r"^package:(?P<apk_path>.*)"
+
+
+class AppInstallerError(ActsError):
+    """Exception class for AppInstaller's errors."""
+
+
+class AppInstaller(object):
+    """Class that represents an app on an Android device. Includes methods
+    for install, uninstall, and getting info.
+    """
+    def __init__(self, ad, apk_path):
+        """Initializes an AppInstaller.
+
+        Args:
+            ad: device to install the apk
+            apk_path: path to the apk
+        """
+        self._ad = ad
+        self._apk_path = apk_path
+        self._pkg_name = None
+
+    @staticmethod
+    def pull_from_device(ad, pkg_name, dest):
+        """Initializes an AppInstaller by pulling the apk file from the device,
+        given the package name
+
+        Args:
+            ad: device on which the apk is installed
+            pkg_name: package name
+            dest: destination directory
+                (Note: If path represents a directory, it must already exist as
+                 a directory)
+
+        Returns: AppInstaller object representing the pulled apk, or None if
+            package not installed
+        """
+        if not ad.is_apk_installed(pkg_name):
+            ad.log.warning('Unable to find package %s on device. Pull aborted.'
+                           % pkg_name)
+            return None
+        path_on_device = re.compile(PM_PATH_PATTERN).search(
+            ad.adb.shell('pm path %s' % pkg_name)).group('apk_path')
+        ad.pull_files(path_on_device, dest)
+        if os.path.isdir(dest):
+            dest = os.path.join(dest, os.path.basename(path_on_device))
+        return AppInstaller(ad, dest)
+
+    @property
+    def apk_path(self):
+        return self._apk_path
+
+    @property
+    def pkg_name(self):
+        """Get the package name corresponding to the apk from aapt
+
+        Returns: The package name, or empty string if not found.
+        """
+        try:
+            job.run('which aapt')
+        except job.Error:
+            raise AppInstallerError('aapt not found or is not executable. Make '
+                                    'sure aapt is reachable from PATH and'
+                                    'executable')
+
+        if self._pkg_name is None:
+            dump = job.run(
+                'aapt dump badging %s' % self.apk_path).stdout
+            match = re.compile(PKG_NAME_PATTERN).search(dump)
+            self._pkg_name = match.group('pkg_name') if match else ''
+        return self._pkg_name
+
+    def install(self, *extra_args):
+        """Installs the apk on the device.
+
+        Args:
+            extra_args: Additional flags to the ADB install command.
+                Note that '-r' is included by default.
+        """
+        self._ad.log.info('Installing app %s from %s' %
+                          (self.pkg_name, self.apk_path))
+        args = '-r %s' % ' '.join(extra_args)
+        self._ad.adb.install('%s %s' % (args, self.apk_path))
+
+    def uninstall(self, *extra_args):
+        """Uninstalls the apk from the device.
+
+        Args:
+            extra_args: Additional flags to the uninstall command.
+        """
+        self._ad.log.info('Uninstalling app %s' % self.pkg_name)
+        if not self.is_installed():
+            self._ad.log.warning('Unable to uninstall app %s. App is not '
+                                 'installed.' % self.pkg_name)
+            return
+        self._ad.adb.shell(
+            'pm uninstall %s %s' % (' '.join(extra_args), self.pkg_name))
+
+    def is_installed(self):
+        """Verifies that the apk is installed on the device.
+
+        Returns: True if the apk is installed on the device.
+        """
+        if not self.pkg_name:
+            self._ad.log.warning('No package name found for %s' % self.apk_path)
+            return False
+        return self._ad.is_apk_installed(self.pkg_name)
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/dismiss_dialogs.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/dismiss_dialogs.py
new file mode 100644
index 0000000..72f240b
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/dismiss_dialogs.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import \
+    AppInstaller
+from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder \
+    import InstrumentationCommandBuilder
+
+DISMISS_DIALOGS_RUNNER = '.DismissDialogsInstrumentation'
+SCREENSHOTS_DIR = 'dialog-dismissal'
+DISMISS_DIALOGS_TIMEOUT = 300
+
+
+class DialogDismissalUtil(object):
+    """Utility for dismissing app dialogs."""
+    def __init__(self, dut, util_apk):
+        self._dut = dut
+        self._dismiss_dialogs_apk = AppInstaller(dut, util_apk)
+        self._dismiss_dialogs_apk.install('-g')
+
+    def dismiss_dialogs(self, apps, screenshots=True, quit_on_error=True):
+        """Dismiss dialogs for the given apps.
+
+        Args:
+            apps: List of apps to dismiss dialogs
+            screenshots: Boolean to enable screenshots upon dialog dismissal
+            quit_on_error: Boolean to indicate if tool should quit on failure
+        """
+        if not apps:
+            return
+        if not isinstance(apps, list):
+            apps = [apps]
+        self._dut.log.info('Dismissing app dialogs for %s' % apps)
+        cmd_builder = InstrumentationCommandBuilder()
+        cmd_builder.set_manifest_package(self._dismiss_dialogs_apk.pkg_name)
+        cmd_builder.set_runner(DISMISS_DIALOGS_RUNNER)
+        cmd_builder.add_flag('-w')
+        cmd_builder.add_key_value_param('apps', ','.join(apps))
+        cmd_builder.add_key_value_param('screenshots', screenshots)
+        cmd_builder.add_key_value_param('quitOnError', quit_on_error)
+        self._dut.adb.shell(cmd_builder.build(),
+                            timeout=DISMISS_DIALOGS_TIMEOUT)
+
+        # Pull screenshots if screenshots=True
+        if screenshots:
+            self._dut.pull_files(
+                os.path.join(self._dut.external_storage_path, SCREENSHOTS_DIR),
+                self._dut.device_log_path
+            )
+
+    def close(self):
+        """Clean up util by uninstalling the dialog dismissal APK."""
+        self._dismiss_dialogs_apk.uninstall()
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/google_account_util.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/google_account_util.py
new file mode 100644
index 0000000..a0ba36a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/google_account_util.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import \
+  AppInstaller
+
+class GoogleAccountUtil(object):
+  """Class to act as a Google account utility """
+  def __init__(self, dut, util_apk):
+    self._dut = dut
+    self._google_account_util_apk = AppInstaller(dut, util_apk)
+    self._google_account_util_apk.install()
+
+  def close(self):
+    """Clean up util by uninstalling the Google account util APK."""
+    self._google_account_util_apk.uninstall()
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/google_apps_test_utils.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/google_apps_test_utils.py
new file mode 100644
index 0000000..3f0733f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/google_apps_test_utils.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import \
+    AppInstaller
+from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder \
+    import InstrumentationCommandBuilder
+
+ACTIVITY = '.FinskyInstrumentation'
+
+
+class GoogleAppsTestUtils(object):
+    """Utility for managing operations regarding the GoogleAppsTestUtils.apk."""
+    def __init__(self, dut, util_apk):
+        self._dut = dut
+        self._google_apps_test_utils_apk = AppInstaller(dut, util_apk)
+
+    def prevent_playstore_auto_updates(self):
+        """Prevents the playstore from auto updating."""
+        self._dut.log.info('Preventing playstore from auto updating.')
+        if not self._google_apps_test_utils_apk.is_installed():
+            self._google_apps_test_utils_apk.install('-g')
+
+        cmd_builder = InstrumentationCommandBuilder()
+        cmd_builder.set_manifest_package(self._google_apps_test_utils_apk.pkg_name)
+        cmd_builder.set_runner(ACTIVITY)
+        cmd_builder.add_flag('-w')
+        cmd_builder.add_flag('-r')
+        cmd_builder.add_key_value_param('command', 'auto_update')
+        cmd_builder.add_key_value_param('value', 'false')
+        self._dut.adb.shell(cmd_builder.build())
+
+    def close(self):
+        """Clean up util by uninstalling the APK."""
+        self._google_apps_test_utils_apk.uninstall()
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/hotword_model_extractor.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/hotword_model_extractor.py
new file mode 100644
index 0000000..b7772af
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/hotword_model_extractor.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import tempfile
+import zipfile
+
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer \
+    import AppInstaller
+
+DEFAULT_MODEL_NAME = 'en_us.mmap'
+MODEL_DIR = 'res/raw'
+
+
+class HotwordModelExtractor(object):
+    """
+    Extracts a voice model data file from the Hotword APK and pushes it
+    onto the device.
+    """
+    def __init__(self, dut):
+        self._dut = dut
+
+    def extract_to_dut(self, hotword_pkg, model_name=DEFAULT_MODEL_NAME):
+        with tempfile.TemporaryDirectory() as tmp_dir:
+            extracted_model = self._extract(hotword_pkg, model_name, tmp_dir)
+            if not extracted_model:
+                return
+            device_dir = self._dut.adb.shell('echo $EXTERNAL_STORAGE')
+            self._dut.adb.push(
+                extracted_model, os.path.join(device_dir, model_name))
+
+    def _extract(self, hotword_pkg, model_name, dest):
+        """Extracts the model file from the given Hotword APK.
+
+        Args:
+            hotword_pkg: Package name of the Hotword APK
+            model_name: File name of the model file.
+            dest: Destination directory
+
+        Returns: Full path to the extracted model file.
+        """
+        self._dut.log.info('Extracting voice model from Hotword APK.')
+        hotword_apk = AppInstaller.pull_from_device(
+            self._dut, hotword_pkg, dest)
+        if not hotword_apk:
+            self._dut.log.warning('Cannot extract Hotword voice model: '
+                                  'Hotword APK not installed.')
+            return None
+
+        model_rel_path = os.path.join(MODEL_DIR, model_name)
+        with zipfile.ZipFile(hotword_apk.apk_path) as hotword_zip:
+            try:
+                return hotword_zip.extract(model_rel_path, dest)
+            except KeyError:
+                self._dut.log.warning(
+                    'Cannot extract Hotword voice model: Model file %s not '
+                    'found.' % model_rel_path)
+                return None
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/modem_diag_util.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/modem_diag_util.py
new file mode 100644
index 0000000..cf558c3
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/modem_diag_util.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import \
+  AppInstaller
+
+class ModemDiagUtil(object):
+  """Class to act as a Modem Diagnostic utility """
+  def __init__(self, dut, util_apk):
+    self._dut = dut
+    self._modem_diag_util_apk = AppInstaller(dut, util_apk)
+    self._modem_diag_util_apk.install()
+
+  def cut_band(self, band_to_cut):
+    if band_to_cut == 'Band13':
+        self._dut.adb.shell("am instrument -w -e request '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' com.google.mdstest/com.google.mdstest.instrument.ModemCommandInstrumentation")
+    elif band_to_cut == 'Band4':
+        self._dut.adb.shell("am instrument -w -e request '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' com.google.mdstest/com.google.mdstest.instrument.ModemCommandInstrumentation")
+
+  def close(self):
+    """Clean up util by uninstalling the Google account util APK."""
+    self._modem_diag_util_apk.uninstall()
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/permissions.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/permissions.py
new file mode 100644
index 0000000..33f7f58
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/apps/permissions.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import \
+    AppInstaller
+from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder \
+    import InstrumentationCommandBuilder
+
+PERMISSION_RUNNER = '.PermissionInstrumentation'
+
+
+class PermissionsUtil(object):
+    """Utility for granting all revoked runtime permissions."""
+    def __init__(self, dut, util_apk):
+        self._dut = dut
+        self._permissions_apk = AppInstaller(dut, util_apk)
+        self._permissions_apk.install()
+
+    def grant_all(self):
+        """Grant all runtime permissions with PermissionUtils."""
+        if not self._permissions_apk.is_installed():
+            self._permissions_apk.install('-g')
+        self._dut.log.info('Granting all revoked runtime permissions.')
+        cmd_builder = InstrumentationCommandBuilder()
+        cmd_builder.set_manifest_package(self._permissions_apk.pkg_name)
+        cmd_builder.set_runner(PERMISSION_RUNNER)
+        cmd_builder.add_flag('-w')
+        cmd_builder.add_flag('-r')
+        cmd_builder.add_key_value_param('command', 'grant-all')
+        self._dut.adb.shell(cmd_builder.build())
+
+    def close(self):
+        """Clean up util by uninstalling the permissions APK."""
+        self._permissions_apk.uninstall()
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/adb_command_types.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_command_types.py
new file mode 100644
index 0000000..c65d8a8
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_command_types.py
@@ -0,0 +1,177 @@
+#!/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 tracelogger
+
+from acts_contrib.test_utils.instrumentation.device.command.intent_builder import IntentBuilder
+
+DESCRIPTION_MISSING_MESSAGE = (
+    'Description for command is mandatory. Provide a brief description on what '
+    'the command "%s" does. Preferably in third person, for example, instead '
+    'of "Turn torch on" you should write "Turns torch on". If a description is '
+    'too long and adding a link would help, adding a link is allowed. Make '
+    'sure that the link is to a specific/fixed version of a document (either '
+    'website or code) so that it doesn\'t lose context over time.')
+
+log = tracelogger.TraceLogger(logging.getLogger())
+
+
+class GenericCommand(object):
+    """Class for generic adb commands."""
+
+    def __init__(self, cmd, desc=None):
+        """ Constructor for GenericCommand.
+
+        Args:
+          cmd: ADB command.
+          desc: Free form string to describe what this command does.
+        """
+        if not cmd:
+            raise ValueError('Command can not be left undefined.')
+
+        if not desc:
+            log.warning(DESCRIPTION_MISSING_MESSAGE % cmd)
+
+        self.cmd = cmd
+        self.desc = desc
+
+
+class DeviceState(object):
+    """Class for adb commands for setting device properties to a value."""
+
+    def __init__(self, base_cmd, on_val='1', off_val='0', desc=None):
+        """Create a DeviceState.
+
+        Args:
+            base_cmd: The base adb command. Needs to accept an argument/value to
+                generate the full command.
+            on_val: Value used for the 'on' state
+            off_val: Value used for the 'off' state
+            desc: Free form string to describes what is this command does.
+        """
+        if not desc:
+            log.warning(DESCRIPTION_MISSING_MESSAGE % base_cmd)
+
+        self._base_cmd = base_cmd
+        self._on_val = on_val
+        self._off_val = off_val
+        self.desc = desc
+
+    def set_value(self, *values):
+        """Returns the adb command with the given arguments/values.
+
+        Args:
+            values: The value(s) to run the command with
+        """
+        try:
+            cmd = self._base_cmd % values
+        except TypeError:
+            cmd = str.strip(' '.join(
+                [self._base_cmd] + [str(value) for value in values]))
+        return GenericCommand(cmd, self.desc)
+
+    def toggle(self, enabled):
+        """Returns the command corresponding to the desired state.
+
+        Args:
+            enabled: True for the 'on' state.
+        """
+        return self.set_value(self._on_val if enabled else self._off_val)
+
+
+class DeviceSetprop(DeviceState):
+    """Class for setprop commands."""
+
+    def __init__(self, prop, on_val='1', off_val='0', desc=None):
+        """Create a DeviceSetprop.
+
+        Args:
+            prop: Property name
+            on_val: Value used for the 'on' state
+            off_val: Value used for the 'off' state
+            desc: Free form string to describes what is this command does.
+        """
+        super().__init__('setprop %s' % prop, on_val=on_val, off_val=off_val,
+                         desc=desc)
+
+
+class DeviceSetting(DeviceState):
+    """Class for commands to set a settings.db entry to a value."""
+
+    # common namespaces
+    GLOBAL = 'global'
+    SYSTEM = 'system'
+    SECURE = 'secure'
+
+    def __init__(self, namespace, setting, on_val='1', off_val='0', desc=None):
+        """Create a DeviceSetting.
+
+        Args:
+            namespace: Namespace of the setting
+            setting: Setting name
+            on_val: Value used for the 'on' state
+            off_val: Value used for the 'off' state
+            desc: Free form string to describes what is this command does.
+        """
+        super().__init__('settings put %s %s' % (namespace, setting),
+                         on_val=on_val, off_val=off_val, desc=desc)
+
+
+class DeviceGServices(DeviceState):
+    """Class for overriding a GServices value."""
+
+    OVERRIDE_GSERVICES_INTENT = ('com.google.gservices.intent.action.'
+                                 'GSERVICES_OVERRIDE')
+
+    def __init__(self, setting, on_val='true', off_val='false', desc=None):
+        """Create a DeviceGServices.
+
+        Args:
+            setting: Name of the GServices setting
+            on_val: Value used for the 'on' state
+            off_val: Value used for the 'off' state
+            desc: Free form string to describes what is this command does.
+        """
+        super().__init__(None, on_val=on_val, off_val=off_val, desc=desc)
+        self._intent_builder = IntentBuilder('am broadcast')
+        self._intent_builder.set_action(self.OVERRIDE_GSERVICES_INTENT)
+        self._setting = setting
+
+    def set_value(self, value):
+        """Returns the adb command with the given value."""
+        self._intent_builder.add_key_value_param(self._setting, value)
+        return GenericCommand(self._intent_builder.build(), desc=self.desc)
+
+
+class DeviceBinaryCommandSeries(object):
+    """Class for toggling multiple settings at once."""
+
+    def __init__(self, binary_commands):
+        """Create a DeviceBinaryCommandSeries.
+
+        Args:
+            binary_commands: List of commands for setting toggleable options
+        """
+        self.cmd_list = binary_commands
+
+    def toggle(self, enabled):
+        """Returns the list of command corresponding to the desired state.
+
+        Args:
+            enabled: True for the 'on' state.
+        """
+        return [cmd.toggle(enabled) for cmd in self.cmd_list]
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_commands/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_commands/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_commands/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_commands/common.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_commands/common.py
new file mode 100644
index 0000000..677eaf7
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_commands/common.py
@@ -0,0 +1,396 @@
+#!/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.
+"""Common device settings for power testing."""
+
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  DeviceBinaryCommandSeries
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  DeviceSetprop
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  DeviceSetting
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  DeviceState
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  GenericCommand
+
+# Network/Connectivity
+
+airplane_mode = DeviceBinaryCommandSeries([
+    DeviceSetting(DeviceSetting.GLOBAL, 'airplane_mode_on',
+                  desc='Modifies the property that indicates whether '
+                       'airplane mode is enabled. This command is always '
+                       'used together with an activity manager broadcast.'),
+    DeviceState(
+        'am broadcast -a android.intent.action.AIRPLANE_MODE --ez state',
+        on_val='true',
+        off_val='false',
+        desc='Modifies the airplane mode state. This is always done '
+             'after setting the airplane_mode_on global property.')
+])
+
+mobile_data = DeviceBinaryCommandSeries([
+    DeviceSetting(
+        DeviceSetting.GLOBAL,
+        'mobile_data',
+        desc='Modifies the property that indicates whether mobile data is '
+             'enabled. This is used always together with an svc data '
+             'command.'),
+    DeviceState(
+        'svc data',
+        on_val='enable',
+        off_val='disable',
+        desc='Modifies the mobile data state. This is always done'
+             'after setting the mobile_data global property.')
+])
+
+cellular = DeviceSetting(
+    DeviceSetting.GLOBAL,
+    'cell_on',
+    desc='Modifies whether to enable the cellular radio.')
+
+preferred_network_mode = DeviceSetting(
+    DeviceSetting.GLOBAL, 'preferred_network_mode',
+    desc='Sets the preferred network (lte/3g).')
+
+wifi_global = DeviceSetting(
+    DeviceSetting.GLOBAL,
+    'wifi_on',
+    desc='Modifies the property that indicates whether wifi '
+         'is enabled. This is always used together with an'
+         'svc wifi command.')
+
+wifi_scan_always_enabled = DeviceSetting(
+    DeviceSetting.GLOBAL,
+    'wifi_scan_always_enabled',
+    desc='Modifies whether to enable the wifi scan always.')
+
+wifi_state = DeviceState('svc wifi', on_val='enable', off_val='disable',
+                         desc='Modifies the wifi state. This is always done after'
+                              'setting the wifi_on global property.')
+
+ethernet = DeviceState(
+    'ifconfig eth0',
+    on_val='up',
+    off_val='down',
+    desc='Modifies whether to enable ethernet.')
+
+bluetooth = DeviceState(
+    'service call bluetooth_manager',
+    on_val='6',
+    off_val='8',
+    desc='Modifies whether bluetooth is enabled (6 means enabled, 8 disabled).'
+         'TODO: add the source for these magic numbers. BluetoothAdapter '
+         'http://shortn/_FTBWhfJJs7 makes reference to these enums '
+         'http://shortn/_w9rcHX8jm4, but that doesn\'t seem to be the right '
+         'source.')
+
+nfc = DeviceState(
+    'svc nfc',
+    on_val='enable',
+    off_val='disable',
+    desc='Modifies whether to enable nfc.')
+
+disable_modem = GenericCommand(
+    'pm disable com.google.android.apps.scone', desc='Disables modem service.')
+
+mobile_network_settings = GenericCommand('am start -n com.android.phone/'
+                                         '.MobileNetworkSettings',
+                                         desc='Opens network settings')
+
+# Calling
+
+disable_dialing = DeviceSetprop(
+    'ro.telephony.disable-call',
+    on_val='true',
+    off_val='false',
+    desc='Modifies whether to allow voice calls.')
+
+# Screen
+
+screen_adaptive_brightness = DeviceSetting(
+    DeviceSetting.SYSTEM,
+    'screen_brightness_mode',
+    desc='Modifies whether the adaptive brightness feature is enabled. Differs '
+         'from ambient EQ modifies the color balance and this feature'
+         'modifies the screen brightness. '
+         'https://support.google.com/android/answer/9084191?hl=en '
+         'http://shortn/_ptmpx4wuVW')
+
+screen_brightness = DeviceSetting(
+    DeviceSetting.SYSTEM,
+    'screen_brightness',
+    desc='Sets the brightness level.')
+
+screen_always_on = DeviceState(
+    'svc power stayon',
+    on_val='true',
+    off_val='false',
+    desc='Modifies whether the device should stay on while connected. '
+         'http://shortn/_0DB29fy5HL')
+
+screen_timeout_ms = DeviceSetting(
+    DeviceSetting.SYSTEM, 'screen_off_timeout',
+    desc='Sets the time to wait before turning the screen off.')
+
+disable_doze = GenericCommand('dumpsys deviceidle disable',
+                              desc='Disables device from going into doze mode')
+
+doze_mode = DeviceSetting(
+    DeviceSetting.SECURE,
+    'doze_enabled',
+    desc='Modifies whether showing notifications in ambient (mostly dark) mode '
+         'is enabled.')
+
+doze_always_on = DeviceSetting(
+    DeviceSetting.SECURE,
+    'doze_always_on',
+    desc='Modifies whether ambient mode (mostly dark) is enabled. Ambient '
+         'mode is the one where the device shows the time all the time.')
+
+# Gestures
+doze_pulse_on_pick_up = DeviceSetting(
+    DeviceSetting.SECURE,
+    'doze_pulse_on_pick_up',
+    desc='Modifies whether to enable gesture to wake up device when picked up.')
+
+camera_double_tap_power_gesture_disabled = DeviceSetting(
+    DeviceSetting.SECURE,
+    'camera_double_tap_power_gesture_disabled',
+    desc='Enables or disables gesture to open camera from any screen by '
+         'pressing power button twice.')
+
+camera_double_twist_to_flip_enabled = DeviceSetting(
+    DeviceSetting.SECURE,
+    'camera_double_twist_to_flip_enabled',
+    desc='Enables or disables gesture to switch between front and rear cameras '
+         'by twisting phone twice.')
+
+system_navigation_keys_enabled = DeviceSetting(
+    DeviceSetting.SECURE,
+    'system_navigation_keys_enabled',
+    desc='Enables soft navigation buttons to be displayed. When disabled '
+         'navigation is done through screen gestures.')
+
+camera_lift_trigger_enabled = DeviceSetting(
+    DeviceSetting.SECURE,
+    'camera_lift_trigger_enabled',
+    desc='Enables gesture to start camera with lift gesture.')
+
+aware_enabled = DeviceSetting(
+    DeviceSetting.SECURE, 'aware_enabled',
+    desc='Enables to discovery and connection to other wifi-aware devices '
+         'without any other type of connectivity between them.')
+
+doze_wake_screen_gesture = DeviceSetting(
+    DeviceSetting.SECURE,
+    'doze_wake_screen_gesture',
+    desc='Enables or disable doze/wake screen gesture.')
+
+skip_gesture = DeviceSetting(
+    DeviceSetting.SECURE,
+    'skip_gesture',
+    desc='Enables or disables gest gesture to skip songs by using a hand wave '
+         'gesture.')
+
+silence_gesture = DeviceSetting(
+    DeviceSetting.SECURE,
+    'silence_gesture',
+    desc='Enables or disables gesture to silence the phone ring by pressing '
+         'power and volume up buttons.')
+
+single_tap_gesture = DeviceSetting(
+    DeviceSetting.SECURE,
+    'doze_tap_gesture',
+    desc='Modifies whether the single tap gesture is enabled.')
+
+double_tap_gesture = DeviceSetting(
+    DeviceSetting.SECURE,
+    'doze_pulse_on_double_tap',
+    desc='Modifies whether the double tap gesture is enabled.')
+
+wake_gesture = DeviceSetting(
+    DeviceSetting.SECURE,
+    'wake_gesture_enabled',
+    desc='Modifies whether the device should wake when the wake gesture sensor '
+         'detects motion.')
+
+screensaver = DeviceSetting(
+    DeviceSetting.SECURE,
+    'screensaver_enabled',
+    desc='Modifies whether the screensaver is enabled.')
+
+notification_led = DeviceSetting(
+    DeviceSetting.SYSTEM,
+    'notification_light_pulse',
+    desc='Modifies whether the notification led is enabled.')
+
+# Audio
+
+disable_audio = DeviceSetprop('ro.audio.silent',
+                              desc='Modifies the audio silent property.')
+
+# Accelerometer
+
+auto_rotate = DeviceSetting(
+    DeviceSetting.SYSTEM, 'accelerometer_rotation',
+    desc='Modifies whether auto-rotation is enabled.')
+
+# Time
+
+auto_time = DeviceSetting(
+    DeviceSetting.GLOBAL,
+    'auto_time',
+    desc='Modifies whether the time is defined automatically.')
+
+auto_timezone = DeviceSetting(
+    DeviceSetting.GLOBAL,
+    'auto_timezone',
+    desc='Modifies whether timezone is defined automatically.')
+
+timezone = DeviceSetprop(
+    'persist.sys.timezone',
+    desc='Sets a specified timezone.')
+
+# Location
+
+location_gps = DeviceSetting(
+    DeviceSetting.SECURE,
+    'location_providers_allowed',
+    on_val='+gps',
+    off_val='-gps',
+    desc='Modifies whether gps is an allowed location provider.')
+
+location_network = DeviceSetting(
+    DeviceSetting.SECURE, 'location_providers_allowed',
+    on_val='+network', off_val='-network',
+    desc='Modifies whether network is an allowed location provider.')
+
+location_mode = DeviceSetting(
+    DeviceSetting.SECURE, 'location_mode', on_val='3', off_val='0',
+    desc='Sets location mode to either high accuracy (3) or off (0).')
+
+# Power
+
+battery_saver_mode = DeviceSetting(
+    DeviceSetting.GLOBAL,
+    'low_power',
+    desc='Modifies whether to enable battery saver mode.')
+
+battery_saver_trigger = DeviceSetting(
+    DeviceSetting.GLOBAL,
+    'low_power_trigger_level',
+    desc='Defines the battery level [1-100] at which low power mode '
+         'automatically turns on. If 0, it will not automatically turn on. For '
+         'Q and newer, it will only automatically turn on if the value is '
+         'greater than 0 and is set to '
+         'PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE. '
+         'http://shortn/_aGYdmJ8mvf')
+
+enable_full_batterystats_history = GenericCommand(
+    'dumpsys batterystats --enable full-history',
+    desc='Enables full battery stats history.')
+
+disable_doze = GenericCommand(
+    'dumpsys deviceidle disable',
+    desc='Disables device\'s deep sleep also known as doze (not to be confused '
+         'with ambient, which is also referred to as doze).')
+
+power_stayon = GenericCommand('vc power stayon true',
+                              desc='Keep awake from entering sleep.')
+
+# Sensors
+
+disable_sensors = GenericCommand(
+    'dumpsys sensorservice restrict blah', desc='Disables sensors.')
+
+MOISTURE_DETECTION_SETTING_FILE = '/sys/class/power_supply/usb/moisture_detection_enabled'
+disable_moisture_detection = GenericCommand(
+    'echo 0 > %s' % MOISTURE_DETECTION_SETTING_FILE,
+    desc='Modifies /sys/class/power_supply/usb/moisture_detection_enabled so '
+         'that moisture detection will be disabled next time it is read.')
+stop_moisture_detection = GenericCommand(
+    'setprop vendor.usb.contaminantdisable true',
+    desc='Triggers a re-read of '
+         '/sys/class/power_supply/usb/moisture_detection_enabled'
+         'which will enable / disable moisture detection based on its content.')
+
+ambient_eq = DeviceSetting(
+    DeviceSetting.SECURE, 'display_white_balance_enabled',
+    desc='Modifies ambient EQ, which is auto balance of brightness and color '
+         'temperature feature. Differs from adaptive brightness in that this '
+         'also changes the color balance.'
+         'https://support.google.com/googlenest/answer/9137130?hl=en. ')
+
+disable_pixellogger = GenericCommand(
+    'pm disable com.android.pixellogger', desc="Disables system apps.")
+
+oslo_gating = DeviceSetprop('pixel.oslo.gating',
+                            desc='Disables oslo gating.')
+
+# Miscellaneous
+
+hidden_api_exemption = GenericCommand(
+    'settings put global hidden_api_blacklist_exemptions \\*',
+    desc='Allows all private apis for testing')
+
+test_harness = DeviceBinaryCommandSeries([
+    DeviceSetprop('ro.monkey', desc='Modifies monkey state.'),
+    DeviceSetprop('ro.test_harness', desc='Modifies test_harness property.')
+])
+
+crashed_activities = GenericCommand('dumpsys activity processes | grep -e'
+                                    ' .*crashing=true.*AppErrorDialog.* -e'
+                                    ' .*notResponding=true.'
+                                    '*AppNotRespondingDialog.*',
+                                    desc='Logs crashed processes')
+
+dismiss_keyguard = GenericCommand('wm dismiss-keyguard',
+                                  desc='Dismisses the lockscreen.')
+
+disable_live_captions = GenericCommand('device_config put '
+                                       'device_personalization_services '
+                                       'Captions__disable_prod true',
+                                       desc='Disables live captions')
+
+disable_super_packs = GenericCommand(
+    'device_config put device_personalization_services '
+    'Overview__enable_superpacks_download false',
+    desc='Disables AiAi Webref download')
+
+home_button = GenericCommand('input keyevent 3',
+                             desc='Goes to home screen')
+
+menu_button = GenericCommand('input keyevent 82',
+                             desc='Unlocks screen by pressing menu button')
+
+modem_diag = DeviceBinaryCommandSeries([
+    DeviceSetprop(
+        'persist.vendor.sys.modem.diag.mdlog',
+        'true',
+        'false',
+        desc='Modifies vendor modem logging property'),
+    DeviceSetprop(
+        'persist.sys.modem.diag.mdlog',
+        'true',
+        'false',
+        desc='Modifies modem logging property'),
+])
+
+reboot_power = GenericCommand('svc power reboot null',
+                              desc='Reboots device')
+
+enable_ramdumps = DeviceSetprop('persist.vendor.sys.ssr.enable_ramdumps',
+                                desc='Modifies whether to enable ramdumps.')
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_commands/goog.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_commands/goog.py
new file mode 100644
index 0000000..fd676b7
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/adb_commands/goog.py
@@ -0,0 +1,205 @@
+#!/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.
+"""Google-internal device settings for power testing."""
+
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  DeviceBinaryCommandSeries
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  DeviceGServices
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  DeviceState
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  DeviceSetprop
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  DeviceSetting
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  GenericCommand
+
+# Location
+
+location_collection = DeviceGServices(
+    'location:collection_enabled',
+    on_val='1',
+    off_val='0',
+    desc='Modifies whether collecting location is enabled.')
+
+location_off_warning_dialog = DeviceGServices(
+    'location:enable_location_off_warning_dialog', on_val='true',
+    off_val='false',
+    desc='Modifies whether the location off warning dialog should appear.'
+)
+
+location_opt_in = DeviceBinaryCommandSeries([
+    DeviceState(
+        'content insert --uri content://com.google.settings/'
+        'partner --bind name:s:use_location_for_services '
+        '--bind value:s:%s',
+        desc='Modifies whether using location for services is '
+             'allowed.'),
+    DeviceState(
+        'content insert --uri content://com.google.settings/'
+        'partner --bind name:s:network_location_opt_in '
+        '--bind value:s:%s',
+        desc='Modifies whether to allow location over network.')
+])
+
+location_activity_low_power = DeviceGServices(
+    'location:activity_low_power_mode_enabled',
+    on_val='true',
+    off_val='false',
+    desc='Enables or disables location activity low power mode.'
+)
+
+location_policy_set = GenericCommand(
+    'am broadcast -a com.google.gservices.intent.action.GSERVICES_OVERRIDE '
+    '-e gms:chimera:dev_module_packages '
+    '"com.google.android.gms.policy_location"',
+    desc='Allows location policy set'
+)
+
+# Cast
+cast_broadcast = DeviceGServices(
+    'gms:cast:mdns_device_scanner:is_enabled',
+    desc='Disable devices sending CAST messages to prevent frequent wakeups '
+         'by WiFi')
+
+# Apps
+disable_chrome = GenericCommand(
+    'pm disable-user com.android.chrome', desc='Disables the Google chrome.')
+
+force_stop_nexuslauncher = GenericCommand(
+    'am force-stop com.google.android.apps.nexuslauncher',
+    desc='Force stop nexus launcher.')
+
+disable_playstore = GenericCommand(
+    'pm disable-user com.android.vending',
+    desc='Disables the Google playstore.')
+
+duo_grant_camera = GenericCommand(
+    'pm grant com.google.android.apps.faketachyon android.permission.CAMERA',
+    desc='Grants camera permissions to the duo(faketachyon) app')
+
+duo_grant_audio = GenericCommand(
+    'pm grant '
+    'com.google.android.apps.faketachyon android.permission.RECORD_AUDIO',
+    desc='Grants audio permissions to the duo(faketachyon) app')
+
+duo_grant_contacts = GenericCommand(
+    'pm grant '
+    'com.google.android.apps.faketachyon android.permission.READ_CONTACTS',
+    desc='Grants contacts permissions to the duo(faketachyon) app')
+
+# Volta
+
+disable_volta = GenericCommand(
+    'pm disable-user com.google.android.volta',
+    desc='Disables the volta app.')
+
+# Betterbug
+
+disable_betterbug = GenericCommand(
+    'pm disable-user --user 0 com.google.android.apps.internal.betterbug',
+    desc='Disables the betterbug app.')
+
+# CHRE
+
+disable_chre = GenericCommand(
+    'setprop ctl.stop vendor.chre',
+    desc='Disables chre.')
+
+enable_chre = GenericCommand('setprop ctl.start vendor.chre',
+                             desc='Enables chre.')
+
+# MusicIQ
+
+disable_musiciq = GenericCommand(
+    'am force-stop com.google.intelligence.sense',
+    desc='Disables the musiciq feature, whch listens to surrounding music to '
+         'show what is being played.')
+
+enable_musiciq = GenericCommand(
+    'am start -n com.google.intelligence.sense/com.google.intelligence.sense.'
+    'ambientmusic.AmbientMusicSettingsActivity',
+    desc='Enables the musiciq feature')
+
+# Email
+
+remove_gmail_account = GenericCommand(
+    'am instrument -w com.google.android.tradefed.account/.RemoveAccounts',
+    desc='Removes gmail account.')
+
+# Hotword
+
+hotword = DeviceState(
+    'am start -a com.android.intent.action.MANAGE_VOICE_KEYPHRASES '
+    '--ei com.android.intent.extra.VOICE_KEYPHRASE_ACTION %s '
+    '--es com.android.intent.extra.VOICE_KEYPHRASE_HINT_TEXT "demo" '
+    '--es com.android.intent.extra.VOICE_KEYPHRASE_LOCALE "en-US" '
+    'com.android.hotwordenrollment.okgoogle/'
+    'com.android.hotwordenrollment.okgoogle.EnrollmentActivity',
+    on_val='0',
+    off_val='2',
+    desc='Modifies whether hotword detection is enabled.')
+
+camera_hdr_mode = DeviceSetprop(
+    'camera.optbar.hdr', on_val='true', off_val='false',
+    desc="Modifies whether to use HDR camera mode.")
+
+# persists across reboots
+compact_location_log = DeviceGServices(
+    'location:compact_log_enabled',
+    desc='Enables compact location logging')
+
+# persists across reboots
+magic_tether = DeviceGServices('gms:magictether:enable',
+                               desc='Disables magic tethering')
+
+# persists across reboots
+ocr = DeviceGServices('ocr.cc_ocr_enabled',
+                      desc='Turns off OCR download for testing purposes')
+
+# persists across reboots
+phenotype = DeviceGServices(
+    'gms:phenotype:phenotype_flag:debug_bypass_phenotype',
+    desc='Bypasses GMS core phenotype experiments')
+
+# persists across reboots
+icing = DeviceGServices('gms_icing_extension_download_enabled',
+                        desc='Turns off icing download for testing purposes')
+
+edge_sensor = DeviceSetting(
+    DeviceSetting.SECURE, 'assist_gesture_enabled',
+    desc='Modifies whether the edge sensor gesture is enabled.')
+
+edge_sensor_wakeup = DeviceSetting(
+    DeviceSetting.SECURE,
+    'assist_gesture_wake_enabled',
+    desc='Modifies whether the edge sensor gesture is allowed to wake up '
+         'the device.')
+
+edge_sensor_alerts = DeviceSetting(
+    DeviceSetting.SECURE,
+    'assist_gesture_silence_alerts_enabled',
+    desc='Triggers activation/deactivation of edge sensor gesture. It '
+         'depends on the settings assist_gesture_enabled and '
+         'assist_gesture_wake_enabled to be previously set.')
+
+activity_recognition = GenericCommand(
+    'am instrument -e interval_ms 180000'
+    ' -e class com.google.android.gms.testapps.location.activity.power.'
+    'ActivityPowerUtil#testRegisterAR -w '
+    'com.google.android.gms.testapps.location.activity.power/.ARTestRunner',
+    desc='Turns activity recognition on')
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/instrumentation/device/command/intent_builder.py b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/intent_builder.py
new file mode 100644
index 0000000..d18a273
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/device/command/intent_builder.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 collections
+import shlex
+
+TYPE_TO_FLAG = collections.defaultdict(lambda: '--es')
+TYPE_TO_FLAG.update({bool: '--ez', int: '--ei', float: '--ef', str: '--es'})
+
+
+class IntentBuilder(object):
+    """Helper class to build am broadcast <INTENT> commands."""
+
+    def __init__(self, base_cmd=''):
+        """Initializes the intent command builder.
+
+        Args:
+            base_cmd: The base am command, e.g. am broadcast, am start
+        """
+        self._base_cmd = base_cmd
+        self._action = None
+        self._component = None
+        self._data_uri = None
+        self._flags = []
+        self._key_value_params = collections.OrderedDict()
+
+    def set_action(self, action):
+        """Set the intent action, as marked by the -a flag"""
+        self._action = action
+
+    def set_component(self, package, component=None):
+        """Set the package and/or component, as marked by the -n flag.
+        Only the package name will be used if no component is specified.
+        """
+        if component:
+            self._component = '%s/%s' % (package, component)
+        else:
+            self._component = package
+
+    def set_data_uri(self, data_uri):
+        """Set the data URI, as marked by the -d flag"""
+        self._data_uri = data_uri
+
+    def add_flag(self, flag):
+        """Add any additional flags to the intent argument"""
+        self._flags.append(flag)
+
+    def add_key_value_param(self, key, value=None):
+        """Add any extra data as a key-value pair"""
+        self._key_value_params[key] = value
+
+    def build(self):
+        """Returns the full intent command string."""
+        cmd = [self._base_cmd]
+        if self._action:
+            cmd.append('-a %s' % self._action)
+        if self._component:
+            cmd.append('-n %s' % self._component)
+        if self._data_uri:
+            cmd.append('-d %s' % self._data_uri)
+        cmd += self._flags
+        for key, value in self._key_value_params.items():
+            if value is None:
+                cmd.append('--esn %s' % key)
+            else:
+                str_value = str(value)
+                if isinstance(value, bool):
+                    str_value = str_value.lower()
+                cmd.append(' '.join((TYPE_TO_FLAG[type(value)], key,
+                                     shlex.quote(str_value))))
+        return ' '.join(cmd).strip()
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/instrumentation_base_test.py b/acts_tests/acts_contrib/test_utils/instrumentation/instrumentation_base_test.py
new file mode 100644
index 0000000..f798462
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/instrumentation_base_test.py
@@ -0,0 +1,301 @@
+#!/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 yaml
+
+from acts import base_test
+from acts import context
+from acts import error
+
+from acts.controllers.adb import DEFAULT_ADB_TIMEOUT
+
+from acts_contrib.test_utils.instrumentation import instrumentation_proto_parser as proto_parser
+from acts_contrib.test_utils.instrumentation import instrumentation_text_output_parser
+from acts_contrib.test_utils.instrumentation.config_wrapper import ContextualConfigWrapper
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import GenericCommand
+from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import DEFAULT_INSTRUMENTATION_LOG_OUTPUT
+
+RESOLVE_FILE_MARKER = 'FILE'
+FILE_NOT_FOUND = 'File is missing from ACTS config'
+DEFAULT_TEST_OPTIONS_FILE = 'test_options.yaml'
+
+
+class InstrumentationTestError(error.ActsError):
+    """Raised for general instrumentation test errors."""
+
+
+class InstrumentationBaseTest(base_test.BaseTestClass):
+    """Base class for tests based on am instrument."""
+
+    def __init__(self, configs):
+        """Initialize an InstrumentationBaseTest
+
+        Args:
+            configs: Dict representing the test configuration
+        """
+        super().__init__(configs)
+        if 'test_options' in self.user_params:
+            test_options_path = self.user_params['test_options'][0]
+        elif 'instrumentation_config' in self.user_params:
+            test_options_path = self.user_params['instrumentation_config'][0]
+        else:
+            raise InstrumentationTestError(
+                'Test options file not specified. Please add a valid '
+                '"test_options" path to the ACTS config.')
+        self._test_options = ContextualConfigWrapper()
+        if os.path.exists(test_options_path):
+            self._test_options = self._load_test_options(test_options_path)
+        else:
+            raise InstrumentationTestError(
+                'Test options file %s does not exist'
+                % test_options_path)
+        self._instrumentation_config = self._test_options
+
+    def _load_test_options(self, path):
+        """Load the test options file into a ContextualConfigWrapper object.
+
+        Args:
+            path: Path to the test options file.
+
+        Returns: The loaded test options as a ContextualConfigWrapper
+        """
+        try:
+            with open(path, mode='r', encoding='utf-8') as f:
+                config_dict = yaml.safe_load(f)
+        except Exception as e:
+            raise InstrumentationTestError(
+                'Cannot open or parse test options file %s. Error: %s'
+                % (path, e))
+
+        # Write out a copy of the test options
+        with open(os.path.join(
+                self.log_path, DEFAULT_TEST_OPTIONS_FILE),
+                mode='w', encoding='utf-8') as f:
+            yaml.safe_dump(config_dict, f)
+
+        return ContextualConfigWrapper(config_dict)
+
+    def setup_class(self):
+        """Class setup"""
+        self.ad_dut = self.android_devices[0]
+
+    def teardown_test(self):
+        """Test teardown. Takes bugreport and cleans up device."""
+        self._cleanup_device()
+
+    def on_exception(self, test_name, begin_time):
+        """Called upon unhandled test exception."""
+        if self._test_options.get('bugreport_on_exception', default=True):
+            self._take_bug_report(test_name, begin_time)
+
+    def on_pass(self, test_name, begin_time):
+        """Called upon test pass."""
+        if self._test_options.get('bugreport_on_pass', default=True):
+            self._take_bug_report(test_name, begin_time)
+
+    def on_fail(self, test_name, begin_time):
+        """Called upon test failure."""
+        if self._test_options.get('bugreport_on_fail', default=True):
+            self._take_bug_report(test_name, begin_time)
+
+    def _prepare_device(self):
+        """Prepares the device for testing."""
+        pass
+
+    def _cleanup_device(self):
+        """Clean up device after test completion."""
+        pass
+
+    def get_files_from_config(self, config_key):
+        """Get a list of file paths on host from self.user_params with the
+        given key. Verifies that each file exists.
+
+        Args:
+            config_key: Key in which the files are found.
+
+        Returns: list of str file paths
+        """
+        if config_key not in self.user_params:
+            raise InstrumentationTestError(
+                'Cannot get files for key "%s": Key missing from config.'
+                % config_key)
+        files = self.user_params[config_key]
+        for f in files:
+            if not os.path.exists(f):
+                raise InstrumentationTestError(
+                    'Cannot get files for key "%s": No file exists for %s.' %
+                    (config_key, f))
+        return files
+
+    def get_file_from_config(self, config_key):
+        """Get a single file path on host from self.user_params with the given
+        key. See get_files_from_config for details.
+        """
+        return self.get_files_from_config(config_key)[-1]
+
+    def adb_run(self, cmds, ad=None, timeout=DEFAULT_ADB_TIMEOUT):
+        """Run the specified command, or list of commands, with the ADB shell.
+
+        Args:
+            cmds: A string, A GenericCommand, a list of strings or a list of
+                  GenericCommand representing ADB shell command(s)
+            ad: The device to run on. Defaults to self.ad_dut.
+
+        Returns: dict mapping command to resulting stdout
+        """
+        if ad is None:
+            ad = self.ad_dut
+        if isinstance(cmds, str) or isinstance(cmds, GenericCommand):
+            cmds = [cmds]
+
+        out = {}
+        for cmd in cmds:
+            if isinstance(cmd, GenericCommand):
+                if cmd.desc:
+                    ad.log.debug('Applying command that: %s' % cmd.desc)
+                cmd = cmd.cmd
+            out[cmd] = ad.adb.shell(cmd, timeout=timeout)
+        return out
+
+    def fastboot_run(self, cmds, ad=None):
+        """Run the specified command, or list of commands, with the FASTBOOT shell.
+
+        Args:
+            cmds: A string, A GenericCommand, a list of strings or a list of
+                  GenericCommand representing FASTBOOT command(s)
+            ad: The device to run on. Defaults to self.ad_dut.
+
+        Returns: dict mapping command to resulting stdout
+        """
+        if ad is None:
+            ad = self.ad_dut
+        if isinstance(cmds, str) or isinstance(cmds, GenericCommand):
+            cmds = [cmds]
+
+        out = {}
+        for cmd in cmds:
+            if isinstance(cmd, GenericCommand):
+                if cmd.desc:
+                    ad.log.debug('Applying command that: %s' % cmd.desc)
+                cmd = cmd.cmd
+            out[cmd] = ad.fastboot._exec_fastboot_cmd(cmd, '')
+        return out
+
+    def adb_run_async(self, cmds, ad=None):
+        """Run the specified command, or list of commands, with the ADB shell.
+        (async)
+
+        Args:
+            cmds: A string or list of strings representing ADB shell command(s)
+            ad: The device to run on. Defaults to self.ad_dut.
+
+        Returns: dict mapping command to resulting subprocess.Popen object
+        """
+        if ad is None:
+            ad = self.ad_dut
+
+        if isinstance(cmds, str) or isinstance(cmds, GenericCommand):
+            cmds = [cmds]
+
+        procs = {}
+        for cmd in cmds:
+            if isinstance(cmd, GenericCommand):
+                if cmd.desc:
+                    ad.log.debug('Applying command to: %s' % cmd.desc)
+                cmd = cmd.cmd
+            procs[cmd] = ad.adb.shell_nb(cmd)
+        return procs
+
+    def parse_instrumentation_result(self):
+        """Parse the instrumentation result and write it to a human-readable
+        txt file in the log directory.
+
+        Returns: The parsed instrumentation_data_pb2.Session
+        """
+        log_path = context.get_current_context().get_full_output_path()
+
+        if proto_parser.has_instrumentation_proto(self.ad_dut):
+            proto_file = proto_parser.pull_proto(self.ad_dut, log_path)
+            proto_txt_path = os.path.join(log_path, 'instrumentation_proto.txt')
+            session = proto_parser.get_session_from_local_file(proto_file)
+            with open(proto_txt_path, 'w') as f:
+                f.write(str(session))
+            return session
+
+        on_device = os.path.join(
+            self.ad_dut.external_storage_path,
+            DEFAULT_INSTRUMENTATION_LOG_OUTPUT)
+        if self.file_exists(on_device):
+            plain_output = instrumentation_text_output_parser.pull_output(
+                self.ad_dut,
+                log_path,
+                on_device)
+            proto_txt_path = os.path.join(
+                log_path,
+                'instrumentation_proto.from_plain_text.txt')
+            session = instrumentation_text_output_parser.parse_from_file(
+                plain_output)
+            with open(proto_txt_path, 'w') as f:
+                f.write(str(session))
+            return session
+
+        raise InstrumentationTestError('No instrumentation output was detected '
+                                       'in either proto nor text format.')
+
+    def file_exists(self, file_path, ad=None):
+        """Returns whether a file exists on a device.
+
+        Args:
+            file_path: The path of the file to check for.
+            ad: The AndroiDevice to check on. If left undefined the default
+            device under test will be used.
+        """
+        if ad is None:
+            ad = self.ad_dut
+
+        cmd = '(test -f %s && echo yes) || echo no' % file_path
+        result = self.adb_run(cmd, ad)
+        if result[cmd] == 'yes':
+            return True
+        elif result[cmd] == 'no':
+            return False
+        raise ValueError('Couldn\'t determine if %s exists. '
+                         'Expected yes/no, got %s' % (file_path, result[cmd]))
+
+    def log_instrumentation_result(self, session):
+        """Logs instrumentation test result.
+
+        Args:
+            session: an instrumentation_data_pb2.Session
+
+        Returns: The parsed instrumentation result
+        """
+        result = proto_parser.get_instrumentation_result(session)
+
+        if result['status_code']:
+            self.log.error('Instrumentation session aborted!')
+            if 'error_text' in result:
+                self.log.error('Instrumentation error: %s'
+                               % result['error_text'])
+            return result
+
+        log = self.log.info if result['result_code'] == -1 else self.log.error
+        log('Instrumentation command finished with result_code %d.'
+            % result['result_code'])
+        if 'stream' in result:
+            log('Instrumentation output: %s' % result['stream'])
+        return result
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/instrumentation_proto_parser.py b/acts_tests/acts_contrib/test_utils/instrumentation/instrumentation_proto_parser.py
new file mode 100644
index 0000000..8b50fb8
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/instrumentation_proto_parser.py
@@ -0,0 +1,178 @@
+#!/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 collections
+import os
+import tempfile
+
+from acts.error import ActsError
+from acts_contrib.test_utils.instrumentation.proto.gen import instrumentation_data_pb2
+
+DEFAULT_INST_LOG_DIR = 'instrument-logs'
+
+START_TIMESTAMP = 'start'
+END_TIMESTAMP = 'end'
+
+
+class ProtoParserError(ActsError):
+    """Class for exceptions raised by the proto parser."""
+
+
+def _build_proto_location(ad, source_path=None):
+    if source_path:
+        return source_path
+    else:
+        default_full_proto_dir = os.path.join(
+            ad.external_storage_path, DEFAULT_INST_LOG_DIR)
+        filename = ad.adb.shell(
+            '(ls %s -t | head -n1) || true' % default_full_proto_dir)
+        return os.path.join(default_full_proto_dir,
+                            filename) if filename else None
+
+
+def has_instrumentation_proto(ad, source_path=None):
+    """Determines whether an instrument proto was produced.
+
+    Args:
+        ad: AndroidDevice object
+        source_path: Path on the device where the proto is generated. If None,
+            pull the latest proto from DEFAULT_INST_PROTO_DIR.
+    """
+    proto_location = _build_proto_location(ad, source_path)
+    if proto_location is None:
+        return False
+    ls_out = ad.adb.shell('ls %s' % proto_location)
+    return ls_out != ''
+
+
+def pull_proto(ad, dest_dir, source_path=None):
+    """Pull latest instrumentation result proto from device.
+
+    Args:
+        ad: AndroidDevice object
+        dest_dir: Directory on the host where the proto will be sent
+        source_path: Path on the device where the proto is generated. If None,
+            pull the latest proto from DEFAULT_INST_PROTO_DIR.
+
+    Returns: Path to the retrieved proto file
+    """
+    location = _build_proto_location(ad, source_path)
+    if not source_path and not location:
+        raise ProtoParserError(
+            'No instrumentation result protos found at default location.')
+
+    ad.pull_files(location, dest_dir)
+    dest_path = os.path.join(dest_dir, os.path.basename(location))
+    if not os.path.exists(dest_path):
+        raise ProtoParserError(
+            'Failed to pull instrumentation result proto: %s -> %s'
+            % (source_path, dest_path))
+    return dest_path
+
+
+def get_session_from_local_file(proto_file):
+    """Get a instrumentation_data_pb2.Session object from a proto file on the
+    host.
+
+    Args:
+        proto_file: Path to the proto file (on host)
+
+    Returns: An instrumentation_data_pb2.Session
+    """
+    with open(proto_file, 'rb') as f:
+        return instrumentation_data_pb2.Session.FromString(f.read())
+
+
+def get_session_from_device(ad, source_path=None):
+    """Get a instrumentation_data_pb2.Session object from a proto file on
+    device.
+
+    Args:
+        ad: AndroidDevice object
+        source_path: Path to the proto file (on device). If None, defaults to
+            latest proto from DEFAULT_INST_PROTO_DIR.
+
+    Returns: An instrumentation_data_pb2.Session
+    """
+    with tempfile.TemporaryDirectory() as tmp_dir:
+        pulled_proto = pull_proto(ad, tmp_dir, source_path)
+        return get_session_from_local_file(pulled_proto)
+
+
+def get_test_timestamps(session):
+    """Parse an instrumentation_data_pb2.Session to get the timestamps for each
+    test.
+
+    Args:
+        session: an instrumentation_data_pb2.Session
+
+    Returns: a dict in the format
+        {
+            <test name> : (<begin_time>, <end_time>),
+            ...
+        }
+    """
+    timestamps = collections.defaultdict(dict)
+    for test_status in session.test_status:
+        entries = test_status.results.entries
+        # Timestamp entries have the key 'timestamp-message'
+        if any(entry.key == 'timestamps-message' for entry in entries):
+            test_name = None
+            timestamp = None
+            timestamp_type = None
+            for entry in entries:
+                if entry.key == 'test':
+                    test_name = entry.value_string
+                if entry.key == 'timestamp':
+                    if entry.HasField('value_long'):
+                        timestamp = entry.value_long
+                    else:
+                        timestamp = int(entry.value_string)
+                if entry.key == 'start-timestamp':
+                    timestamp_type = START_TIMESTAMP
+                if entry.key == 'end-timestamp':
+                    timestamp_type = END_TIMESTAMP
+            if test_name and timestamp and timestamp_type:
+                timestamps[test_name][timestamp_type] = timestamp
+    return timestamps
+
+
+def get_instrumentation_result(session):
+    """Parse an instrumentation_data_pb2.Session to get the result code and
+    stream of the session.
+
+    Args:
+        session: an instrumentation_data_pb2.Session
+
+    Returns: a dict of
+        {
+            'status_code': <int>,
+            'result_code': <int>,
+            'error_text': <str>,
+            'stream': <str>
+        }
+    """
+    session_status = session.session_status
+    res = {
+        'status_code': session_status.status_code,
+        'result_code': session_status.result_code
+    }
+    if session_status.error_text:
+        res['error_text'] = session_status.error_text
+    for entry in session.session_status.results.entries:
+        if entry.key == 'stream':
+            res['stream'] = entry.value_string
+    return res
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/instrumentation_text_output_parser.py b/acts_tests/acts_contrib/test_utils/instrumentation/instrumentation_text_output_parser.py
new file mode 100644
index 0000000..cced3ac
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/instrumentation_text_output_parser.py
@@ -0,0 +1,203 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import re
+
+from acts.error import ActsError
+from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import DEFAULT_INSTRUMENTATION_LOG_OUTPUT
+from acts_contrib.test_utils.instrumentation.proto.gen import instrumentation_data_pb2
+
+
+class InstrumentationOutputError(ActsError):
+    """Class for exceptions raised by instrumentation_test_output_parser"""
+
+
+def pull_output(ad, dest_dir, source_path=None):
+    """Pulls a file from the target device. The default file to be pulled
+    is defined by
+    instrumentation_command_builder.DEFAULT_INSTRUMENTATION_LOG_OUTPUT.
+
+    Args:
+        ad: AndroidDevice object.
+        dest_dir: Directory on the host where the file will be put.
+        source_path: Absolute path on the device where the file will be pulled
+        from. By default
+        instrumentation_command_builder.DEFAULT_INSTRUMENTATION_LOG_OUTPUT.
+    """
+    if source_path is None:
+        source_path = os.path.join(ad.external_storage_path,
+                                   DEFAULT_INSTRUMENTATION_LOG_OUTPUT)
+    ad.pull_files(source_path, dest_dir)
+    dest_path = os.path.join(dest_dir, os.path.basename(source_path))
+    if not os.path.exists(dest_path):
+        raise InstrumentationOutputError(
+            'Failed to pull instrumentation text output: %s -> %s'
+            % (source_path, dest_path))
+    return dest_path
+
+
+class _Markers(object):
+    """Markers used to delimit sections in the instrumentation's output.
+    Standard instrumentation output follows the format::
+
+        INSTRUMENTATION_STATUS: <key>=<value>
+        INSTRUMENTATION_STATUS: <key>=<value>
+        INSTRUMENTATION_STATUS: <key>=<value>
+        INSTRUMENTATION_STATUS_CODE: <code>
+        ...
+        INSTRUMENTATION_STATUS: <key>=<value>
+        INSTRUMENTATION_STATUS: <key>=<value>
+        INSTRUMENTATION_STATUS: <key>=<value>
+        INSTRUMENTATION_STATUS_CODE: <code>
+        INSTRUMENTATION_RESULT: <key>=<value>
+        INSTRUMENTATION_CODE: <code>
+
+
+    The parts marked as <value> can span several lines, this normally happens
+    for INSTRUMENTATION_RESULT and INSTRUMENTATION_STATUS only.
+    """
+    STATUS = "INSTRUMENTATION_STATUS:"
+    STATUS_CODE = "INSTRUMENTATION_STATUS_CODE:"
+    RESULT = 'INSTRUMENTATION_RESULT:'
+    CODE = 'INSTRUMENTATION_CODE:'
+
+
+def _remove_prefix(line, marker):
+    match = re.match('(\\S*%s).*' % marker, line)
+    prefix = match.group(1)
+    return line[len(prefix):].lstrip()
+
+
+def _extract_key_value(line, marker):
+    key_value = _remove_prefix(line, marker)
+    return key_value.split('=', 1)
+
+
+def _extract_status_code(line, marker):
+    return int(_remove_prefix(line, marker))
+
+
+class InstrumentationParserStateMachine(object):
+    """Stateful class that understands transitions in between instrumentation
+    output markers and how they translate into corresponding fragments of the
+    instrumentation_data_pb2.Session equivalent object."""
+
+    def __init__(self):
+        self.session = instrumentation_data_pb2.Session()
+        self._test_status = instrumentation_data_pb2.TestStatus()
+        self._result_entry = instrumentation_data_pb2.ResultsBundleEntry()
+        self._results_bundle = instrumentation_data_pb2.ResultsBundle()
+        self._session_status = instrumentation_data_pb2.SessionStatus()
+        self._value_lines = []
+        self._status_result_entry_is_open = False
+        self._session_result_entry_is_open = False
+
+    def _add_unmarked_line(self, line):
+        """An unmarked line is either part of a status result entry or
+        a session result entry."""
+        if (self._status_result_entry_is_open
+                or self._session_result_entry_is_open):
+            self._value_lines.append(line)
+        else:
+            raise InstrumentationOutputError(
+                'Unmarked line misplacement. Line: %s' % line)
+
+    def _add_test_status_result_code(self, code):
+        """Adds the test_status to the session since the result_code is the
+        last token that appears."""
+        self._close_open_states()
+        self._test_status.result_code = code
+        self._test_status.results.CopyFrom(self._results_bundle)
+        self.session.test_status.add().CopyFrom(self._test_status)
+
+        # clear holders for next additions
+        self._results_bundle.Clear()
+        self._test_status.Clear()
+
+    def _add_session_result_code(self, code):
+        """Adds the results bundle to the session_status since the result_code
+        is the last token that appears."""
+        self._close_open_states()
+        self.session.session_status.result_code = code
+        self.session.session_status.results.CopyFrom(self._results_bundle)
+        self._results_bundle.Clear()
+
+    def _add_status_result_entry(self, key):
+        self._close_open_states()
+        self._status_result_entry_is_open = True
+        self._result_entry.key = key
+
+    def _add_session_result_entry(self, key):
+        self._close_open_states()
+        self._session_result_entry_is_open = True
+        self._result_entry.key = key
+
+    def _close_open_states(self):
+        """If a marker is found, open states can be wrapped."""
+        self._wrap_session_result_entry_if_open()
+        self._wrap_sesion_result_entry_if_open()
+
+    def _wrap_sesion_result_entry_if_open(self):
+        if not self._session_result_entry_is_open:
+            return
+        self._result_entry.value_string = '\n'.join(self._value_lines)
+        self._results_bundle.entries.add().CopyFrom(self._result_entry)
+
+        # clear holders for next additions
+        self._value_lines.clear()
+        self._result_entry.Clear()
+        self._session_result_entry_is_open = False
+
+    def _wrap_session_result_entry_if_open(self):
+        if not self._status_result_entry_is_open:
+            return
+        self._result_entry.value_string = '\n'.join(self._value_lines)
+        self._results_bundle.entries.add().CopyFrom(self._result_entry)
+
+        # clear holders for next additions
+        self._value_lines.clear()
+        self._result_entry.Clear()
+        self._status_result_entry_is_open = False
+
+    def add_line(self, line):
+        if re.match('\\S*%s.*' % _Markers.STATUS, line):
+            (key, value) = _extract_key_value(line, _Markers.STATUS)
+            self._add_status_result_entry(key)
+            self._add_unmarked_line(value)
+        elif re.match('\\S*%s.*' % _Markers.STATUS_CODE, line):
+            code = _extract_status_code(line, _Markers.STATUS_CODE)
+            self._add_test_status_result_code(code)
+        elif re.match('\\S*%s.*' % _Markers.RESULT, line):
+            (key, value) = _extract_key_value(line, _Markers.RESULT)
+            self._add_session_result_entry(key)
+            self._add_unmarked_line(value)
+        elif re.match('\\S*%s.*' % _Markers.CODE, line):
+            code = _extract_status_code(line, _Markers.CODE)
+            self._add_session_result_code(code)
+        else:
+            self._add_unmarked_line(line)
+
+def parse_from_file(source_path):
+    """Parses a file into a instrumentation_data_pb2.Session object. All values
+    are casted as string."""
+    state_machine = InstrumentationParserStateMachine()
+
+    with open(source_path, 'r') as f:
+        for line in f:
+            line = line.rstrip()
+            state_machine.add_line(line)
+    return state_machine.session
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/apollo/ApolloBaseTest.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/apollo/ApolloBaseTest.py
new file mode 100644
index 0000000..751579a
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/apollo/ApolloBaseTest.py
@@ -0,0 +1,103 @@
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import AppInstaller
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
+
+import time
+
+
+class ApolloBaseTest(instrumentation_power_test.InstrumentationPowerTest):
+    """Test class for running instrumentation test idle system cases.
+
+    Many functions shamelessly copied from:
+        google3/wireless/android/apollo/test/lib/apollo_decorator.py
+    """
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.supported_hooks = {'start', 'stop'}
+        self._apk_install_wait_time_seconds = 5
+        self._scan_interval_seconds = None
+        self._scan_time_seconds = None
+        # TODO: remove once b/156301031 is resolved
+        self._disable_consent_dialog = True
+
+    def _prepare_device(self):
+        super()._prepare_device()
+        self.base_device_configuration()
+
+    def setup_test(self):
+        super().setup_test()
+        # clear command options that won't work on OEM devices.
+        self._instr_cmd_builder.set_output_as_text()
+        self._instr_cmd_builder.remove_flag('--no-isolated-storage')
+
+    def _set_nearby_phenotype_flag(self, flag_name, flag_type, flag_value):
+        self.adb_run(f'am broadcast -a "com.google.android.gms.phenotype.FLAG_OVERRIDE" --es package "com.google.android.gms.nearby" ' \
+                     f'--es user "*" --esa flags "{flag_name}" --esa values "{flag_value}" --esa types "{flag_type}" com.google.android.gms')
+
+    def _set_installation_overrides(self):
+        self._set_nearby_phenotype_flag('exposure_notification_enable_client_apps_whitelist', 'boolean', 'false')
+        if self._disable_consent_dialog:
+            self._set_nearby_phenotype_flag('exposure_notification_use_consent_dialog_for_all_clients', 'boolean', 'false')
+
+        # Scanning interval and scanning time need to be set here, before scanning starts
+        if self._scan_interval_seconds:
+            self._set_nearby_phenotype_flag('contact_tracing_scan_interval_second', 'long', str(self._scan_interval_seconds))
+        if self._scan_time_seconds:
+            self._set_nearby_phenotype_flag('contact_tracing_scan_time_second', 'long', str(self._scan_time_seconds))
+
+    def _start_scanning(self):
+        self._issue_apollo_test_hook_command('start')
+
+    def _issue_apollo_test_hook_command(self, hook_command, payload=None, time_to_wait_seconds=10):
+        if hook_command not in self.supported_hooks:
+            raise ValueError(f'Unsupported apollo test hook {hook_command}')
+        # Send a hook command, which is handled by the apollo test APK
+        self.adb_run(f'am start-foreground-service -a {hook_command} com.google.android.apps.exposurenotification/.debug.HookService')
+        # Wait for success and timeout on a failure. The test app does not explicitly tell you if the call failed.
+        start_time = time.time()
+        while time.time() - start_time < time_to_wait_seconds:
+            if self.ad_dut.search_logcat(f'HookService: Success:{hook_command}'):
+                return True
+            time.sleep(1)
+        raise RuntimeError(f'HookService:{hook_command} did not finish in {time_to_wait_seconds} seconds')
+
+    def _sideload_apollo(self):
+        self.ad_dut.adb.ensure_root()
+
+        self.adb_run('logcat -c')  # Clear previous logcat information - reflashing is not performed for Apollo
+
+        # Uninstall old APK's and clear flags
+        gmscore_apk_file = self.get_file_from_config('gmscore_file_' + self.ad_dut.serial)
+        gmscore_apk = AppInstaller(self.ad_dut, gmscore_apk_file)
+        gmscore_apk.uninstall()
+        nearby_module_apk_file = self.get_file_from_config('gmscore_nearby_en_file_' + self.ad_dut.serial)
+        nearby_module_apk = AppInstaller(self.ad_dut, nearby_module_apk_file)
+        nearby_module_apk.uninstall()
+        apollo_test_apk_file = self.get_file_from_config('exposure_notification_app')
+        apollo_test_apk = AppInstaller(self.ad_dut, apollo_test_apk_file)
+        apollo_test_apk.uninstall()
+
+        # Set up BT and location
+        self.adb_run('service call bluetooth_manager 6')
+        self.adb_run('settings put secure location_providers_allowed +gps')
+        self.adb_run('settings put secure location_mode 3')
+
+        # Install gmscore
+        gmscore_apk.install()
+        # Give gmscore some time to initialize (there doesn't appear to be a logcat message to key off)
+        time.sleep(self._apk_install_wait_time_seconds)
+
+        # Whitelist EN for sideloading
+        self.adb_run('am broadcast -a com.google.gservices.intent.action.GSERVICES_OVERRIDE -e gms:chimera:dev_module_packages "com.google.android.gms.policy_nearby"')
+        # Install EN module
+        nearby_module_apk.install()
+        # Give EN some time to initialize (there doesn't appear to be a logcat message to key off)
+        time.sleep(self._apk_install_wait_time_seconds)
+
+        # Whitelist test app and disable consent dialogs
+        self._set_installation_overrides()
+
+        # Install test apk
+        apollo_test_apk.install()
+        # Give the test app some time to initialize (there doesn't appear to be a logcat message to key off)
+        time.sleep(self._apk_install_wait_time_seconds)
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/apollo/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/apollo/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/apollo/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/README.txt b/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/README.txt
new file mode 100644
index 0000000..d9c32db
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/README.txt
@@ -0,0 +1,51 @@
+This tool serves the following purposes:
+=======================================
+
+1. Swiss Army Tool
+
+   Like a swiss army tool, applicable on any platform: Linux, MAC, Windows.
+
+   Handle big data (Up-to 2 million sampling data). Quick response to drawing chart, Zoom-In/Out.
+ 
+   No dependencies: click and execute. Python installation is not required.
+
+   Offline/Online: applicable with/without internet
+
+   Free Zoom-In, Zoom-Out in any scale. Mark start-end to Zoom-In, double click to Zoom-Out to previous scale.
+
+2. File Format Auto-Detection
+
+   Detect the input file format automatically:
+     data type:                   ascii or binary
+     sampling rate:               detect the sampling rate via the timestamp in the 1st column from the original data, or read from the command line
+     multiple columns:          2 or 3  (e.g. timestamp, current, voltage)
+     single column:               1 column of current, voltage... without any timestamp (not yet implemented)
+   
+   Input file types:
+     Monsoon original csv (ascii) file which is converted from Monsoon PT5 format (5000 samples per second)
+     Monsoon raw csv (ascii) file which is generated by "Manual Assistant Tool"   (5000 / 500 or other samples per second)
+     Audio PCM (binary, 8-Unsigned-Char) file which is generated by audio tool    (44100Hz or given samples per second)
+
+3. Generate html file to draw power chart for power breakdown testing
+
+   Calculate average current for the whole period or a given marked start-end time
+
+   Two filters can be applied based on the given interval(ms) and the voltage threshold(mA)
+
+   Flat the curve: round down any large data to max 2500 (3000 is exception for cutting the interested wave)
+
+   Free Zoom-In and Zoom-Out
+
+4. Generate html to draw audio chart in time-domain for audio signal processing
+
+   Draw the audio chart in time-domain
+
+   Low-Pass, High-Pass, Band-Pass, Noise-Reduction fileters can be applied based on the given frequency(like 1575Hz) (not yet implemented)
+
+   Free Zoom-In and Zoom-Out
+
+
+Syntax:
+=======
+
+power-audio-chart.py --input-file input_file_name --output-file output_file_name --template-file template_file_name
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/power_audio_chart.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/power_audio_chart.py
new file mode 100644
index 0000000..0b7dbf9
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/power_audio_chart.py
@@ -0,0 +1,71 @@
+import mimetypes
+import os
+
+from acts_contrib.test_utils.instrumentation.power.data_graph.utils import CSV_Monsoon_Reader
+from acts_contrib.test_utils.instrumentation.power.data_graph.utils import ACTS_Monsoon_Reader
+from acts_contrib.test_utils.instrumentation.power.data_graph.utils import CSV_PCM_Reader
+
+
+def generate_chart(input_file, output_file, template_file):
+  """Generate power chart by using monsoon data.
+
+       Args:
+           input_file: monsoon data file path.
+           output_file: power chart html file path.
+           template_file: template file path.
+  """
+  input_file_path = os.path.realpath(input_file)
+  output_file_path = os.path.realpath(output_file)
+  template_file_path = os.path.realpath(template_file)
+
+  data = read_data(input_file_path)
+  data_string = data.to_data_string()
+  global_stats = data.get_statistics()
+
+  with open(template_file_path, 'r') as template, open(output_file_path,
+                                                       'w') as output:
+    output.truncate()
+
+    for line in template:
+      if '[$POWERDATA]' in line:
+        line = line.replace('[$POWERDATA]', data_string)
+      elif '[$GLOBALMETRICS]' in line:
+        line = line.replace(
+            '[$GLOBALMETRICS]',
+            str({
+                k: (round(v, 3) if isinstance(v, float) else v)
+                for k, v in global_stats.items()
+            }))
+      output.write(line)
+
+
+def read_data(input_file):
+  """Read monsoon data file."""
+  file_type, _ = mimetypes.guess_type(input_file)
+
+  if file_type is not None and file_type.startswith('text'):
+    delimiter = guess_csv_delimiter(input_file)
+    if delimiter == ',':
+      data = CSV_Monsoon_Reader()
+      data.extract_data_from_file(input_file)
+    else:
+      data = ACTS_Monsoon_Reader()
+      data.extract_data_from_file(input_file)
+  else:
+    data = CSV_PCM_Reader()
+    data.extract_data_from_file(input_file)
+
+  return data
+
+
+def guess_csv_delimiter(file):
+  """Get monsoon data delimiter"""
+  with open(file, 'r') as input_stream:
+    line = input_stream.readline()
+    if ',' in line:
+      delimiter = ','
+    elif ' ' in line:
+      delimiter = ' '
+    else:
+      raise ValueError('Wrong data format!')
+  return delimiter
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/template.html b/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/template.html
new file mode 100644
index 0000000..2b4cb94
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/template.html
@@ -0,0 +1,601 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<script src="https://www.gstatic.com/external_hosted/dygraphs/dygraph-combined.js"></script>
+<!-- script placeholder -->
+<script>
+const INDICATOR_VALUE = 3000;
+
+// Since we roughly have 1 sample every 2 milliseconds
+const SAMPLE_PER_MILLISEC = 0.5;
+
+charting = {};
+
+charting.colors = [ "#4285F4", "#34A853", "#EA4335", "#FBBC05" ];
+
+charting.zoomHistory = [];
+
+charting.globalMetrics = [$GLOBALMETRICS];
+
+charting.data = [$POWERDATA];
+
+charting.padLeft = function(str, length, character){
+  if (character.length != 1){
+   throw "Invalid padding character";
+  }
+
+  let padding = Array(length + 1).join(character);
+  // making sure the input is treated as String
+  str = str + "";
+  return padding.substring(0, length - str.length) + str;
+}
+
+// generates a converter function.
+charting.msToDateConverter = function(includeMicro) {
+   return function(ms){
+      let date = new Date(Math.trunc(ms));
+      let microSec = Math.trunc((ms - Math.trunc(ms)) * 1000);
+
+      let s = charting.padLeft(date.getUTCSeconds(), 2, "0");
+      let m = charting.padLeft(date.getUTCMinutes(), 2, "0");
+      let h = charting.padLeft(date.getUTCHours(),   2, "0");
+      let result = h + ":" + m + ":" + s
+      if(includeMicro){
+         let mms = charting.padLeft(date.getMilliseconds() * 1000 + microSec, 6, "0");
+         result += `.${mms}`;
+      }
+      return result;
+   }
+}
+
+/* gets the index of the best of approximation for a given x-value */
+charting.getIndex = function(g, value){
+   let min = 0;
+   let max = g.numRows() - 1;
+   let middle = parseInt((max + min) / 2);
+
+   let i = 0;
+   while (max > min){
+      if (g.getValue(middle, 0) > value) {
+         max = middle - 1;
+      } else {
+         min = middle + 1;
+      }
+      middle = parseInt((max + min) / 2);
+   }
+   return middle;
+}
+
+charting.getChartMetrics = function(g) {
+   let timeColumn = 0;
+   let lower = charting.getIndex(g, g.xAxisRange()[0]);
+   let upper = charting.getIndex(g, g.xAxisRange()[1]);
+   let metrics = [];
+
+   for (let valueColumn = 1; valueColumn < g.numColumns(); valueColumn++) {
+      let sum = 0;
+      let minValue = Infinity;
+      let maxValue = -Infinity;
+      for (let i = lower; i <= upper; i++){
+         let value = g.getValue(i, valueColumn);
+         if (value < INDICATOR_VALUE) {
+            sum += value;
+            minValue = minValue < value ? minValue : value;
+            maxValue = value < maxValue ? maxValue : value;
+         }
+      }
+      let average  = sum / (upper - lower + 1);
+      let duration = g.getValue(upper, timeColumn) - g.getValue(lower, timeColumn) + 1;
+      metrics.push({
+         average  : Math.round(average  * 1000) / 1000,
+         maxValue : Math.round(maxValue * 1000) / 1000,
+         minValue : Math.round(minValue * 1000) / 1000,
+         duration : duration,
+      });
+   }
+
+   return metrics;
+}
+
+charting.toChartInfoHtml = function(metrics, color) {
+   return `
+   <div class="chart-info">
+      <div style="color:${color}">Average:   <span>${metrics.average}</span></div>
+      <div style="color:${color}">Max:       <span>${metrics.maxValue}</span></div>
+      <div style="color:${color}">Min:       <span>${metrics.minValue}</span></div>
+      <div style="color:${color}">Duration:  <span>${(metrics.duration / 1000).toFixed(3)} s</span></div>
+   </div>
+   `;
+}
+
+charting.metricsUpdateTimeoutId = 0;
+charting.updateMetrics = function(g, isInitial) {
+   clearTimeout(charting.metricsUpdateTimeoutId);
+   charting.metricsUpdateTimeoutId = setTimeout(function(){
+      let metrics = isInitial ? [ charting.globalMetrics ] : charting.getChartMetrics(g)
+      let statisticsContainer = document.getElementById("statistics");
+      let innerHTML = "";
+      for(let valueColumn = 0; valueColumn < metrics.length; valueColumn++) {
+         if (g.visibility()[valueColumn]) {
+            let metricInfo = charting.toChartInfoHtml(metrics[valueColumn], g.colors_[valueColumn]);
+            innerHTML += metricInfo;
+         }
+      }
+      statisticsContainer.innerHTML = innerHTML;
+   }, 50);
+}
+
+charting.drawCallback = function(g, isInitial) {
+   charting.updateMetrics(g, isInitial);
+}
+
+charting.zoomCallback = function(xMin, xMax, yRanges) {
+   let [xGlobalMin, xGlobalMax] = charting.graph.xAxisExtremes();
+   if (xMin === xGlobalMin && xMax === xGlobalMax) {
+      charting.zoomHistory = [];
+   } else if (charting.zoomHistory.length === 0) {
+      charting.zoomHistory.push([xMin, xMax]);
+   } else {
+      let [topXmin, topXmax] = charting.zoomHistory[charting.zoomHistory.length - 1];
+      let topStackDistance = topXmax - topXmin;
+      let distance = xMax - xMin;
+
+      if (distance < topStackDistance) {
+         charting.zoomHistory.push([xMin, xMax]);
+      }
+   }
+}
+
+charting.restorePosition = function() {
+   charting.graph.resetZoom();
+   charting.zoomHistory = [];
+}
+
+charting.toogleFixedRange = function(e) {
+   if (e.target.checked) {
+      charting.fixRange();
+      return;
+   }
+   charting.graph.updateOptions({ valueRange: [null, null] });
+}
+
+charting.toogleFilterOptions1 = function(e) {
+   if (e.target.checked) {
+      charting.elements.peakFilterOptions1.style.display = "block";
+      charting.graph.setVisibility(1, true);
+   } else {
+      charting.elements.peakFilterOptions1.style.display = "none";
+      charting.graph.setVisibility(1, false);
+   }
+}
+
+charting.toogleFilterOptions2 = function(e) {
+   if (e.target.checked) {
+      charting.elements.peakFilterOptions2.style.display = "block";
+      charting.graph.setVisibility(2, true);
+   } else {
+      charting.elements.peakFilterOptions2.style.display = "none";
+      charting.graph.setVisibility(2, false);
+   }
+}
+
+charting.toogleData = function(e) {
+   charting.graph.setVisibility(0, e.target.checked);
+}
+
+charting.fixRange = function() {
+   if (typeof charting.initialMetrics == "undefined" ){
+     charting.initialMetrics = charting.getChartMetrics(charting.graph);
+   }
+
+   let metrics = charting.initialMetrics;
+   let visibleRange = metrics.maxValue - metrics.minValue;
+   let buffer = visibleRange / 10; // 10%
+   let min = metrics.minValue - buffer;
+   let max = metrics.maxValue + buffer;
+   charting.graph.updateOptions({ valueRange: [min, max] });
+}
+
+charting.addXLabelPlaceholder = function() {
+   charting.elements.labelsDiv.className = "empty";
+}
+
+charting.removeXLabelPlaceholder = function() {
+   charting.elements.labelsDiv.className = "";
+}
+
+charting.filterPeaks = function(element, dataIndex) {
+   console.log("start");
+   let lowerThreshold = element.parentElement.querySelector("#lower-threshold").value;
+   let upperThreshold = element.parentElement.querySelector("#upper-threshold").value;
+   let frequency = element.parentElement.querySelector("#frequency").value;
+
+   let peaks = charting.findPeaks(lowerThreshold, upperThreshold, frequency);
+
+   let currentData = charting.data.map(entry => entry[1]);
+   for (let peak of peaks) {
+      for (let point = peak.start; point <= peak.end; point++) {
+         currentData[point] = charting.globalMetrics.average;
+      }
+   }
+
+   for (let i = 0; i < charting.data.length; i++) {
+      charting.data[i][dataIndex] = currentData[i];
+   }
+
+   charting.graph.updateOptions({
+      file: charting.data,
+   });
+
+   console.log("done");
+}
+
+charting.findPeaks = function(lowerThreshold, upperThreshold, frequency) {
+   const SAMPLE_SIZE = 4;
+   const CONSECUTIVE_PEAKS = 3;
+
+   let index = 0;
+   let peaks = [];
+   let runningSum = null;
+   let consecutivePeaks = 0;
+   while (index < charting.data.length) {
+      if (peaks.length === 0) {
+         runningSum = charting.findWindowSumShiftLeft(index - SAMPLE_SIZE/2, index + SAMPLE_SIZE/2, runningSum);
+         let average = runningSum/(SAMPLE_SIZE + 1);
+         if (average >= charting.globalMetrics.average) {
+            let peak = charting.findPeakInfo(index);
+            if (lowerThreshold <= charting.data[peak.highestPoint][1] && charting.data[peak.highestPoint][1] <= upperThreshold) {
+               peaks.push(peak);
+               index = peak.highestPoint + Math.floor(frequency * SAMPLE_PER_MILLISEC);
+               consecutivePeaks++;
+            } else {
+               index = peak.end + SAMPLE_SIZE / 2;
+            }
+         } else {
+            index++;
+         }
+      } else {
+         // We will look around +- 10% from the frequency
+         let surveyWindow = Math.floor(frequency / 10);
+         let lowerBound = index - Math.floor(surveyWindow * SAMPLE_PER_MILLISEC);
+         let upperBound = index + Math.floor(surveyWindow * SAMPLE_PER_MILLISEC);
+         let found = false;
+         runningSum = null;
+         for (let i = lowerBound; !found && i <= upperBound && i < charting.data.length; i++) {
+            runningSum = charting.findWindowSumShiftLeft(i - SAMPLE_SIZE/2, i + SAMPLE_SIZE/2, runningSum);
+            let average = runningSum / (SAMPLE_SIZE + 1);
+            if (average > charting.globalMetrics.average) {
+               let peak = charting.findPeakInfo(i);
+               if (lowerThreshold <= charting.data[peak.highestPoint][1] && charting.data[peak.highestPoint][1] <= upperThreshold) {
+                  peaks.push(peak);
+                  found = true;
+                  index = peak.highestPoint + Math.floor(frequency * SAMPLE_PER_MILLISEC);
+                  consecutivePeaks++;
+               } else {
+                  i = peak.end + SAMPLE_SIZE / 2;
+               }
+            }
+         }
+
+         if (!found) {
+            if (consecutivePeaks < CONSECUTIVE_PEAKS) {
+               index = peaks[0].end + SAMPLE_SIZE / 2;
+               runningSum = null;
+               consecutivePeaks = 0;
+               peaks = [];
+            } else {
+               index = index + Math.floor(frequency * SAMPLE_PER_MILLISEC);
+            }
+         }
+      }
+   }
+
+   return peaks;
+}
+
+charting.findPeakInfo = function(peak) {
+   // We make it so that the WINDOW_SIZE to calculate peak start and end to be equal to the
+   // sample size used to calculate sum. This will narrow the peak down.
+   const WINDOW_SIZE = 3;
+   let start = charting.findPeakStart(peak, WINDOW_SIZE);
+   let end = charting.findPeakEnd(peak, WINDOW_SIZE);
+
+   let highestPoint = start;
+   for (let i = start; i < charting.data.length && i <= end; i++) {
+      if (charting.data[i][1] < INDICATOR_VALUE && charting.data[highestPoint][1] < charting.data[i][1]) {
+         highestPoint = i;
+      }
+   }
+
+   return {start, end, highestPoint};
+}
+
+charting.findPeakStart = function(peak, windowSize) {
+   let start = peak - windowSize + 1;
+   let end = peak;
+   let runningSum = null;
+   let average = 0;
+
+   do {
+      runningSum = charting.findWindowSumShiftRight(start, end, runningSum);
+      average = runningSum / windowSize;
+      start--;
+      end--;
+   } while (average > charting.globalMetrics.average && start >= 0)
+   return start >= 0 ? start + 1 : 0;
+}
+
+charting.findPeakEnd = function(peak, windowSize) {
+   let start = peak;
+   let end = peak + windowSize - 1;
+   let runningSum = null;
+   let average = 0;
+
+   do {
+      runningSum = charting.findWindowSumShiftLeft(start, end, runningSum);
+      average = runningSum / windowSize;
+      start++;
+      end++;
+   } while (average > charting.globalMetrics.average && end < charting.data.length)
+   return end < charting.data.length ? end - 1: charting.data.length;
+}
+
+charting.findWindowSumShiftLeft = function(start, end, runningSum) {
+  try {
+   if (start < 0) {
+      start = 0;
+   }
+
+   if (end >= charting.data.length) {
+      end = charting.data.length;
+   }
+
+   if (runningSum === null) {
+         runningSum = 0;
+         for (let i = start; i < charting.data.length && i <= end; i++) {
+            if (charting.data[i][1] < INDICATOR_VALUE)
+               runningSum += charting.data[i][1];
+         }
+      } else {
+         if (start > 0 && charting.data[start-1][1] < INDICATOR_VALUE)
+            runningSum -= charting.data[start-1][1];
+
+         if (end < charting.data.length && charting.data[end][1] < INDICATOR_VALUE)
+            runningSum += charting.data[end][1];
+      }
+
+      return runningSum;
+  } catch (error) {
+     console.log(error);
+     console.log(start, end, charting.data.length);
+  }
+}
+
+charting.findWindowSumShiftRight = function(start, end, runningSum) {
+   if (start < 0) {
+    start = 0;
+   }
+
+  if (end >= charting.data.length) {
+    end = charting.data.length;
+  }
+
+  if (runningSum === null) {
+      runningSum = 0;
+      for (let i = start; i < charting.data.length && i <= end; i++) {
+         if (charting.data[i][1] < INDICATOR_VALUE)
+            runningSum += charting.data[i][1];
+      }
+   } else {
+      if (start >= 0 && charting.data[start][1] < INDICATOR_VALUE)
+         runningSum += charting.data[start][1];
+
+      if (end < charting.data.length - 1 && charting.data[end+1][1] < INDICATOR_VALUE)
+         runningSum -= charting.data[end+1][1];
+   }
+
+   return runningSum;
+}
+
+charting.interactionModel = Dygraph.defaultInteractionModel;
+charting.interactionModel.dblclick = function(event, graph, context) {
+   if (context.cancelNextDbclick) {
+      context.cancelNextDbclick = false;
+      return;
+   }
+
+   if (event.altKey || event.shiftKey) {
+      return;
+   }
+
+   // pop current view out
+   if (charting.zoomHistory.length !== 0) {
+      charting.zoomHistory.pop();
+      if (charting.zoomHistory.length === 0) {
+         graph.resetZoom();
+      } else {
+         graph.updateOptions({
+            dateWindow: charting.zoomHistory[charting.zoomHistory.length - 1]
+         });
+      }
+   }
+}
+
+charting.buildChart = function() {
+   charting.elements = {
+      graphDiv      : document.getElementById("graph-div"),
+      labelsDiv     : document.getElementById("labels-div"),
+      rangeCheckbox : document.getElementById("range-checkbox"),
+      dataFilterCheckbox: document.getElementById("data-filter-checkbox"),
+      peakFilterCheckbox1: document.getElementById("peak-filter-checkbox-1"),
+      peakFilterOptions1: document.getElementById("peak-filter-options-1"),
+      peakFilterCheckbox2: document.getElementById("peak-filter-checkbox-2"),
+      peakFilterOptions2: document.getElementById("peak-filter-options-2"),
+  }
+
+  let graph = new Dygraph(
+      charting.elements.graphDiv,
+      charting.data,
+      {
+         legend               : 'always',
+         colors               : charting.colors,
+         labels               : ['Time [s]', 'Amplitude', 'Peak Filter 1', 'Peak Filter 2'],
+         labelsDiv            : charting.elements.labelsDiv,
+         labelsSeparateLines  : true,
+         visibility           : [true, false, false],
+         axisLabelFontSize    : 12,
+         axes: {
+            x: {
+               axisLabelFormatter: charting.msToDateConverter(false),
+               valueFormatter: charting.msToDateConverter(true),
+            }
+         },
+         drawCallback       : charting.drawCallback,
+         highlightCallback  : charting.removeXLabelPlaceholder,
+         unhighlightCallback: charting.addXLabelPlaceholder,
+         zoomCallback       : charting.zoomCallback,
+         interactionModel   : charting.interactionModel,
+      });
+   charting.graph = graph;
+
+   charting.elements.rangeCheckbox.onchange = charting.toogleFixedRange;
+
+   charting.elements.dataFilterCheckbox.onchange = charting.toogleData;
+   charting.elements.peakFilterCheckbox1.onchange = charting.toogleFilterOptions1;
+   charting.elements.peakFilterCheckbox2.onchange = charting.toogleFilterOptions2;
+   charting.elements.peakFilterOptions1.style.display = "none";
+   charting.elements.peakFilterOptions2.style.display = "none";
+
+   graph.ready(function(g){
+      charting.addXLabelPlaceholder();
+   });
+}
+
+document.addEventListener('DOMContentLoaded', charting.buildChart);
+</script>
+<style>
+   .chart-info {
+      overflow: hidden;
+      float: left;
+      padding-left: 10px;
+      min-width: 10%;
+      padding: 15px;
+   }
+
+   .chart-info div {
+      font-weight: bold;
+   }
+
+   .chart-info div span {
+      color: black;
+      font-weight: normal;
+      display: block;
+      float: right;
+   }
+
+   .clear {
+      clear: both;
+      height: 10px;
+   }
+
+   html {
+      font-family: monospace;
+   }
+
+   #labels-div.empty:before {
+      content: '-- time\00000A'
+   }
+
+   #labels-div:before {
+      white-space: pre;
+      content: 'time: ';
+      font-weight: bold;
+   }
+
+   .dygraph-label.dygraph-xlabel,
+   .dygraph-label.dygraph-ylabel {
+      font-size: 1em;
+      font-weight: bold;
+      color: #757575;
+   }
+
+   #graph-title {
+      text-align: left;
+      font-size: 1em;
+      font-weight: bold;
+      color: #000;
+   }
+
+   #graph-div {
+      height: 80%;
+   }
+
+   #container {
+      width: 100%;
+   }
+
+   #left-panel {
+      overflow: hidden;
+      min-height: 100%;
+   }
+
+   #right-panel {
+      float: right;
+      width: 200px;
+      min-height: 100%;
+   }
+
+   #user-tools {
+      margin-top: 20px;
+   }
+
+   #filter-info {
+      display: none;
+   }
+</style>
+</head>
+<body>
+<div id="container">
+   <div id="right-panel">
+      <div id="labels-div" class="dygraph-legend"></div>
+      <div id="user-tools">
+         <label><input id="range-checkbox" type="checkbox"> Fixed Y axis </label>
+         <br>
+         <a href="https://chrome.google.com/webstore/detail/snipit/ehoadneljpdggcbbknedodolkkjodefl" target="_blank">or use sniplt</a>
+         <br>
+         <label><input id="data-filter-checkbox" type="checkbox" checked>Current Data</label>
+         <br>
+         <label><input id="peak-filter-checkbox-1" type="checkbox">Peak Filter Data - 1</label>
+         <div id="peak-filter-options-1">
+            <label>Upper Threshold</label>
+            <input id="upper-threshold" type="number" value="900"><br>
+            <label>Lower Threshold</label>
+            <input id="lower-threshold" type="number" value="400"><br>
+            <label>Frequency</label>
+            <input id="frequency" type="number" value="60000">ms<br>
+            <button onclick="charting.filterPeaks(this, 2)">Draw</button>
+         </div>
+         <label><input id="peak-filter-checkbox-2" type="checkbox">Peak Filter Data - 2</label>
+         <div id="peak-filter-options-2">
+            <label>Upper Threshold</label>
+            <input id="upper-threshold" type="number" value="250"><br>
+            <label>Lower Threshold</label>
+            <input id="lower-threshold" type="number" value="100"><br>
+            <label>Frequency</label>
+            <input id="frequency" type="number" value="1250">ms<br>
+            <button onclick="charting.filterPeaks(this, 3)">Draw</button>
+         </div>
+      </div>
+      <p><b>Drag:</b> Zoom In</p>
+      <p><b>Double Click:</b> Zoom Out</p>
+      <p><b>Shift Drag:</b> Move</p>
+      <button onclick="charting.restorePosition()">Restore View</button>
+   </div>
+   <div id="left-panel" >
+      <div id="graph-div" ></div>
+      <div id="statistics">
+      </div>
+   </div>
+</div>
+</body>
+</html>
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/utils.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/utils.py
new file mode 100644
index 0000000..cb1acc5
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/data_graph/utils.py
@@ -0,0 +1,147 @@
+import csv
+
+class CSV_Reader_Base(object):
+  """Base class that implements common functionality for
+  reading csv file.
+  """
+
+  def __init__(self):
+    self.data = []
+
+  def extract_data_from_file(self, input_file):
+    raise NotImplementedError
+
+  def to_data_string(self):
+    data_string = '['
+    for entry in self.data:
+      data_string += f'[{entry[0]},{entry[1]:.2f},0,0],\n'
+    data_string += ']'
+    return data_string
+
+  def get_statistics(self):
+    current_data = [entry[1] for entry in self.data]
+    return {
+        'average': sum(current_data) / len(current_data),
+        'maxValue': max(current_data),
+        'minValue': min(current_data),
+        'duration': self.data[-1][0] - self.data[0][0]
+    }
+
+
+class ACTS_Monsoon_Reader(CSV_Reader_Base):
+  """Class for reading acts monsoon file"""
+
+  def extract_data_from_file(self, input_file):
+    with open(input_file, 'r') as csv_file:
+      csv_monsoon = csv.DictReader(csv_file, delimiter=' ', fieldnames=['Time (s)', 'Current (A)'])
+      self.read_acts_monsoon_csv(csv_monsoon)
+
+  def read_acts_monsoon_csv(self, csv_monsoon):
+    self.data = []
+    begin_timestamp = None
+    row_no = 0
+    reduce_to_10_percent = True
+
+    for data_entry in csv_monsoon:
+      if data_entry['Time (s)'] is None or \
+          data_entry['Current (A)'] is None:
+        print(f'Missing data entry at {row_no + 1}! Please check your data input.')
+      else:
+        timestamp = int(float(data_entry['Time (s)']) * 1000)
+        current = float(data_entry['Current (A)']) * 1000
+
+        if row_no == 1:
+          time_delta = timestamp - begin_timestamp
+          if time_delta < 2:
+            reduce_to_10_percent = True
+          else:
+            reduce_to_10_percent = False
+
+        if row_no % 10 == 0 or not reduce_to_10_percent:
+          if current < 0:
+            current = 0
+
+          if begin_timestamp is None:
+            begin_timestamp = timestamp
+
+          timestamp = timestamp - begin_timestamp
+          self.data.append([timestamp, current])
+
+        row_no += 1
+
+class CSV_Monsoon_Reader(CSV_Reader_Base):
+  """Class for reading vzw dou manual assistant tool monsoon file"""
+
+  def __init__(self):
+    super().__init__()
+    self._INDICATOR_VALUE = 3000
+
+  def extract_data_from_file(self, input_file):
+    with open(input_file, 'r') as csv_file:
+      csv_monsoon = csv.DictReader(csv_file)
+      self.read_monsoon_csv(csv_monsoon)
+
+  def read_monsoon_csv(self, csv_monsoon):
+    self.data = []
+    begin_timestamp = None
+    row_no = 0
+    reduce_to_10_percent = True
+
+    for data_entry in csv_monsoon:
+      if data_entry['Time (s)'] is None or \
+          data_entry['Main Avg Current (mA)'] is None:
+        # We need to count the header, that's why it's + 2 and not + 1
+        print(f'Missing data entry at {row_no + 2}! Please check your data input.')
+      else:
+        timestamp = float(data_entry['Time (s)']) * 1000
+        current = float(data_entry['Main Avg Current (mA)'])
+
+        if row_no == 1:
+          time_delta = timestamp - begin_timestamp
+          if time_delta < 2:
+            reduce_to_10_percent = True
+          else:
+            reduce_to_10_percent = False
+
+        if row_no % 10 == 0 or not reduce_to_10_percent:
+          if current < 0:
+            current = 0
+          elif current > self._INDICATOR_VALUE:
+            current = 2500
+
+          if begin_timestamp is None:
+            begin_timestamp = timestamp
+
+          timestamp = timestamp - begin_timestamp
+          self.data.append([timestamp, current])
+
+        row_no += 1
+
+  def get_statistics(self):
+    current_data = [entry[1] for entry in self.data if entry[1] < self._INDICATOR_VALUE]
+    return {
+        'average': sum(current_data) / len(current_data),
+        'maxValue': max(current_data),
+        'minValue': min(current_data),
+        'duration': self.data[-1][0] - self.data[0][0]
+    }
+
+class CSV_PCM_Reader(CSV_Reader_Base):
+  """Class for monsoon data sampling"""
+
+  def __init__(self):
+    super().__init__()
+    self._PCM_FREQUENCY = 44100
+
+  def extract_data_from_file(self, input_file):
+    self.data = []
+    microsecond_per_frame = 1 / self._PCM_FREQUENCY * 1000
+
+    with open(input_file, 'rb') as pcm_input:
+      raw_data = pcm_input.read(1)
+      timestamp = 0
+      while raw_data:
+        amplitude = ord(raw_data)
+        self.data.append([timestamp, amplitude])
+        timestamp += microsecond_per_frame
+        raw_data = pcm_input.read(1)
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/instrumentation_power_test.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/instrumentation_power_test.py
new file mode 100644
index 0000000..4d97fa5
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/instrumentation_power_test.py
@@ -0,0 +1,690 @@
+#!/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 shutil
+import tempfile
+import time
+import json
+
+from acts import asserts
+from acts import context
+from acts import signals
+from acts.controllers import monsoon as monsoon_controller
+from acts.controllers import power_monitor as power_monitor_lib
+from acts.controllers.android_device import SL4A_APK_NAME
+from acts.controllers.android_lib.errors import AndroidDeviceError
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.metrics.loggers.bounded_metrics import BoundedMetricsLogger
+from acts_contrib.test_utils.instrumentation import instrumentation_proto_parser as proto_parser
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import AppInstaller
+from acts_contrib.test_utils.instrumentation.device.apps.google_apps_test_utils import GoogleAppsTestUtils
+from acts_contrib.test_utils.instrumentation.device.apps.permissions import PermissionsUtil
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import goog
+from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import DEFAULT_INSTRUMENTATION_LOG_OUTPUT
+from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import InstrumentationTestCommandBuilder
+from acts_contrib.test_utils.instrumentation.instrumentation_base_test import InstrumentationBaseTest
+from acts_contrib.test_utils.instrumentation.instrumentation_base_test import InstrumentationTestError
+from acts_contrib.test_utils.instrumentation.instrumentation_proto_parser import DEFAULT_INST_LOG_DIR
+from acts_contrib.test_utils.instrumentation.power.data_graph import power_audio_chart
+from acts_contrib.test_utils.instrumentation.power.thresholds import AbsoluteThresholds
+
+ACCEPTANCE_THRESHOLD = 'acceptance_threshold'
+DEFAULT_DEVICE_STABILIZATION_TIME = 300
+DEFAULT_PUSH_FILE_TIMEOUT = 180
+DEFAULT_WAIT_FOR_DEVICE_TIMEOUT = 180
+POLLING_INTERVAL = 0.5
+RESET_SLEEP_TIME = 0.5
+
+AUTOTESTER_LOG = 'autotester.log'
+DISCONNECT_USB_FILE = 'disconnectusb.log'
+SCREENSHOTS_DIR = 'test_screenshots'
+
+_NETWORK_TYPES = {
+    '2g': 1,
+    '3g': 0,
+    'lte': 12
+}
+
+
+class InstrumentationPowerTest(InstrumentationBaseTest):
+    """Instrumentation test for measuring and validating power metrics.
+
+    Params:
+        metric_logger: Blackbox metric logger used to store test metrics.
+        _instr_cmd_builder: Builder for the instrumentation command
+    """
+
+    def __init__(self, configs):
+        super().__init__(configs)
+
+        self.blackbox_logger = BlackboxMappedMetricLogger.for_test_case()
+        self.bounded_metric_logger = BoundedMetricsLogger.for_test_case()
+        self._test_apk = None
+        self._sl4a_apk = None
+        self._instr_cmd_builder = None
+        self.prefer_bits_over_monsoon = False
+        self.generate_chart = False
+        self.tigertail = None
+        self._google_apps_test_utils = None
+        self._permissions_util = None
+
+        # When using tigertail, sets this value to True in the test or test_setup
+        self.use_tigertail_if_available = False
+
+    def setup_class(self):
+        super().setup_class()
+        power_monitor_lib.update_registry(self.user_params)
+        self.power_monitors = self.pick_power_monitor()
+        self.power_monitor = self.power_monitors[0]
+        if hasattr(self, 'tigertails'):
+            self.tigertail = self.tigertails[0]
+
+    def pick_power_monitor(self):
+        there_are_monsoons = hasattr(self, 'monsoons')
+        there_are_bitses = hasattr(self, 'bitses')
+        asserts.assert_true(there_are_bitses or there_are_monsoons,
+                            'at least one power monitor must be defined')
+        # use bits if there are bitses defined and is preferred
+        # use bits if it is not possible to use monsoons
+        use_bits = there_are_bitses and (
+            self.prefer_bits_over_monsoon or not there_are_monsoons)
+        if use_bits and there_are_monsoons:
+            # the monsoon controller interferes with bits.
+            monsoon_controller.destroy(self.monsoons)
+        if use_bits:
+            power_monitors = self.bitses
+        else:
+            power_monitors = [power_monitor_lib.PowerMonitorMonsoonFacade(
+                monsoon) for monsoon in self.monsoons]
+            self.generate_chart = True
+        return power_monitors
+
+    def setup_test(self):
+        """Test setup"""
+        super().setup_test()
+        self._setup_power_monitor()
+        self._prepare_device()
+        self._instr_cmd_builder = self.power_default_instr_command_builder()
+
+    def teardown_test(self):
+        """Test teardown"""
+        super().teardown_test()
+        self.power_monitor.teardown()
+
+    def _prepare_device(self):
+        """Prepares the device for power testing."""
+        super()._prepare_device()
+        self._cleanup_test_files()
+        self._permissions_util = PermissionsUtil(
+            self.ad_dut,
+            self.get_file_from_config('permissions_apk'))
+        self._permissions_util.grant_all()
+        self._google_apps_test_utils = GoogleAppsTestUtils(
+            self.ad_dut,
+            self.get_file_from_config('google_apps_test_utils_apk'))
+        self._google_apps_test_utils.prevent_playstore_auto_updates()
+        self._install_test_apk()
+
+    def _cleanup_device(self):
+        """Clean up device after power testing."""
+        if self._test_apk:
+            self._test_apk.uninstall()
+        if self._google_apps_test_utils:
+            self._google_apps_test_utils.close()
+        if self._permissions_util:
+            self._permissions_util.close()
+        self._pull_test_files()
+        self._cleanup_test_files()
+
+    def base_device_configuration(self):
+        """Run the base setup commands for power testing."""
+        self.log.info('Running base device setup commands.')
+
+        self.ad_dut.adb.ensure_root()
+        self.adb_run(common.dismiss_keyguard)
+        self.ad_dut.ensure_screen_on()
+        self.adb_run(common.screen_always_on.toggle(True))
+        self.adb_run(common.menu_button)
+        self.adb_run(common.home_button)
+        self.adb_run(common.airplane_mode.toggle(True))
+
+        self.set_screen_brightness_level()
+        self.adb_run(common.screen_adaptive_brightness.toggle(False))
+        self.adb_run(common.location_gps.toggle(False))
+        self.adb_run(common.location_network.toggle(False))
+        self.adb_run(common.auto_time.toggle(False))
+        self.adb_run(common.wifi_global.toggle(False))
+        self.adb_run(common.auto_timezone.toggle(False))
+        self.adb_run(common.wifi_state.toggle(False))
+        self.adb_run(common.bluetooth.toggle(False))
+        self.adb_run(common.crashed_activities)
+        self.adb_run(common.dismiss_keyguard)
+        self.adb_run(common.screen_always_on.toggle(True))
+        self.adb_run(common.menu_button)
+        self.adb_run(common.home_button)
+        self.adb_run(common.airplane_mode.toggle(True))
+        self.set_screen_brightness_level()
+
+        self.adb_run(common.notification_led.toggle(False))
+        self.adb_run(common.screen_timeout_ms.set_value(1800000))
+        self.adb_run(common.auto_rotate.toggle(False))
+        self.adb_run(common.screen_adaptive_brightness.toggle(False))
+        self.adb_run(common.skip_gesture.toggle(False))
+        self.adb_run(common.screensaver.toggle(False))
+        self.adb_run(common.doze_pulse_on_pick_up.toggle(False))
+        self.adb_run(goog.edge_sensor.toggle(False))
+        self.adb_run(common.system_navigation_keys_enabled.toggle(False))
+        self.adb_run(common.camera_lift_trigger_enabled.toggle(False))
+        self.adb_run(common.doze_wake_screen_gesture.toggle(False))
+        self.adb_run(goog.edge_sensor_wakeup.toggle(False))
+        self.adb_run(common.doze_always_on.toggle(False))
+        self.adb_run(common.location_mode.toggle(False))
+        self.adb_run(common.single_tap_gesture.toggle(False))
+        self.adb_run(common.camera_double_twist_to_flip_enabled.toggle(False))
+        self.adb_run(common.silence_gesture.toggle(False))
+        self.adb_run(common.location_gps.toggle(False))
+        self.adb_run(common.location_network.toggle(False))
+        self.adb_run(goog.edge_sensor_alerts.toggle(False))
+
+        self.adb_run(common.aware_enabled.toggle(False))
+        self.adb_run(
+            common.camera_double_tap_power_gesture_disabled.toggle(True))
+        self.adb_run(common.doze_mode.toggle(False))
+        self.adb_run(common.wake_gesture.toggle(False))
+        self.adb_run(common.double_tap_gesture.toggle(False))
+        self.adb_run(common.battery_saver_trigger.set_value(0))
+        self.adb_run(common.auto_time.toggle(False))
+        self.adb_run(common.wifi_global.toggle(False))
+        self.adb_run(common.auto_timezone.toggle(False))
+        self.adb_run(common.battery_saver_mode.toggle(False))
+
+        self.adb_run(goog.magic_tether.toggle(False))
+        self.adb_run(goog.location_collection.toggle(False))
+        self.adb_run(goog.compact_location_log.toggle(True))
+        self.adb_run(goog.cast_broadcast.toggle(False))
+        self.adb_run(goog.ocr.toggle(False))
+        if self._test_options.get('set_gms_phenotype_flag', default=True):
+            self.adb_run(goog.phenotype.toggle(True))
+        self.adb_run(goog.icing.toggle(False))
+
+        self.adb_run('echo 1 > /d/clk/debug_suspend')
+        self.adb_run(common.wifi_state.toggle(False))
+        self.adb_run(common.bluetooth.toggle(False))
+        self.adb_run(common.nfc.toggle(False))
+        self.adb_run(common.enable_full_batterystats_history)
+        self.adb_run(common.disable_doze)
+        self.adb_run(goog.disable_playstore)
+        self.adb_run(goog.disable_musiciq)
+        self.adb_run(goog.enable_musiciq)
+        self.adb_run(common.home_button)
+        self.adb_run(common.disable_live_captions)
+        self.adb_run(common.disable_super_packs)
+        self.adb_run(common.disable_pixellogger)
+        self.adb_run(goog.hotword.toggle(False))
+        self.adb_run(goog.disable_volta)
+        self.adb_run(common.crashed_activities)
+
+        try:
+            self.ad_dut.reboot(timeout=180)
+        except (AndroidDeviceError, TimeoutError):
+            raise signals.TestFailure('Device did not reboot successfully.')
+        self.log.debug('Giving device extra minute after booting before '
+                       'starting instrumentation test.')
+        time.sleep(60)
+
+        self.adb_run(goog.location_off_warning_dialog.toggle(False))
+        self.adb_run(common.hidden_api_exemption)
+        if self.file_exists(common.MOISTURE_DETECTION_SETTING_FILE):
+            self.adb_run(common.disable_moisture_detection)
+        self.adb_run(common.stop_moisture_detection)
+        self.adb_run(common.ambient_eq.toggle(False))
+
+        # Test harness flag
+        harness_prop = 'getprop ro.test_harness'
+        # command would fail if properties were previously set, therefore it
+        # needs to be verified first
+        if self.adb_run(harness_prop)[harness_prop] != '1':
+            self.adb_run('echo ro.test_harness=1 >> /data/local.prop')
+            self.adb_run('chmod 644 /data/local.prop')
+            self.adb_run(common.test_harness.toggle(True))
+
+        # Calling
+        disable_dialing_prop = 'getprop ro.telephony.disable-call'
+        # command would fail if property was previously set, therefore it needs
+        # to be verified first.
+        if self.adb_run(disable_dialing_prop)[disable_dialing_prop] != 'true':
+            self.adb_run(
+                'echo ro.telephony.disable-call=true >> /data/local.prop')
+            self.adb_run('chmod 644 /data/local.prop')
+            self.adb_run(common.disable_dialing.toggle(True))
+
+    def set_screen_brightness_level(self):
+        """set screen brightness level"""
+        brightness_level = None
+        if 'brightness_level' in self._test_options:
+            brightness_level = self._test_options['brightness_level']
+
+        if brightness_level is None:
+            raise ValueError('no brightness level defined (or left as None) '
+                             'and it is needed.')
+
+        self.adb_run(common.screen_brightness.set_value(brightness_level))
+
+    def _setup_power_monitor(self, **kwargs):
+        """Set up the Monsoon controller for this testclass/testcase."""
+        monsoon_config = self._test_options.get_config('Monsoon')
+        self.power_monitor.setup(monsoon_config=monsoon_config)
+
+    def _uninstall_sl4a(self):
+        """Stops and uninstalls SL4A if it is available on the DUT"""
+        self.ad_dut.log.info('Stopping and uninstalling SL4A if available.')
+        self.ad_dut.stop_services()
+        # Uninstall SL4A
+        self._sl4a_apk = AppInstaller.pull_from_device(
+            self.ad_dut, SL4A_APK_NAME, tempfile.mkdtemp(prefix='sl4a'))
+        if self._sl4a_apk:
+            self._sl4a_apk.uninstall()
+        time.sleep(1)
+
+    def _reinstall_sl4a(self):
+        """Re-installs and starts SL4A (if it is available)"""
+        self.ad_dut.adb.wait_for_device(timeout=DEFAULT_WAIT_FOR_DEVICE_TIMEOUT)
+        self.ad_dut.log.debug('device found; allowing 10 seconds for system '
+                              'services to start')
+        time.sleep(10)
+        # Reinstall SL4A
+        if not self.ad_dut.is_sl4a_installed() and self._sl4a_apk:
+            self._sl4a_apk.install()
+            shutil.rmtree(os.path.dirname(self._sl4a_apk.apk_path))
+            self._sl4a_apk = None
+        self.ad_dut.start_services()
+
+        # Release wake lock to put device into sleep.
+        if self.ad_dut.droid:
+            self.ad_dut.droid.goToSleepNow()
+
+        self.ad_dut.log.info('SL4A reinstalled and started.')
+
+    def _install_test_apk(self):
+        """Installs test apk on the device."""
+        test_apk_file = self.get_file_from_config('test_apk')
+        self._test_apk = AppInstaller(self.ad_dut, test_apk_file)
+        self._test_apk.install('-g')
+        if not self._test_apk.is_installed():
+            raise InstrumentationTestError('Failed to install test APK.')
+
+    def _pull_test_files(self):
+        """Pull test-generated files from the device onto the log directory."""
+        dest = self.ad_dut.device_log_path
+        self.ad_dut.log.info('Pulling test generated files to %s.' % dest)
+        for file_name in [DEFAULT_INSTRUMENTATION_LOG_OUTPUT, SCREENSHOTS_DIR]:
+            src = os.path.join(self.ad_dut.external_storage_path, file_name)
+            if self.ad_dut.adb.shell('ls %s || true' % src):
+                self.ad_dut.pull_files(src, dest)
+
+    def _cleanup_test_files(self):
+        """Remove test-generated files from the device."""
+        self.ad_dut.log.info('Cleaning up test generated files.')
+        for file_name in [DISCONNECT_USB_FILE, DEFAULT_INST_LOG_DIR,
+                          DEFAULT_INSTRUMENTATION_LOG_OUTPUT, AUTOTESTER_LOG,
+                          SCREENSHOTS_DIR]:
+            src = os.path.join(self.ad_dut.external_storage_path, file_name)
+            if self.ad_dut.adb.shell('ls %s || true' % src):
+                self.adb_run('rm -rf %s' % src)
+
+    def trigger_scan_on_external_storage(self):
+        cmd = 'am broadcast -a android.intent.action.MEDIA_MOUNTED '
+        cmd = cmd + '-d file://%s ' % self.ad_dut.external_storage_path
+        cmd = cmd + '--receiver-include-background'
+        return self.adb_run(cmd)
+
+    def push_to_external_storage(self, file_path, dest=None,
+                                 timeout=DEFAULT_PUSH_FILE_TIMEOUT):
+        """Pushes a file to {$EXTERNAL_STORAGE} and returns its final location.
+
+        Args:
+            file_path: The file to be pushed.
+            dest: Where within {$EXTERNAL_STORAGE} it should be pushed.
+            timeout: Float number of seconds to wait for the file to be pushed.
+
+        Returns: The absolute path where the file was pushed.
+        """
+        if dest is None:
+            dest = os.path.basename(file_path)
+
+        dest_path = os.path.join(self.ad_dut.external_storage_path, dest)
+        self.log.info('Clearing %s before pushing %s' % (dest_path, file_path))
+        self.ad_dut.adb.shell('rm -rf %s', dest_path)
+        self.log.info('Pushing file %s to %s' % (file_path, dest_path))
+        self.ad_dut.adb.push(file_path, dest_path, timeout=timeout)
+        return dest_path
+
+    def set_preferred_network(self, network_type):
+        """Set the preferred network type."""
+        self.adb_run(common.airplane_mode.toggle(False))
+        self.adb_run(
+            common.preferred_network_mode.set_value(
+                _NETWORK_TYPES[network_type.lower()]
+            )
+        )
+        self.ad_dut.reboot()
+        self.adb_run(common.disable_doze)
+
+    # Test runtime utils
+
+    def power_default_instr_command_builder(self):
+        """Return the default command builder for power tests"""
+        builder = InstrumentationTestCommandBuilder.default()
+        builder.set_manifest_package(self._test_apk.pkg_name)
+        builder.add_flag('--no-isolated-storage')
+        builder.set_output_as_text()
+        builder.set_nohup()
+        return builder
+
+    def _wait_for_disconnect_signal(self, disconnect_usb_timeout):
+        """Poll the device for a disconnect USB signal file. This will indicate
+        to the Monsoon that the device is ready to be disconnected.
+        """
+        self.log.info('Waiting for USB disconnect signal')
+        start_time = time.time()
+        disconnect_usb_file = os.path.join(self.ad_dut.external_storage_path,
+                                           DISCONNECT_USB_FILE)
+        while time.time() < start_time + disconnect_usb_timeout:
+            if self.ad_dut.adb.shell('ls %s || true' % disconnect_usb_file):
+                self.log.info('Disconnection signal received. File: '
+                              '"%s"' % disconnect_usb_file)
+                self.ad_dut.pull_files(disconnect_usb_file,
+                                       self.ad_dut.device_log_path)
+                return
+            time.sleep(POLLING_INTERVAL)
+        raise InstrumentationTestError('Timeout while waiting for USB '
+                                       'disconnect signal.')
+    def _connect_tigertail(self):
+        """Usb live connection is on mux A and the additional periferal is on mux B"""
+        self.tigertail.turn_off()
+        time.sleep(RESET_SLEEP_TIME)
+        self.tigertail.turn_on_mux_A()
+
+    def _disconnect_tigertail(self):
+        """Usb live connection is on mux A and the additional periferal is on mux B"""
+        self.tigertail.turn_off()
+        time.sleep(RESET_SLEEP_TIME)
+        self.tigertail.turn_on_mux_B()
+
+    def measure_power(self, count=None, attempt_number=None):
+        """Measures power consumption with a power_monitor. See power_monitor's
+        API for more details.
+
+        Returns:
+            A list of power_metrics.Metric.
+        """
+        monsoon_config = self._test_options.get_config('Monsoon')
+        disconnect_usb_timeout = monsoon_config.get_numeric(
+            'usb_disconnection_timeout', 240)
+        measurement_args = dict(
+            duration=monsoon_config.get_numeric('duration'),
+            hz=monsoon_config.get_numeric('frequency'),
+            measure_after_seconds=monsoon_config.get_numeric('delay')
+        )
+        # Start measurement after receiving disconnect signal
+        try:
+            self._wait_for_disconnect_signal(disconnect_usb_timeout)
+        except InstrumentationTestError as e:
+            instrumentation_result = self.parse_instrumentation_result()
+            res = self.log_instrumentation_result(instrumentation_result)
+            self._reinstall_sl4a()
+            raise InstrumentationTestError(
+                'Failed to receive USB disconnect signal.',
+                instrumentation_result=res) from e
+
+        self.log.info('Starting measurement with options: %s' % str(
+            measurement_args))
+
+        power_data_path = os.path.join(
+            context.get_current_context().get_full_output_path(),
+            'monsoon.txt' if count is None else 'monsoon_%s.txt' % count)
+
+        if attempt_number is not None:
+            power_data_path = os.path.join(
+                context.get_current_context().get_full_output_path(),
+                'monsoon_attempt_%s.txt' %
+                attempt_number if count is None else 'monsoon_%s_attempt_%s.txt' %
+                                                     (count, attempt_number))
+
+        # TODO(b/155426729): Create an accurate host-to-device time difference
+        # measurement.
+        device_time_cmd = 'echo $EPOCHREALTIME'
+        device_time = self.adb_run(device_time_cmd)[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
+
+        if self.use_tigertail_if_available and self.tigertail:
+            self._disconnect_tigertail()
+
+        self.power_monitor.disconnect_usb()
+        self.power_monitor.measure(
+            measurement_args=measurement_args, output_path=power_data_path,
+            start_time=device_to_host_offset)
+
+        if self.use_tigertail_if_available and self.tigertail:
+            self._connect_tigertail()
+
+        self.power_monitor.connect_usb()
+        self._reinstall_sl4a()
+        # Gather relevant metrics from measurements
+        instrumentation_result = self.parse_instrumentation_result()
+        self.log_instrumentation_result(instrumentation_result)
+        power_metrics = self.power_monitor.get_metrics(
+            start_time=device_to_host_offset,
+            voltage=monsoon_config.get_numeric('voltage', 4.2),
+            monsoon_file_path=power_data_path,
+            timestamps=proto_parser.get_test_timestamps(instrumentation_result))
+
+        self.power_monitor.release_resources()
+        if self.generate_chart:
+            self.generate_power_chart(power_data_path)
+        return power_metrics
+
+    def run_and_measure(self, instr_class, instr_method=None, req_params=None,
+                        extra_params=None, count=None, attempt_number=None):
+        """Convenience method for setting up the instrumentation test command,
+        running it on the device, and starting the Monsoon measurement.
+
+        Args:
+            instr_class: Fully qualified name of the instrumentation test class
+            instr_method: Name of the instrumentation test method
+            req_params: List of required parameter names
+            extra_params: List of ad-hoc parameters to be passed defined as
+                tuples of size 2.
+            count: Measurement count in one test, default None means only measure
+                one time
+            attempt_number: Repeat test run attempt number, default None means no
+                repeated test
+
+        Returns: summary of Monsoon measurement
+        """
+        if instr_method:
+            self._instr_cmd_builder.add_test_method(instr_class, instr_method)
+        else:
+            self._instr_cmd_builder.add_test_class(instr_class)
+        params = {}
+        instr_call_config = self._test_options.get_config('instrumentation_call')
+        # Add required parameters
+        for param_name in req_params or []:
+            params[param_name] = instr_call_config.get(
+                param_name, verify_fn=lambda x: x is not None,
+                failure_msg='%s is a required parameter.' % param_name)
+        # Add all other parameters
+        params.update(instr_call_config)
+        for name, value in params.items():
+            self._instr_cmd_builder.add_key_value_param(name, value)
+
+        if extra_params:
+            for name, value in extra_params:
+                self._instr_cmd_builder.add_key_value_param(name, value)
+
+        instr_cmd = self._instr_cmd_builder.build()
+        self._uninstall_sl4a()
+
+        # Allow device to stabilize
+        stabilization_time = self._test_options.get_int(
+            'device_stabilization_time', DEFAULT_DEVICE_STABILIZATION_TIME)
+        self.ad_dut.log.debug('Waiting %s seconds for device to stabilize',
+                              stabilization_time)
+        time.sleep(stabilization_time)
+
+        self.log.info('Running instrumentation call: %s' % instr_cmd)
+        self.adb_run_async(instr_cmd)
+        return self.measure_power(count=count, attempt_number=attempt_number)
+
+    def get_absolute_thresholds_for_metric(self, instr_test_name, metric_name):
+        all_thresholds = self._test_options.get_config(ACCEPTANCE_THRESHOLD)
+        test_thresholds = all_thresholds.get_config(instr_test_name)
+        if metric_name not in test_thresholds:
+            return None
+        thresholds_conf = test_thresholds[metric_name]
+        try:
+            return AbsoluteThresholds.from_threshold_conf(thresholds_conf)
+        except (ValueError, TypeError) as e:
+            self.log.error(
+                'Incorrect threshold definition for %s %s' % (instr_test_name,
+                                                              metric_name))
+            self.log.error('Error detail: %s', str(e))
+            return None
+
+    def record_metrics(self, power_metrics):
+        """Record the collected metrics with the metric logger."""
+        self.log.info('Recording metrics summaries:')
+        for segment_name, metrics in power_metrics.items():
+            for metric in metrics:
+                self.log.info(
+                    '    %s %s %s' % (segment_name, metric.name, metric))
+
+            for metric in metrics:
+                self.blackbox_logger.add_metric(
+                    '%s__%s' % (metric.name, segment_name), metric.value)
+                thresholds = self.get_absolute_thresholds_for_metric(
+                    segment_name,
+                    metric.name)
+
+                lower_limit = None
+                upper_limit = None
+                if thresholds:
+                    lower_limit = thresholds.lower.to_unit(metric.unit).value
+                    upper_limit = thresholds.upper.to_unit(metric.unit).value
+
+                self.bounded_metric_logger.add(
+                    '%s.%s' % (segment_name, metric.name), metric.value,
+                    lower_limit=lower_limit,
+                    upper_limit=upper_limit,
+                    unit=metric.unit)
+
+    def validate_metrics(self, power_metrics, *instr_test_names):
+        """Compare power measurements with target values and set the test result
+        accordingly.
+
+        Args:
+            power_metrics: The metrics to be validated.
+            instr_test_names: Name(s) of the instrumentation test method.
+                If none specified, defaults to all test methods run.
+
+        Raises:
+            signals.TestFailure if one or more metrics do not satisfy threshold
+        """
+        summaries = {}
+        failure = False
+        all_thresholds = self._test_options.get_config(ACCEPTANCE_THRESHOLD)
+
+        if not instr_test_names:
+            instr_test_names = all_thresholds.keys()
+
+        for instr_test_name in instr_test_names:
+            try:
+                test_metrics = {metric.name: metric for metric in
+                                power_metrics[instr_test_name]}
+            except KeyError:
+                raise InstrumentationTestError(
+                    'Unable to find test method %s in instrumentation output. '
+                    'Check instrumentation call results in '
+                    'instrumentation_proto.txt.'
+                    % instr_test_name)
+
+            metric_list = []
+            for metric in power_metrics[instr_test_name]:
+                metric_dic = {
+                    'name' : metric.name,
+                    'value' : metric.value,
+                    'unit': metric.unit,
+                    '_unit_type' : metric._unit_type
+                }
+                metric_list.append(metric_dic)
+
+            summaries[instr_test_name] = {}
+            test_thresholds_configs = all_thresholds.get_config(instr_test_name)
+            for metric_name, thresholds_conf in test_thresholds_configs.items():
+                try:
+                    actual_result = test_metrics[metric_name]
+                except KeyError as e:
+                    self.log.warning(
+                        'Error while retrieving results for %s: %s' % (
+                            metric_name, str(e)))
+                    continue
+
+                try:
+                    thresholds = AbsoluteThresholds.from_threshold_conf(
+                        thresholds_conf)
+                except (ValueError, TypeError) as e:
+                    self.log.error(
+                        'Incorrect threshold definition for %s %s',
+                        (instr_test_name, metric_name))
+                    self.log.error('Error detail: %s', str(e))
+                    continue
+
+                power_metrics_json_string = json.dumps(metric_list)
+
+                summary_entry = {
+                    'expected': '[%s, %s]' % (
+                        thresholds.lower, thresholds.upper),
+                    'actual': str(actual_result.to_unit(thresholds.unit)),
+                    'power_metric': power_metrics_json_string
+                }
+                summaries[instr_test_name][metric_name] = summary_entry
+                if not thresholds.lower <= actual_result <= thresholds.upper:
+                    failure = True
+        self.log.info('Validation output: %s' % summaries)
+        asserts.assert_false(
+            failure,
+            msg='One or more measurements do not meet the specified criteria',
+            extras=summaries)
+        asserts.explicit_pass(
+            msg='All measurements meet the criteria',
+            extras=summaries)
+
+    def generate_power_chart(self, power_data_path):
+        """Generate power chat by using the monsoon raw data."""
+        power_chart_path = power_data_path.rsplit('.', 1)[0] + '.html'
+        self.log.info('power_chart_path: %s' % power_chart_path)
+        file_path = os.path.dirname(__file__)
+        self.log.info('current file directory: %s' % file_path)
+        template_file_path = os.path.join(file_path, 'data_graph/template.html')
+        self.log.info('template file path: %s' % template_file_path)
+
+        power_audio_chart.generate_chart(
+            power_data_path, power_chart_path, template_file_path)
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/thresholds.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/thresholds.py
new file mode 100644
index 0000000..2087935
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/thresholds.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from acts.controllers.power_metrics import Metric
+
+
+class AbsoluteThresholds(object):
+    """Class to represent thresholds in absolute (non-relative) values.
+
+    Attributes:
+        lower: Lower limit of the threshold represented by a measurement.
+        upper: Upper limit of the threshold represented by a measurement.
+        unit_type: Type of the unit (current, power, etc).
+        unit: The  unit for this threshold (W, mW, uW).
+    """
+
+    def __init__(self, lower, upper, unit_type, unit):
+        self.unit_type = unit_type
+        self.unit = unit
+        self.lower = Metric(lower, unit_type, unit)
+        self.upper = Metric(upper, unit_type, unit)
+
+    @staticmethod
+    def from_percentual_deviation(expected, percentage, unit_type, unit):
+        """Creates an AbsoluteThresholds object from an expected value and its
+        allowed percentual deviation (also in terms of the expected value).
+
+        For example, if the expected value is 20 and the deviation 25%, this
+        would imply that the absolute deviation is 20 * 0.25 = 5 and therefore
+        the absolute threshold would be from (20-5, 20+5) or (15, 25).
+
+        Args:
+            expected: Central value from which the deviation will be estimated.
+            percentage: Percentage of allowed deviation, the percentage itself
+            is in terms of the expected value.
+            unit_type: Type of the unit (current, power, etc).
+            unit: Unit for this threshold (W, mW, uW).
+        """
+        return AbsoluteThresholds(expected * (1 - percentage / 100),
+                                  expected * (1 + percentage / 100),
+                                  unit_type,
+                                  unit)
+
+    @staticmethod
+    def from_threshold_conf(thresholds_conf):
+        """Creates a AbsoluteThresholds object from a ConfigWrapper describing
+        a threshold (either absolute or percentual).
+
+        Args:
+            thresholds_conf: ConfigWrapper object that describes a threshold.
+        Returns:
+            AbsolutesThresholds object.
+        Raises:
+            ValueError if configuration is incorrect or incomplete.
+        """
+        if 'unit_type' not in thresholds_conf:
+            raise ValueError(
+                'A threshold config must contain a unit_type. %s is incorrect'
+                % str(thresholds_conf))
+
+        if 'unit' not in thresholds_conf:
+            raise ValueError(
+                'A threshold config must contain a unit. %s is incorrect'
+                % str(thresholds_conf))
+
+        unit_type = thresholds_conf['unit_type']
+        unit = thresholds_conf['unit']
+
+        is_relative = (
+            'expected_value' in thresholds_conf and
+            'percent_deviation' in thresholds_conf)
+
+        is_almost_relative = (
+            'expected_value' in thresholds_conf or
+            'percent_deviation' in thresholds_conf)
+
+        is_absolute = ('lower_limit' in thresholds_conf or
+                       'upper_limit' in thresholds_conf)
+
+        if is_absolute and is_almost_relative:
+            raise ValueError(
+                'Thresholds can either be absolute (with lower_limit and'
+                'upper_limit defined) or by percentual deviation (with'
+                'expected_value and percent_deviation defined), but never'
+                'a mixture of both. %s is incorrect'
+                % str(thresholds_conf))
+
+        if is_almost_relative and not is_relative:
+            if 'expected_value' not in thresholds_conf:
+                raise ValueError(
+                    'Incomplete definition of a threshold by percentual '
+                    'deviation. percent_deviation given, but missing '
+                    'expected_value. %s is incorrect'
+                    % str(thresholds_conf))
+
+            if 'percent_deviation' not in thresholds_conf:
+                raise ValueError(
+                    'Incomplete definition of a threshold by percentual '
+                    'deviation. expected_value given, but missing '
+                    'percent_deviation. %s is incorrect'
+                    % str(thresholds_conf))
+
+        if not is_absolute and not is_relative:
+            raise ValueError(
+                'Thresholds must be either absolute (with lower_limit and'
+                'upper_limit defined) or defined by percentual deviation (with'
+                'expected_value and percent_deviation defined). %s is incorrect'
+                % str(thresholds_conf))
+
+        if is_relative:
+            expected = thresholds_conf.get_numeric('expected_value')
+            percent = thresholds_conf.get_numeric('percent_deviation')
+
+            thresholds = (
+                AbsoluteThresholds.from_percentual_deviation(
+                    expected,
+                    percent,
+                    unit_type, unit))
+
+        else:
+            lower_value = thresholds_conf.get_numeric('lower_limit',
+                                                      float('-inf'))
+            upper_value = thresholds_conf.get_numeric('upper_limit',
+                                                      float('inf'))
+            thresholds = AbsoluteThresholds(lower_value, upper_value, unit_type,
+                                            unit)
+        return thresholds
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/vzw_dou_automation/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/vzw_dou_automation/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/vzw_dou_automation/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/vzw_dou_automation/vzw_dou_automation_base_test.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/vzw_dou_automation/vzw_dou_automation_base_test.py
new file mode 100644
index 0000000..0974461
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/vzw_dou_automation/vzw_dou_automation_base_test.py
@@ -0,0 +1,351 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import random
+import statistics
+import tempfile
+import time
+import json
+
+from acts import signals
+from acts.controllers.android_device import SL4A_APK_NAME
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import goog
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import AppInstaller
+from acts_contrib.test_utils.instrumentation.instrumentation_base_test import InstrumentationTestError
+from acts.controllers import power_metrics
+
+from enum import Enum
+
+DEFAULT_WAIT_TO_FASTBOOT_MODE = 60
+DEFAULT_DEVICE_COOL_DOWN_TIME = 80
+DEFAULT_WAIT_FOR_REBOOT = 180
+WIFI_SSID = 'TP-Link-VZW-DoU'
+GMAIL_ACCOUNT = 'vdou001@gmail.com'
+TWITTER_ACCOUNT = 'vdou002@gmail.com'
+
+
+def get_median_current(test_results, test_instance):
+  """Returns the median current, or a failure if the test failed."""
+
+  # Look at results within the good range (i.e., passing results).
+  valid_results = list(filter(lambda result: isinstance(result, signals.TestPass),
+                         test_results))
+
+  # If there is no passing test results check failed test results.
+  if not valid_results:
+    out_range_results = list(
+        filter(lambda result: isinstance(result, signals.TestFailure),
+               test_results))
+    if not out_range_results:
+      return test_results[-1]
+
+    median_current = test_instance.get_median_metric(out_range_results)
+    return signals.TestFailure(
+        'Failed msg! Current: %s out of range' % median_current,
+        extras={'average_current': median_current})
+  else:
+    median_current = test_instance.get_median_metric(valid_results)
+    return signals.TestPass(
+        'Pass msg! Current: %s' % median_current,
+        extras={'average_current': median_current})
+
+
+class TestCase(Enum):
+    TC25 = 'TC25'
+    TC28 = 'TC28'
+    TC29 = 'TC29'
+    TC34 = 'TC34'
+
+
+class VzWDoUAutomationBaseTest(
+    instrumentation_power_test.InstrumentationPowerTest):
+  """Base class that implements common functionality of
+  days of use test cases
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self._google_account_util = None
+    self._facebook_apk = None
+    self._twitter_apk = None
+
+  def base_device_configuration(self):
+    """Runs the adb commands for days of use power testing."""
+
+    self.log.info('Running base adb setup commands.')
+    self.ad_dut.adb.ensure_root()
+    self.adb_run(common.dismiss_keyguard)
+    self.adb_run(goog.location_off_warning_dialog.toggle(False), timeout=120)
+    self.adb_run(common.airplane_mode.toggle(False), timeout=120)
+    self.adb_run(common.auto_rotate.toggle(False))
+    self.set_screen_brightness_level()
+    self.adb_run(common.screen_adaptive_brightness.toggle(False))
+    self.adb_run(common.modem_diag.toggle(False))
+    self.adb_run(common.skip_gesture.toggle(False))
+    self.adb_run(common.screensaver.toggle(False))
+    self.adb_run(common.doze_pulse_on_pick_up.toggle(False))
+    self.adb_run(common.aware_enabled.toggle(False))
+    self.adb_run(common.doze_wake_screen_gesture.toggle(False))
+    self.adb_run(common.doze_mode.toggle(False))
+    self.adb_run(common.doze_always_on.toggle(False))
+    self.adb_run(common.silence_gesture.toggle(False))
+    self.adb_run(common.single_tap_gesture.toggle(False))
+    self.adb_run(goog.location_collection.toggle(False), timeout=120)
+    self.adb_run(goog.icing.toggle(False))
+    self.adb_run(common.stop_moisture_detection)
+    self.adb_run(common.ambient_eq.toggle(False))
+    self.adb_run(common.wifi_state.toggle(True))
+    self.adb_run('echo 1 > /d/clk/debug_suspend')
+    self.adb_run(common.bluetooth.toggle(True))
+    self.adb_run(common.enable_full_batterystats_history)
+    self.adb_run(goog.disable_playstore)
+    self.adb_run(goog.disable_volta)
+    # Test harness flag
+    harness_prop = 'getprop ro.test_harness'
+    # command would fail if properties were previously set, therefore it
+    # needs to be verified first
+    if self.adb_run(harness_prop)[harness_prop] != '1':
+      self.log.info('Enable test harness.')
+      self.adb_run('echo ro.test_harness=1 >> /data/local.prop')
+      self.adb_run('chmod 644 /data/local.prop')
+      self.adb_run(common.test_harness.toggle(True))
+    self.adb_run(goog.force_stop_nexuslauncher)
+    self.adb_run(common.enable_ramdumps.toggle(False))
+    self.adb_run(goog.disable_betterbug)
+    self.adb_run('input keyevent 26')
+    self.adb_run(common.screen_timeout_ms.set_value(180000))
+
+  def _prepare_device(self):
+    """Prepares the device for power testing."""
+    self._factory_reset()
+    super()._prepare_device()
+    self._cut_band()
+    self.log_in_gmail_account()
+    self._update_apps()
+    self.base_device_configuration()
+
+  def _cleanup_device(self):
+    if self._google_account_util:
+      self._google_account_util.uninstall()
+    if self._facebook_apk:
+      self._facebook_apk.uninstall()
+    if self._twitter_apk:
+      self._twitter_apk.uninstall()
+    super()._cleanup_device()
+    self.adb_run('input keyevent 26')
+
+  def teardown_test(self):
+    """Test teardown"""
+    self.log.info('Teardown test at vzw dou automation base.')
+    self.power_monitor.connect_usb()
+    super().teardown_test()
+
+  def _factory_reset(self):
+    """Factory reset device before testing."""
+    self.log.info('Running factory reset.')
+    self._sl4a_apk = AppInstaller.pull_from_device(
+        self.ad_dut, SL4A_APK_NAME, tempfile.mkdtemp(prefix='sl4a'))
+    self.ad_dut.adb.ensure_root()
+    self._install_google_account_util_apk()
+    self.adb_run(goog.remove_gmail_account)
+    self.ad_dut.reboot(wait_after_reboot_complete=DEFAULT_WAIT_FOR_REBOOT)
+    self.ad_dut.adb.ensure_root()
+    self.ad_dut.log.debug('Reboot to bootloader')
+    self.ad_dut.stop_services()
+    self.ad_dut.adb.reboot('bootloader', ignore_status=True)
+    time.sleep(DEFAULT_WAIT_FOR_REBOOT)
+    self.fastboot_run('-w')
+    self.ad_dut.log.debug('Reboot in fastboot')
+    self.ad_dut.fastboot.reboot()
+    self.ad_dut.wait_for_boot_completion()
+    time.sleep(DEFAULT_WAIT_FOR_REBOOT)
+    self.ad_dut.root_adb()
+    if not self.ad_dut.is_sl4a_installed() and self._sl4a_apk:
+      self._sl4a_apk.install()
+    self.ad_dut.start_services()
+
+  def _install_google_account_util_apk(self):
+    """Installs google account util apk on the device."""
+    _google_account_util_file = self.get_file_from_config(
+        'google_account_util_apk')
+    self._google_account_util = AppInstaller(self.ad_dut,
+                                             _google_account_util_file)
+    self._google_account_util.install('-g')
+    if not self._google_account_util.is_installed():
+      raise InstrumentationTestError(
+          'Failed to install google account util APK.')
+
+  def install_facebook_apk(self):
+    """Installs facebook apk on the device."""
+    _facebook_apk_file = self.get_file_from_config('facebook_apk')
+    self._facebook_apk = AppInstaller(self.ad_dut, _facebook_apk_file)
+    self._facebook_apk.install('-g')
+    if not self._facebook_apk.is_installed():
+      raise InstrumentationTestError('Failed to install facebook APK.')
+
+  def install_twitter_apk(self):
+    """Installs twitter apk on the device."""
+    _twitter_apk_file = self.get_file_from_config('twitter_apk')
+    self._twitter_apk = AppInstaller(self.ad_dut, _twitter_apk_file)
+    self._twitter_apk.install('-g')
+    if not self._twitter_apk.is_installed():
+      raise InstrumentationTestError('Failed to install twitter APK.')
+
+  def _cut_band(self):
+    additional_setting = self._instrumentation_config.get_config('additional_setting')
+    band_to_cut = None
+    if additional_setting:
+      band_to_cut = additional_setting.get('band_to_cut')
+    if band_to_cut:
+      self.log.info('Cutting band: {}'.format(band_to_cut))
+      self.ad_dut.adb.ensure_root()
+      lock_band_cmd = ('am instrument -w -r -e lock_band {} -e '
+                       'skip_pre_test_conditions TRUE -e '
+                       'skip_post_test_conditions TRUE -e class '
+                       'com.google.android.platform.dou.MDSSwitchBandTests#testSwitchBand'
+                       ' '
+                       'com.google.android.platform.dou/androidx.test.runner.AndroidJUnitRunner').format(
+          band_to_cut)
+      self.adb_run(lock_band_cmd, timeout=480)
+      self.ad_dut.reboot(wait_after_reboot_complete=DEFAULT_WAIT_FOR_REBOOT)
+
+  def _update_apps(self):
+    apps_to_update = self._instrumentation_config.get_config(
+        'additional_setting').get('apps_to_update')
+    if apps_to_update:
+      self.log.info('Update apps: {}'.format(apps_to_update))
+      self.ad_dut.adb.ensure_root()
+      update_apps_cmd = (
+          'am instrument -w -r -e update_package_name '
+          'com.google.android.apps.photos -e profile_iterations 1 '
+          '-e dou_mode RADIO -e precondition_music_volume 50 -e '
+          'bluetooth_device_mac_addr E4:22:A5:B5:A8:00 -e '
+          'precondition_wifi CONNECTED -e wifi_ssid {} -e '
+          'wifi_passphrase Google123 -e precondition_radio CONNECTED '
+          '-e precondition_mobile_data ON -e '
+          'precondition_bluetooth ON -e class '
+          'com.google.android.platform.dou.PlayStoreUpdateAppTests#testUpdateApp'
+          ' '
+          'com.google.android.platform.dou/androidx.test.runner.AndroidJUnitRunner'
+      ).format(WIFI_SSID)
+      self.adb_run(update_apps_cmd, timeout=480)
+      self.ad_dut.reboot(wait_after_reboot_complete=DEFAULT_WAIT_FOR_REBOOT)
+
+  def generate_random_ssid(self):
+    # Generate random permutations as ssid
+    ssid = os.popen('shuf -i 1111111111-9999999999 -n 1').read(10)
+    return ssid.strip()
+
+  def push_movies_to_dut(self):
+    # Push the movies folder to Android device
+    sdcard_movies_path_dut = '/sdcard/Movies/'
+    sdcard_movies_path = self.user_params['sdcard_movies_path'][0]
+    self.log.info('sdcard_movies_path is %s' % sdcard_movies_path)
+    self.ad_dut.adb.push(sdcard_movies_path + '/*', sdcard_movies_path_dut)
+    self.ad_dut.reboot(wait_after_reboot_complete=DEFAULT_WAIT_FOR_REBOOT)
+
+  def log_in_gmail_account(self, sync='false', wait_for_checkin='false'):
+    # Log in to gmail account
+    self._install_google_account_util_apk()
+    time.sleep(DEFAULT_DEVICE_COOL_DOWN_TIME)
+    additional_setting = self._instrumentation_config.get_config('additional_setting')
+    gmail_phrase = additional_setting.get('gmail_phrase')
+    log_in_cmd = (
+        'am instrument -w -e account {} -e '
+        'password {} -e sync {} -e wait-for-checkin {} '
+        'com.google.android.tradefed.account/.AddAccount'
+    ).format(GMAIL_ACCOUNT, gmail_phrase, sync, wait_for_checkin)
+    self.log.info('gmail log in commands %s' % log_in_cmd)
+    self.adb_run(log_in_cmd, timeout=300)
+    time.sleep(DEFAULT_DEVICE_COOL_DOWN_TIME)
+
+  def push_music_to_dut(self):
+    # Push the music folder to Android device
+    sdcard_music_path_dut = '/sdcard/Music/'
+    sdcard_music_path = self.user_params['sdcard_music_path'][0]
+    self.log.info('sdcard_music_path is %s' % sdcard_music_path)
+    self.ad_dut.adb.push(sdcard_music_path + '/*', sdcard_music_path_dut)
+    self.ad_dut.reboot(wait_after_reboot_complete=DEFAULT_WAIT_FOR_REBOOT)
+
+  def generate_random_exchange_email_account(self, test_name: TestCase):
+    # Generate random exchange email account based on test case
+    if test_name == TestCase.TC25:
+      random_num = str(random.randint(1, 25))
+      num = random_num.zfill(3)
+      email_account = 'pixelvzwdoutouch%s@gtestmailer.com' % num
+      self.log.info('TC25 exchange email is %s' % email_account)
+    elif test_name == TestCase.TC34:
+      random_num = str(random.randint(2, 25))
+      num = random_num.zfill(3)
+      email_account = 'pixelvzwdoupure%s@gtestmailer.com' % num
+      self.log.info('TC34 exchange email is %s' % email_account)
+    else:
+      random_num = str(random.randint(1, 50))
+      num = random_num.zfill(3)
+      email_account = 'pixelvzwdou%s@gtestmailer.com' % num
+      self.log.info('Exchange email is %s' % email_account)
+    return email_account
+
+  def convert_power_metric_dict_to_object(self, power_meric_dict):
+    # Convert power metric dict to object
+    metric_object_list = []
+    for metric in power_meric_dict:
+      metric_object = power_metrics.Metric(metric['value'], metric['_unit_type'],
+                                           metric['unit'], name=metric['name'])
+      metric_object_list.append(metric_object)
+    return metric_object_list
+
+  def get_median_metric(self, test_results):
+    # Get the median current and median metric from the given test results.
+    median_current = statistics.median_low([
+        x.extras[list(x.extras.keys())[0]]['avg_current']['actual']
+        for x in test_results
+    ])
+    self.log.debug('The median_current is %s' % median_current)
+
+    # Get the median metrics for test results recording.
+    final_metrics = {}
+    result_dict_list = []
+    key = ''
+    for x in test_results:
+      key = list(x.extras.keys())[0]
+      if str(x.extras[list(
+          x.extras.keys())[0]]['avg_current']['actual']) == str(median_current):
+        median_metric = x.extras[list(
+            x.extras.keys())[0]]['avg_current']['power_metric']
+        result_dict_list = json.loads(median_metric)
+        self.log.debug('Median metrics result dict list is %s' %
+                       result_dict_list)
+        key = list(x.extras.keys())[0]
+        self.log.debug('The key of the median result dict is %s' % key)
+        break
+
+    # Get the median metrics for test results recording.
+    self.log.debug('The key of the final_metrics is %s' % key)
+    self.log.debug('The result dict list of the final_metrics %s' %
+                   result_dict_list)
+    result_object_list = self.convert_power_metric_dict_to_object(
+        result_dict_list)
+    self.log.debug('The result object list of the final_metrics %s' %
+                   result_object_list)
+    final_metrics[key] = result_object_list
+
+    # Record median metrics.
+    self.record_metrics(final_metrics)
+    return median_current
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/vzw_dou_automation/vzw_dou_automation_comp_base_test.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/vzw_dou_automation/vzw_dou_automation_comp_base_test.py
new file mode 100644
index 0000000..b23d6a9
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/vzw_dou_automation/vzw_dou_automation_comp_base_test.py
@@ -0,0 +1,273 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import time
+
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import AppInstaller
+from acts_contrib.test_utils.instrumentation.device.apps.permissions import PermissionsUtil
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import goog
+from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import \
+  DEFAULT_INSTRUMENTATION_LOG_OUTPUT
+from acts_contrib.test_utils.instrumentation.device.command.instrumentation_command_builder import \
+  InstrumentationTestCommandBuilder
+from acts_contrib.test_utils.instrumentation.instrumentation_base_test import \
+  InstrumentationTestError
+from acts_contrib.test_utils.instrumentation.instrumentation_proto_parser import \
+  DEFAULT_INST_LOG_DIR
+from acts_contrib.test_utils.instrumentation.power.vzw_dou_automation import \
+  vzw_dou_automation_base_test
+
+AUTOTESTER_LOG = 'autotester.log'
+SCREENSHOTS_DIR = 'test_screenshots'
+
+
+class VzWDoUAutomationCompBaseTest(
+    vzw_dou_automation_base_test.VzWDoUAutomationBaseTest):
+  """Base class that implements common functionality of
+
+  days of use test cases with companion phone
+  """
+
+  def __init__(self, configs):
+    super().__init__(configs)
+    self._companion_test_apk = None
+    self._companion_wifi_util = None
+    self._companion_google_account_util = None
+    self._companion_instr_cmd_builder = None
+    self._companion_permissions_util = None
+
+  def setup_class(self):
+    """Class setup"""
+    super().setup_class()
+    self.ad_cp = self.android_devices[1]
+
+  def setup_test(self):
+    """Test setup"""
+    super().setup_test()
+    self._prepare_companion_device()
+    self._companion_instr_cmd_builder = self._instr_command_builder()
+
+  def _prepare_companion_device(self):
+    """Prepares the companion device for power testing."""
+    self._cleanup_companion_test_files()
+    self._companion_permissions_util = PermissionsUtil(
+        self.ad_cp, self.get_file_from_config('permissions_apk'))
+    self._companion_permissions_util.grant_all()
+    self._install_companion_test_apk()
+    self.base_companion_device_configuration()
+
+  def _install_companion_test_apk(self):
+    """Installs test apk on the companion device."""
+    test_apk_file = self.get_file_from_config('test_apk')
+    self._companion_test_apk = AppInstaller(self.ad_cp, test_apk_file)
+    self._companion_test_apk.install('-g')
+    if not self._companion_test_apk.is_installed():
+      raise InstrumentationTestError(
+          'Failed to install test APK on companion device.')
+
+  def _install_companion_wifi_util_apk(self):
+    """Installs google account util apk on the device."""
+    _wifi_util_file = self.get_file_from_config(
+        'wifi_util_apk')
+    self._companion_wifi_util = AppInstaller(self.ad_cp,
+                                     _wifi_util_file)
+    self._companion_wifi_util.install('-g')
+    if not self._companion_wifi_util.is_installed():
+      raise InstrumentationTestError(
+          'Failed to install wif util APK on companion.')
+
+  def _install_companion_google_account_util_apk(self):
+    """Installs google account util apk on the device."""
+    _google_account_util_file = self.get_file_from_config(
+        'google_account_util_apk')
+    self._companion_google_account_util = AppInstaller(self.ad_cp,
+                                             _google_account_util_file)
+    self._companion_google_account_util.install('-g')
+    if not self._companion_google_account_util.is_installed():
+      raise InstrumentationTestError(
+          'Failed to install google account util APK on companion.')
+
+  def _pull_companion_test_files(self):
+    """Pull test-generated files from the companion device onto the log directory."""
+    dest = self.ad_cp.device_log_path
+    self.ad_cp.log.info(
+        'Pulling test generated files from companion device to %s.' % dest)
+    for file_name in [DEFAULT_INSTRUMENTATION_LOG_OUTPUT, SCREENSHOTS_DIR]:
+      src = os.path.join(self.ad_cp.external_storage_path, file_name)
+      if self.ad_cp.adb.shell('ls %s || true' % src):
+        self.ad_cp.pull_files(src, dest)
+
+  def _cleanup_companion_test_files(self):
+    """Remove test-generated files from the companion device."""
+    self.ad_cp.log.info('Cleaning up test generated files on companion device.')
+    for file_name in [
+        DEFAULT_INST_LOG_DIR, DEFAULT_INSTRUMENTATION_LOG_OUTPUT,
+        AUTOTESTER_LOG, SCREENSHOTS_DIR
+    ]:
+      src = os.path.join(self.ad_cp.external_storage_path, file_name)
+      if self.ad_cp.adb.shell('ls %s || true' % src):
+        self.adb_run('rm -rf %s' % src, self.ad_cp)
+
+  def _cleanup_companion_device(self):
+    """Clean up device after power testing."""
+    if self._companion_test_apk:
+      self._companion_test_apk.uninstall()
+    if self._companion_wifi_util:
+      self._companion_wifi_util.uninstall()
+    if self._companion_google_account_util:
+      self._companion_google_account_util.uninstall()
+    if self._companion_permissions_util:
+      self._companion_permissions_util.close()
+    self._pull_companion_test_files()
+    self._cleanup_companion_test_files()
+
+  def teardown_test(self):
+    """Test teardown. Takes bugreport and cleans up device."""
+    super().teardown_test()
+    self._cleanup_companion_device()
+
+  def base_companion_device_configuration(self):
+    """Runs the adb commands to prepare the companion phone for days of use power testing."""
+
+    self.log.info('Running base adb setup commands on companion.')
+    self.ad_cp.adb.ensure_root()
+    self.ad_cp.reboot(wait_after_reboot_complete=vzw_dou_automation_base_test
+                      .DEFAULT_WAIT_FOR_REBOOT)
+    self.ad_cp.adb.ensure_root()
+    # Test harness flag
+    harness_prop = 'getprop ro.test_harness'
+    # command would fail if properties were previously set, therefore it
+    # needs to be verified first
+    if self.adb_run(harness_prop, ad=self.ad_cp)[harness_prop] != '1':
+      self.log.info('Enable test harness at companion.')
+      self.adb_run('echo ro.test_harness=1 >> /data/local.prop', ad=self.ad_cp)
+      self.adb_run('chmod 644 /data/local.prop', ad=self.ad_cp)
+      self.adb_run(common.test_harness.toggle(True), ad=self.ad_cp)
+    self.adb_run(goog.force_stop_nexuslauncher, ad=self.ad_cp)
+    self.adb_run(goog.disable_playstore, ad=self.ad_cp)
+    self.adb_run(goog.disable_chrome, ad=self.ad_cp)
+    self.adb_run(common.wifi_state.toggle(True), ad=self.ad_cp)
+    self.adb_run(common.mobile_data.toggle(True), ad=self.ad_cp)
+    self.adb_run('input keyevent 82', ad=self.ad_cp)
+    self.adb_run('input keyevent 3', ad=self.ad_cp)
+
+  def _instr_command_builder(self):
+    """Return a command builder for companion devices in power tests """
+    builder = InstrumentationTestCommandBuilder.default()
+    builder.set_manifest_package(self._companion_test_apk.pkg_name)
+    builder.add_flag('--no-isolated-storage')
+    builder.set_output_as_text()
+    builder.set_nohup()
+    return builder
+
+  def run_instrumentation_on_companion(self,
+                                       instr_class,
+                                       instr_method=None,
+                                       req_params=None,
+                                       extra_params=None):
+    """Convenience method for setting up the instrumentation test command,
+
+    running it on the companion device.
+
+    Args:
+        instr_class: Fully qualified name of the instrumentation test class
+        instr_method: Name of the instrumentation test method
+        req_params: List of required parameter names
+        extra_params: List of ad-hoc parameters to be passed defined as tuples
+          of size 2.
+    """
+    if instr_method:
+      self._companion_instr_cmd_builder.add_test_method(instr_class,
+                                                        instr_method)
+    else:
+      self._companion_instr_cmd_builder.add_test_class(instr_class)
+    params = {}
+    companion_instr_call_config = self._instrumentation_config.get_config(
+        'companion_instrumentation_call')
+    # Add required parameters
+    for param_name in req_params or []:
+      params[param_name] = companion_instr_call_config.get(
+          param_name,
+          verify_fn=lambda x: x is not None,
+          failure_msg='%s is a required parameter.' % param_name)
+    # Add all other parameters
+    params.update(companion_instr_call_config)
+    for name, value in params.items():
+      self._companion_instr_cmd_builder.add_key_value_param(name, value)
+
+    if extra_params:
+      for name, value in extra_params:
+        self._companion_instr_cmd_builder.add_key_value_param(name, value)
+
+    instr_cmd = self._companion_instr_cmd_builder.build()
+    self.log.info('Running instrumentation call on companion: %s' % instr_cmd)
+    self.adb_run_async(instr_cmd, ad=self.ad_cp)
+
+  def get_phone_number(self, ad):
+    """Retrieve the phone number for the device.
+
+        Args:
+            ad: The device to run on.
+        Returns: string phone number
+    """
+    cmd = ('service call iphonesubinfo 16 | fgrep "\'" | cut -d"\'" -f2 | tr -d'
+           ' "\\n" | sed "s/\.//g"')
+    result = self.adb_run(cmd, ad=ad)
+    return result[cmd]
+
+  def pair_dut_bluetooth(self):
+    # Push the bt_config.conf file to Android device
+    # so it can pair and connect automatically.
+    bt_conf_path_dut = '/data/misc/bluedroid/bt_config.conf'
+    bt_config_file = 'bt_config.conf'
+    bt_config_path = self.user_params['bt_configs'][0]
+    self.log.info('Base bt config path %s' % bt_config_path)
+    self.ad_dut.adb.push(bt_config_path + '/' + self.ad_dut.serial + '/' +
+                         bt_config_file + ' %s' % bt_conf_path_dut)
+    self.ad_dut.reboot(wait_after_reboot_complete=vzw_dou_automation_base_test
+                       .DEFAULT_WAIT_FOR_REBOOT)
+
+  def log_in_companion_gmail_account(self, sync='false', wait_for_checkin='false'):
+    # Log in to gmail account
+    self._install_companion_google_account_util_apk()
+    time.sleep(vzw_dou_automation_base_test.DEFAULT_DEVICE_COOL_DOWN_TIME)
+    self.adb_run(goog.remove_gmail_account, ad=self.ad_cp)
+    additional_setting = self._instrumentation_config.get_config('additional_setting')
+    gmail_phrase = additional_setting.get('gmail_phrase')
+    log_in_cmd = (
+        'am instrument -w -e account {} -e '
+        'password {} -e sync {} -e wait-for-checkin {} '
+        'com.google.android.tradefed.account/.AddAccount'
+    ).format(vzw_dou_automation_base_test.GMAIL_ACCOUNT,
+             gmail_phrase, sync, wait_for_checkin)
+    self.log.info('gmail log in commands %s' % log_in_cmd)
+    self.adb_run(log_in_cmd, timeout=300, ad=self.ad_cp)
+    time.sleep(vzw_dou_automation_base_test.DEFAULT_DEVICE_COOL_DOWN_TIME)
+
+  def connect_companion_to_wifi(self):
+    # Log in to gmail account
+    self._install_companion_wifi_util_apk()
+    additional_setting = self._instrumentation_config.get_config('additional_setting')
+    wifi_phrase = additional_setting.get('wifi_phrase')
+    wifi_connection_cmd = (
+        'am instrument -e method "connectToNetwork" -e ssid {} -e '
+        'psk "Google123" -w com.android.tradefed.utils.wifi/.WifiUtil'
+    ).format(vzw_dou_automation_base_test.WIFI_SSID, wifi_phrase)
+    self.log.info('wifi_connection_cmd %s' % wifi_connection_cmd)
+    self.adb_run(wifi_connection_cmd, timeout=300, ad=self.ad_cp)
+    time.sleep(vzw_dou_automation_base_test.DEFAULT_DEVICE_COOL_DOWN_TIME)
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/vzwdou/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/vzwdou/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/vzwdou/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/power/vzwdou/vzw_dou_base_test.py b/acts_tests/acts_contrib/test_utils/instrumentation/power/vzwdou/vzw_dou_base_test.py
new file mode 100644
index 0000000..9ab84dd
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/power/vzwdou/vzw_dou_base_test.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import time
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import goog
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+  DeviceGServices
+from acts_contrib.test_utils.instrumentation.device.apps.google_account_util import \
+  GoogleAccountUtil
+from acts_contrib.test_utils.instrumentation.device.apps.modem_diag_util import \
+  ModemDiagUtil
+
+class VzwDoUBaseTest(
+    instrumentation_power_test.InstrumentationPowerTest):
+  """Base class that implements common functionality of
+  VZW DoU test cases
+  """
+
+  def base_device_configuration(self):
+    """Runs the adb commands for VZW DoU power testing."""
+
+    self.log.info('Running base adb setup commands.')
+
+    self.ad_dut.adb.ensure_root()
+
+    self.adb_run(common.dismiss_keyguard)
+
+    self.adb_run(goog.location_off_warning_dialog.toggle(False))
+
+    self.adb_run(common.airplane_mode.toggle(False))
+
+    self.adb_run(common.auto_rotate.toggle(False))
+
+    self.adb_run(common.screen_brightness.set_value(58))
+
+    self.adb_run(common.screen_adaptive_brightness.toggle(False))
+
+    self.adb_run(common.modem_diag.toggle(False))
+
+    self.adb_run(common.skip_gesture.toggle(False))
+
+    self.adb_run(common.screensaver.toggle(False))
+
+    self.adb_run(common.doze_pulse_on_pick_up.toggle(False))
+
+    self.adb_run(common.aware_enabled.toggle(False))
+
+    self.adb_run(common.doze_wake_screen_gesture.toggle(False))
+
+    self.adb_run(common.doze_mode.toggle(False))
+
+    self.adb_run(common.doze_always_on.toggle(False))
+
+    self.adb_run(common.silence_gesture.toggle(False))
+
+    self.adb_run(common.single_tap_gesture.toggle(False))
+
+    self.adb_run(goog.location_collection.toggle(False))
+
+    self.adb_run(goog.icing.toggle(False))
+
+    self.adb_run(common.stop_moisture_detection)
+
+    self.adb_run(common.ambient_eq.toggle(False))
+
+    self.adb_run(common.wifi.toggle(True))
+
+    self.adb_run('echo 1 > /d/clk/debug_suspend')
+
+    self.adb_run(common.bluetooth.toggle(True))
+
+    self.adb_run(common.enable_full_batterystats_history)
+
+    self.adb_run(goog.disable_playstore)
+
+    self.adb_run(goog.disable_volta)
+
+    self.adb_run(common.test_harness.toggle(True))
+
+    self.adb_run('am force-stop com.google.android.apps.nexuslauncher')
+
+    self.adb_run('input keyevent 26')
+
+    self.adb_run(common.screen_timeout_ms.set_value(180000))
+
+  def _prepare_device(self):
+    """Prepares the device for power testing."""
+    super()._prepare_device()
+    self.base_device_configuration()
+    self._google_account_util = GoogleAccountUtil(
+        self.ad_dut,
+        self.get_file_from_config('google_account_util_apk'))
+    # TODO: Use google account util
+
+    band_to_cut = self._test_options.get('band_to_cut')
+    if band_to_cut:
+      self.log.info('Cutting band: {}'.format(band_to_cut))
+      self._modem_diag_util = ModemDiagUtil(
+          self.ad_dut,
+          self.get_file_from_config('modem_diag_util_apk'))
+
+      self._modem_diag_util.cut_band(band_to_cut)
+      self.ad_dut.reboot()
+
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/proto/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/proto/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/proto/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/proto/gen/__init__.py b/acts_tests/acts_contrib/test_utils/instrumentation/proto/gen/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/proto/gen/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/proto/gen/instrumentation_data_pb2.py b/acts_tests/acts_contrib/test_utils/instrumentation/proto/gen/instrumentation_data_pb2.py
new file mode 100644
index 0000000..71c9f9c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/proto/gen/instrumentation_data_pb2.py
@@ -0,0 +1,359 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: instrumentation_data.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='instrumentation_data.proto',
+  package='android.am',
+  syntax='proto2',
+  serialized_options=b'\n\027com.android.commands.am',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x1ainstrumentation_data.proto\x12\nandroid.am\"\xcf\x01\n\x12ResultsBundleEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x14\n\x0cvalue_string\x18\x02 \x01(\t\x12\x11\n\tvalue_int\x18\x03 \x01(\x11\x12\x13\n\x0bvalue_float\x18\x04 \x01(\x02\x12\x14\n\x0cvalue_double\x18\x05 \x01(\x01\x12\x12\n\nvalue_long\x18\x06 \x01(\x12\x12/\n\x0cvalue_bundle\x18\x07 \x01(\x0b\x32\x19.android.am.ResultsBundle\x12\x13\n\x0bvalue_bytes\x18\x08 \x01(\x0c\"@\n\rResultsBundle\x12/\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x1e.android.am.ResultsBundleEntry\"]\n\nTestStatus\x12\x13\n\x0bresult_code\x18\x03 \x01(\x11\x12*\n\x07results\x18\x04 \x01(\x0b\x32\x19.android.am.ResultsBundle\x12\x0e\n\x06logcat\x18\x05 \x01(\t\"\x98\x01\n\rSessionStatus\x12\x32\n\x0bstatus_code\x18\x01 \x01(\x0e\x32\x1d.android.am.SessionStatusCode\x12\x12\n\nerror_text\x18\x02 \x01(\t\x12\x13\n\x0bresult_code\x18\x03 \x01(\x11\x12*\n\x07results\x18\x04 \x01(\x0b\x32\x19.android.am.ResultsBundle\"i\n\x07Session\x12+\n\x0btest_status\x18\x01 \x03(\x0b\x32\x16.android.am.TestStatus\x12\x31\n\x0esession_status\x18\x02 \x01(\x0b\x32\x19.android.am.SessionStatus*>\n\x11SessionStatusCode\x12\x14\n\x10SESSION_FINISHED\x10\x00\x12\x13\n\x0fSESSION_ABORTED\x10\x01\x42\x19\n\x17\x63om.android.commands.am'
+)
+
+_SESSIONSTATUSCODE = _descriptor.EnumDescriptor(
+  name='SessionStatusCode',
+  full_name='android.am.SessionStatusCode',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='SESSION_FINISHED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='SESSION_ABORTED', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=675,
+  serialized_end=737,
+)
+_sym_db.RegisterEnumDescriptor(_SESSIONSTATUSCODE)
+
+SessionStatusCode = enum_type_wrapper.EnumTypeWrapper(_SESSIONSTATUSCODE)
+SESSION_FINISHED = 0
+SESSION_ABORTED = 1
+
+
+
+_RESULTSBUNDLEENTRY = _descriptor.Descriptor(
+  name='ResultsBundleEntry',
+  full_name='android.am.ResultsBundleEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='android.am.ResultsBundleEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='value_string', full_name='android.am.ResultsBundleEntry.value_string', 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='value_int', full_name='android.am.ResultsBundleEntry.value_int', index=2,
+      number=3, type=17, 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='value_float', full_name='android.am.ResultsBundleEntry.value_float', 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='value_double', full_name='android.am.ResultsBundleEntry.value_double', index=4,
+      number=5, type=1, cpp_type=5, label=1,
+      has_default_value=False, default_value=float(0),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='value_long', full_name='android.am.ResultsBundleEntry.value_long', index=5,
+      number=6, type=18, 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='value_bundle', full_name='android.am.ResultsBundleEntry.value_bundle', index=6,
+      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='value_bytes', full_name='android.am.ResultsBundleEntry.value_bytes', index=7,
+      number=8, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=43,
+  serialized_end=250,
+)
+
+
+_RESULTSBUNDLE = _descriptor.Descriptor(
+  name='ResultsBundle',
+  full_name='android.am.ResultsBundle',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='entries', full_name='android.am.ResultsBundle.entries', 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=252,
+  serialized_end=316,
+)
+
+
+_TESTSTATUS = _descriptor.Descriptor(
+  name='TestStatus',
+  full_name='android.am.TestStatus',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='result_code', full_name='android.am.TestStatus.result_code', index=0,
+      number=3, type=17, 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='results', full_name='android.am.TestStatus.results', index=1,
+      number=4, 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='logcat', full_name='android.am.TestStatus.logcat', index=2,
+      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),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=318,
+  serialized_end=411,
+)
+
+
+_SESSIONSTATUS = _descriptor.Descriptor(
+  name='SessionStatus',
+  full_name='android.am.SessionStatus',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='status_code', full_name='android.am.SessionStatus.status_code', 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='error_text', full_name='android.am.SessionStatus.error_text', 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='result_code', full_name='android.am.SessionStatus.result_code', index=2,
+      number=3, type=17, 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='results', full_name='android.am.SessionStatus.results', index=3,
+      number=4, 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=414,
+  serialized_end=566,
+)
+
+
+_SESSION = _descriptor.Descriptor(
+  name='Session',
+  full_name='android.am.Session',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='test_status', full_name='android.am.Session.test_status', 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='session_status', full_name='android.am.Session.session_status', 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),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto2',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=568,
+  serialized_end=673,
+)
+
+_RESULTSBUNDLEENTRY.fields_by_name['value_bundle'].message_type = _RESULTSBUNDLE
+_RESULTSBUNDLE.fields_by_name['entries'].message_type = _RESULTSBUNDLEENTRY
+_TESTSTATUS.fields_by_name['results'].message_type = _RESULTSBUNDLE
+_SESSIONSTATUS.fields_by_name['status_code'].enum_type = _SESSIONSTATUSCODE
+_SESSIONSTATUS.fields_by_name['results'].message_type = _RESULTSBUNDLE
+_SESSION.fields_by_name['test_status'].message_type = _TESTSTATUS
+_SESSION.fields_by_name['session_status'].message_type = _SESSIONSTATUS
+DESCRIPTOR.message_types_by_name['ResultsBundleEntry'] = _RESULTSBUNDLEENTRY
+DESCRIPTOR.message_types_by_name['ResultsBundle'] = _RESULTSBUNDLE
+DESCRIPTOR.message_types_by_name['TestStatus'] = _TESTSTATUS
+DESCRIPTOR.message_types_by_name['SessionStatus'] = _SESSIONSTATUS
+DESCRIPTOR.message_types_by_name['Session'] = _SESSION
+DESCRIPTOR.enum_types_by_name['SessionStatusCode'] = _SESSIONSTATUSCODE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+ResultsBundleEntry = _reflection.GeneratedProtocolMessageType('ResultsBundleEntry', (_message.Message,), {
+  'DESCRIPTOR' : _RESULTSBUNDLEENTRY,
+  '__module__' : 'instrumentation_data_pb2'
+  # @@protoc_insertion_point(class_scope:android.am.ResultsBundleEntry)
+  })
+_sym_db.RegisterMessage(ResultsBundleEntry)
+
+ResultsBundle = _reflection.GeneratedProtocolMessageType('ResultsBundle', (_message.Message,), {
+  'DESCRIPTOR' : _RESULTSBUNDLE,
+  '__module__' : 'instrumentation_data_pb2'
+  # @@protoc_insertion_point(class_scope:android.am.ResultsBundle)
+  })
+_sym_db.RegisterMessage(ResultsBundle)
+
+TestStatus = _reflection.GeneratedProtocolMessageType('TestStatus', (_message.Message,), {
+  'DESCRIPTOR' : _TESTSTATUS,
+  '__module__' : 'instrumentation_data_pb2'
+  # @@protoc_insertion_point(class_scope:android.am.TestStatus)
+  })
+_sym_db.RegisterMessage(TestStatus)
+
+SessionStatus = _reflection.GeneratedProtocolMessageType('SessionStatus', (_message.Message,), {
+  'DESCRIPTOR' : _SESSIONSTATUS,
+  '__module__' : 'instrumentation_data_pb2'
+  # @@protoc_insertion_point(class_scope:android.am.SessionStatus)
+  })
+_sym_db.RegisterMessage(SessionStatus)
+
+Session = _reflection.GeneratedProtocolMessageType('Session', (_message.Message,), {
+  'DESCRIPTOR' : _SESSION,
+  '__module__' : 'instrumentation_data_pb2'
+  # @@protoc_insertion_point(class_scope:android.am.Session)
+  })
+_sym_db.RegisterMessage(Session)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/acts_tests/acts_contrib/test_utils/instrumentation/proto/instrumentation_data.proto b/acts_tests/acts_contrib/test_utils/instrumentation/proto/instrumentation_data.proto
new file mode 100644
index 0000000..212bfb2
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/instrumentation/proto/instrumentation_data.proto
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.am;
+
+option java_package = "com.android.commands.am";
+
+message ResultsBundleEntry {
+    optional string key = 1;
+
+    optional string value_string = 2;
+    optional sint32 value_int = 3;
+    optional float value_float = 4;
+    optional double value_double = 5;
+    optional sint64 value_long = 6;
+    optional ResultsBundle value_bundle = 7;
+    optional bytes value_bytes = 8;
+}
+
+message ResultsBundle {
+    repeated ResultsBundleEntry entries = 1;
+}
+
+message TestStatus {
+    optional sint32 result_code = 3;
+    optional ResultsBundle results = 4;
+    optional string logcat = 5;
+}
+
+enum SessionStatusCode {
+    /**
+     * The command ran successfully. This does not imply that the tests passed.
+     */
+    SESSION_FINISHED = 0;
+
+    /**
+     * There was an unrecoverable error running the tests.
+     */
+    SESSION_ABORTED = 1;
+}
+
+message SessionStatus {
+    optional SessionStatusCode status_code = 1;
+    optional string error_text = 2;
+    optional sint32 result_code = 3;
+    optional ResultsBundle results = 4;
+}
+
+message Session {
+    repeated TestStatus test_status = 1;
+    optional SessionStatus session_status = 2;
+}
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..7c5e111
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/net_test_utils.py
@@ -0,0 +1,505 @@
+#!/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.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")
+    ad.adb.shell("killall -9 tcpdump", ignore_status=True)
+    ad.adb.shell("mkdir %s" % TCPDUMP_PATH, ignore_status=True)
+    ad.adb.shell("rm -rf %s/*" % TCPDUMP_PATH, ignore_status=True)
+
+    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,
+                 pull_dump=True,
+                 adb_pull_timeout=adb.DEFAULT_ADB_PULL_TIMEOUT):
+    """Stops tcpdump on any iface
+       Pulls the tcpdump file in the tcpdump dir if necessary
+
+    Args:
+        ad: android device object.
+        proc: need to know which pid to stop
+        test_name: test name to save the tcpdump file
+        pull_dump: pull tcpdump file or not
+        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)
+    if pull_dump:
+        log_path = os.path.join(ad.device_log_path, "TCPDUMP_%s" % ad.serial)
+        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)
+    return None
+
+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..4dadda1
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/net/ui_utils.py
@@ -0,0 +1,270 @@
+"""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)
+
+def wait_and_input_text(device, input_text, duration_ms=None, **kwargs):
+  """Wait for a UI element text field that can accept text entry.
+
+  This function located a UI element using wait_and_click. Once the element is
+  clicked, the text is input into the text field.
+
+  Args:
+    device: AndroidDevice, Mobly's Android controller object.
+    input_text: Text string to be entered in to the text field.
+    duration_ms: duration in milliseconds.
+    **kwargs: A set of `key=value` parameters that identifies a UI element.
+  """
+  wait_and_click(device, duration_ms, **kwargs)
+  # Replace special characters.
+  # The command "input text <string>" requires special treatment for
+  # characters ' ' and '&'.  They need to be escaped. for example:
+  #    "hello world!!&" needs to transform to "hello\ world!!\&"
+  special_chars = ' &'
+  for c in special_chars:
+    input_text = input_text.replace(c, '\\%s' % c)
+  input_text = "'" + input_text + "'"
+  args = 'input text %s' % input_text
+  device.adb.shell(args)
diff --git a/acts_tests/acts_contrib/test_utils/power/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..37b9e56
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/power/PowerGnssBaseTest.py
@@ -0,0 +1,177 @@
+#!/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', 'dpooff_nv_dict',
+                      'dpoon_nv_dict', 'mdsapp', 'modemparfile']
+        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..897868f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/WifiBaseTest.py
@@ -0,0 +1,873 @@
+#!/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 os
+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
+from acts.keys import Config
+from acts_contrib.test_utils.net import net_test_utils as nutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+
+AP_1 = 0
+AP_2 = 1
+MAX_AP_COUNT = 2
+
+
+class WifiBaseTest(BaseTestClass):
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.enable_packet_log = False
+        self.packet_log_2g = hostapd_constants.AP_DEFAULT_CHANNEL_2G
+        self.packet_log_5g = hostapd_constants.AP_DEFAULT_CHANNEL_5G
+
+    def setup_class(self):
+        if hasattr(self, 'attenuators') and self.attenuators:
+            for attenuator in self.attenuators:
+                attenuator.set_atten(0)
+        opt_param = ["pixel_models", "cnss_diag_file"]
+        self.unpack_userparams(opt_param_names=opt_param)
+        if hasattr(self, "cnss_diag_file"):
+            if isinstance(self.cnss_diag_file, list):
+                self.cnss_diag_file = self.cnss_diag_file[0]
+            if not os.path.isfile(self.cnss_diag_file):
+                self.cnss_diag_file = os.path.join(
+                    self.user_params[Config.key_config_path.value],
+                    self.cnss_diag_file)
+        if self.enable_packet_log and hasattr(self, "packet_capture"):
+            self.packet_logger = self.packet_capture[0]
+            self.packet_logger.configure_monitor_mode("2G", self.packet_log_2g)
+            self.packet_logger.configure_monitor_mode("5G", self.packet_log_5g)
+
+    def setup_test(self):
+        if (hasattr(self, "android_devices") and
+                hasattr(self, "cnss_diag_file") and
+                hasattr(self, "pixel_models")):
+            wutils.start_cnss_diags(
+                self.android_devices, self.cnss_diag_file, self.pixel_models)
+        self.tcpdump_proc = []
+        if hasattr(self, "android_devices"):
+            for ad in self.android_devices:
+                proc = nutils.start_tcpdump(ad, self.test_name)
+                self.tcpdump_proc.append((ad, proc))
+        if hasattr(self, "packet_logger"):
+            self.packet_log_pid = wutils.start_pcap(
+                    self.packet_logger, 'dual', self.test_name)
+
+    def teardown_test(self):
+        if (hasattr(self, "android_devices") and
+                hasattr(self, "cnss_diag_file") and
+                hasattr(self, "pixel_models")):
+            wutils.stop_cnss_diags(self.android_devices, self.pixel_models)
+        for proc in self.tcpdump_proc:
+            nutils.stop_tcpdump(
+                    proc[0], proc[1], self.test_name, pull_dump=False)
+        self.tcpdump_proc = []
+        if hasattr(self, "packet_logger") and self.packet_log_pid:
+            wutils.stop_pcap(
+                    self.packet_logger, self.packet_log_pid, test_status=True)
+            self.packet_log_pid = {}
+
+    def on_fail(self, test_name, begin_time):
+        if hasattr(self, "android_devices"):
+            for ad in self.android_devices:
+                ad.take_bug_report(test_name, begin_time)
+                ad.cat_adb_log(test_name, begin_time)
+                wutils.get_ssrdumps(ad)
+            if (hasattr(self, "cnss_diag_file") and
+                    hasattr(self, "pixel_models")):
+                wutils.stop_cnss_diags(self.android_devices, self.pixel_models)
+                for ad in self.android_devices:
+                    wutils.get_cnss_diag_log(ad)
+        for proc in self.tcpdump_proc:
+            nutils.stop_tcpdump(proc[0], proc[1], self.test_name)
+        self.tcpdump_proc = []
+        if hasattr(self, "packet_logger") and self.packet_log_pid:
+            wutils.stop_pcap(
+                    self.packet_logger, self.packet_log_pid, test_status=False)
+            self.packet_log_pid = {}
+
+    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..c0c1075
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/AwareBaseTest.py
@@ -0,0 +1,153 @@
+#!/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 os
+
+from acts import asserts
+from acts import utils
+from acts.base_test import BaseTestClass
+from acts.keys import Config
+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.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_class(self):
+        opt_param = ["pixel_models", "cnss_diag_file"]
+        self.unpack_userparams(opt_param_names=opt_param)
+        if hasattr(self, "cnss_diag_file"):
+            if isinstance(self.cnss_diag_file, list):
+                self.cnss_diag_file = self.cnss_diag_file[0]
+            if not os.path.isfile(self.cnss_diag_file):
+                self.cnss_diag_file = os.path.join(
+                    self.user_params[Config.key_config_path.value],
+                    self.cnss_diag_file)
+
+    def setup_test(self):
+        required_params = ("aware_default_power_mode",
+                           "dbs_supported_models",)
+        self.unpack_userparams(required_params)
+
+        if hasattr(self, "cnss_diag_file") and hasattr(self, "pixel_models"):
+            wutils.start_cnss_diags(
+                self.android_devices, self.cnss_diag_file, self.pixel_models)
+        self.tcpdump_proc = []
+        if hasattr(self, "android_devices"):
+            for ad in self.android_devices:
+                proc = nutils.start_tcpdump(ad, self.test_name)
+                self.tcpdump_proc.append((ad, proc))
+
+        for ad in self.android_devices:
+            ad.droid.wifiEnableVerboseLogging(1)
+            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):
+        if hasattr(self, "cnss_diag_file") and hasattr(self, "pixel_models"):
+            wutils.stop_cnss_diags(self.android_devices, self.pixel_models)
+        for proc in self.tcpdump_proc:
+            nutils.stop_tcpdump(
+                    proc[0], proc[1], self.test_name, pull_dump=False)
+        self.tcpdump_proc = []
+        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)
+            wutils.get_ssrdumps(ad)
+        if hasattr(self, "cnss_diag_file") and hasattr(self, "pixel_models"):
+            wutils.stop_cnss_diags(self.android_devices, self.pixel_models)
+            for ad in self.android_devices:
+                wutils.get_cnss_diag_log(ad)
+        for proc in self.tcpdump_proc:
+            nutils.stop_tcpdump(proc[0], proc[1], self.test_name)
+        self.tcpdump_proc = []
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..78d3be7
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/aware/aware_test_utils.py
@@ -0,0 +1,1053 @@
+#!/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
+
+
+def reset_device_parameters(ad):
+    """Reset device configurations.
+
+    Args:
+      ad: device to be reset
+    """
+    ad.adb.shell("cmd wifiaware reset")
+
+
+def reset_device_statistics(ad):
+    """Reset device statistics.
+
+    Args:
+        ad: device to be reset
+    """
+    ad.adb.shell("cmd wifiaware native_cb get_cb_count --reset")
+
+
+def set_power_mode_parameters(ad, power_mode):
+    """Set device power mode.
+
+    Set the power configuration DW parameters for the device based on any
+    configuration overrides (if provided)
+
+    Args:
+        ad: android device
+        power_mode: Desired power mode (INTERACTIVE or NON_INTERACTIVE)
+    """
+    if power_mode == "INTERACTIVE":
+        config_settings_high_power(ad)
+    elif power_mode == "NON_INTERACTIVE":
+        config_settings_low_power(ad)
+    else:
+        asserts.assert_false(
+            "The 'aware_default_power_mode' configuration must be INTERACTIVE or "
+            "NON_INTERACTIVE")
+
+
+#########################################################
+# Aware primitives
+#########################################################
+
+
+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..c0b18ae
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/WifiP2pBaseTest.py
@@ -0,0 +1,159 @@
+#!/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 os
+import re
+import time
+
+from acts import asserts
+from acts import utils
+from acts.base_test import BaseTestClass
+from acts.keys import Config
+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.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", "pixel_models", "cnss_diag_file")
+        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)
+        if hasattr(self, "cnss_diag_file"):
+            if isinstance(self.cnss_diag_file, list):
+                self.cnss_diag_file = self.cnss_diag_file[0]
+            if not os.path.isfile(self.cnss_diag_file):
+                self.cnss_diag_file = os.path.join(
+                    self.user_params[Config.key_config_path.value],
+                    self.cnss_diag_file)
+
+    def 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):
+        if hasattr(self, "cnss_diag_file") and hasattr(self, "pixel_models"):
+            wutils.start_cnss_diags(
+                self.android_devices, self.cnss_diag_file, self.pixel_models)
+        self.tcpdump_proc = []
+        if hasattr(self, "android_devices"):
+            for ad in self.android_devices:
+                proc = nutils.start_tcpdump(ad, self.test_name)
+                self.tcpdump_proc.append((ad, proc))
+
+        for ad in self.android_devices:
+            ad.ed.clear_all_events()
+
+    def teardown_test(self):
+        if hasattr(self, "cnss_diag_file") and hasattr(self, "pixel_models"):
+            wutils.stop_cnss_diags(self.android_devices, self.pixel_models)
+        for proc in self.tcpdump_proc:
+            nutils.stop_tcpdump(
+                    proc[0], proc[1], self.test_name, pull_dump=False)
+        self.tcpdump_proc = []
+        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)
+            wutils.get_ssrdumps(ad)
+        if hasattr(self, "cnss_diag_file") and hasattr(self, "pixel_models"):
+            wutils.stop_cnss_diags(self.android_devices, self.pixel_models)
+            for ad in self.android_devices:
+                wutils.get_cnss_diag_log(ad)
+        for proc in self.tcpdump_proc:
+            nutils.stop_tcpdump(proc[0], proc[1], self.test_name)
+        self.tcpdump_proc = []
+
+    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..32e4d2d
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_const.py
@@ -0,0 +1,104 @@
+#!/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_CONNECT_SLEEPTIME = 3
+DEFAULT_POLLING_SLEEPTIME = 1
+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..38efd32
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/p2p/wifi_p2p_test_utils.py
@@ -0,0 +1,706 @@
+#!/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_CONNECT_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'])
+        # auto-join tries 10 times to find groups, and
+        # one round takes 2 - 3 seconds.
+        maxPollingCount = 31
+        while maxPollingCount > 0:
+            ad2.droid.requestP2pPeerConfigure()
+            ad2_peerConfig = ad2.ed.pop_event(
+                p2pconsts.ONGOING_PEER_INFO_AVAILABLE_EVENT,
+                p2pconsts.DEFAULT_TIMEOUT)
+            maxPollingCount -= 1
+            if ad2_peerConfig['data'][
+                    WifiP2PEnums.WifiP2pConfig.DEVICEADDRESS_KEY]:
+                break
+            ad2.log.debug("%s is not ready for next step" % (ad2.name))
+            time.sleep(p2pconsts.DEFAULT_POLLING_SLEEPTIME)
+        asserts.assert_true(
+            ad2_peerConfig['data'][
+                WifiP2PEnums.WifiP2pConfig.DEVICEADDRESS_KEY],
+            "DUT %s does not receive the request." % (ad2.name))
+        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)
+            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..bb58b02
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/rtt/RttBaseTest.py
@@ -0,0 +1,107 @@
+#!/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 os
+
+from acts import asserts
+from acts import utils
+from acts.base_test import BaseTestClass
+from acts.keys import Config
+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.rtt import rtt_const as rconsts
+from acts_contrib.test_utils.wifi.rtt import rtt_test_utils as rutils
+
+
+class RttBaseTest(BaseTestClass):
+
+    def setup_class(self):
+        opt_param = ["pixel_models", "cnss_diag_file"]
+        self.unpack_userparams(opt_param_names=opt_param)
+        if hasattr(self, "cnss_diag_file"):
+            if isinstance(self.cnss_diag_file, list):
+                self.cnss_diag_file = self.cnss_diag_file[0]
+            if not os.path.isfile(self.cnss_diag_file):
+                self.cnss_diag_file = os.path.join(
+                    self.user_params[Config.key_config_path.value],
+                    self.cnss_diag_file)
+
+    def setup_test(self):
+        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
+
+        if hasattr(self, "cnss_diag_file") and hasattr(self, "pixel_models"):
+            wutils.start_cnss_diags(
+                self.android_devices, self.cnss_diag_file, self.pixel_models)
+        self.tcpdump_proc = []
+        if hasattr(self, "android_devices"):
+            for ad in self.android_devices:
+                proc = nutils.start_tcpdump(ad, self.test_name)
+                self.tcpdump_proc.append((ad, proc))
+
+        for ad in self.android_devices:
+            utils.set_location_service(ad, True)
+            ad.droid.wifiEnableVerboseLogging(1)
+            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):
+        if hasattr(self, "cnss_diag_file") and hasattr(self, "pixel_models"):
+            wutils.stop_cnss_diags(self.android_devices, self.pixel_models)
+        for proc in self.tcpdump_proc:
+            nutils.stop_tcpdump(
+                    proc[0], proc[1], self.test_name, pull_dump=False)
+        self.tcpdump_proc = []
+        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)
+            wutils.get_ssrdumps(ad)
+        if hasattr(self, "cnss_diag_file") and hasattr(self, "pixel_models"):
+            wutils.stop_cnss_diags(self.android_devices, self.pixel_models)
+            for ad in self.android_devices:
+                wutils.get_cnss_diag_log(ad)
+        for proc in self.tcpdump_proc:
+            nutils.stop_tcpdump(proc[0], proc[1], self.test_name)
+        self.tcpdump_proc = []
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..edaf75c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_performance_test_utils.py
@@ -0,0 +1,1649 @@
+#!/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'
+DISCONNECTION_MESSAGE_BRCM = 'driver adapter not found'
+CONST_3dB = 3.01029995664
+RSSI_ERROR_VAL = float('nan')
+RTT_REGEX = re.compile(r'^\[(?P<timestamp>\S+)\] .*? time=(?P<rtt>\S+)')
+LOSS_REGEX = re.compile(r'(?P<loss>\S+)% packet loss')
+FW_REGEX = re.compile(r'FW:(?P<firmware>\S+) HW:')
+
+
+# Threading decorator
+def nonblocking(f):
+    """Creates a decorator transforming function calls to non-blocking"""
+    def wrap(*args, **kwargs):
+        executor = ThreadPoolExecutor(max_workers=1)
+        thread_future = executor.submit(f, *args, **kwargs)
+        # Ensure resources are freed up when executor ruturns or raises
+        executor.shutdown(wait=False)
+        return thread_future
+
+    return wrap
+
+
+# JSON serializer
+def serialize_dict(input_dict):
+    """Function to serialize dicts to enable JSON output"""
+    output_dict = collections.OrderedDict()
+    for key, value in input_dict.items():
+        output_dict[_serialize_value(key)] = _serialize_value(value)
+    return output_dict
+
+
+def _serialize_value(value):
+    """Function to recursively serialize dict entries to enable JSON output"""
+    if isinstance(value, tuple):
+        return str(value)
+    if isinstance(value, list):
+        return [_serialize_value(x) for x in value]
+    elif isinstance(value, dict):
+        return serialize_dict(value)
+    else:
+        return value
+
+
+# Miscellaneous Wifi Utilities
+def extract_sub_dict(full_dict, fields):
+    sub_dict = collections.OrderedDict(
+        (field, full_dict[field]) for field in fields)
+    return sub_dict
+
+
+def validate_network(dut, ssid):
+    """Check that DUT has a valid internet connection through expected SSID
+
+    Args:
+        dut: android device of interest
+        ssid: expected ssid
+    """
+    current_network = dut.droid.wifiGetConnectionInfo()
+    try:
+        connected = wutils.validate_connection(dut) is not None
+    except:
+        connected = False
+    if connected and current_network['SSID'] == ssid:
+        return True
+    else:
+        return False
+
+
+def get_server_address(ssh_connection, dut_ip, subnet_mask):
+    """Get server address on a specific subnet,
+
+    This function retrieves the LAN 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')
+
+
+# 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_linux = 'ping -c {} -w {} -i {} -s {} -D'.format(
+        ping_count,
+        ping_deadline,
+        ping_interval,
+        ping_size,
+    )
+
+    ping_cmd_macos = 'ping -c {} -t {} -i {} -s {}'.format(
+        ping_count,
+        ping_deadline,
+        ping_interval,
+        ping_size,
+    )
+
+    if isinstance(src_device, AndroidDevice):
+        ping_cmd = '{} {}'.format(ping_cmd_linux, dest_address)
+        ping_output = src_device.adb.shell(ping_cmd,
+                                           timeout=ping_deadline + SHORT_SLEEP,
+                                           ignore_status=True)
+    elif isinstance(src_device, ssh.connection.SshConnection):
+        platform = src_device.run('uname').stdout
+        if 'linux' in platform.lower():
+            ping_cmd = 'sudo {} {}'.format(ping_cmd_linux, dest_address)
+        elif 'darwin' in platform.lower():
+            ping_cmd = "sudo {} {}| while IFS= read -r line; do printf '[%s] %s\n' \"$(gdate '+%s.%N')\" \"$line\"; done".format(
+                ping_cmd_macos, dest_address)
+        ping_output = src_device.run(ping_cmd,
+                                     timeout=ping_deadline + SHORT_SLEEP,
+                                     ignore_status=True).stdout
+    else:
+        raise TypeError('Unable to ping using src_device of type %s.' %
+                        type(src_device))
+    return PingResult(ping_output.splitlines())
+
+
+@nonblocking
+def get_ping_stats_nb(src_device, dest_address, ping_duration, ping_interval,
+                      ping_size):
+    return get_ping_stats(src_device, dest_address, ping_duration,
+                          ping_interval, ping_size)
+
+
+# Iperf utilities
+@nonblocking
+def start_iperf_client_nb(iperf_client, iperf_server_address, iperf_args, tag,
+                          timeout):
+    return iperf_client.start(iperf_server_address, iperf_args, tag, timeout)
+
+
+def get_iperf_arg_string(duration,
+                         reverse_direction,
+                         interval=1,
+                         traffic_type='TCP',
+                         socket_size=None,
+                         num_processes=1,
+                         udp_throughput='1000M',
+                         ipv6=False):
+    """Function to format iperf client arguments.
+
+    This function takes in iperf client parameters and returns a properly
+    formatter iperf arg string to be used in throughput tests.
+
+    Args:
+        duration: iperf duration in seconds
+        reverse_direction: boolean controlling the -R flag for iperf clients
+        interval: iperf print interval
+        traffic_type: string specifying TCP or UDP traffic
+        socket_size: string specifying TCP window or socket buffer, e.g., 2M
+        num_processes: int specifying number of iperf processes
+        udp_throughput: string specifying TX throughput in UDP tests, e.g. 100M
+        ipv6: boolean controlling the use of IP V6
+    Returns:
+        iperf_args: string of formatted iperf args
+    """
+    iperf_args = '-i {} -t {} -J '.format(interval, duration)
+    if ipv6:
+        iperf_args = iperf_args + '-6 '
+    if traffic_type.upper() == 'UDP':
+        iperf_args = iperf_args + '-u -b {} -l 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
+
+
+# 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
+
+
+# Generic device utils
+def get_dut_temperature(dut):
+    """Function to get dut temperature.
+
+    The function fetches and returns the reading from the temperature sensor
+    used for skin temperature and thermal throttling.
+
+    Args:
+        dut: AndroidDevice of interest
+    Returns:
+        temperature: device temperature. 0 if temperature could not be read
+    """
+    candidate_zones = [
+        '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:
+            temperature = 0
+    if temperature == 0:
+        logging.debug('Could not check DUT temperature.')
+    elif temperature > 100:
+        temperature = temperature / 1000
+    return temperature
+
+
+def wait_for_dut_cooldown(dut, target_temp=50, timeout=300):
+    """Function to wait for a DUT to cool down.
+
+    Args:
+        dut: AndroidDevice of interest
+        target_temp: target cooldown temperature
+        timeout: maxt time to wait for cooldown
+    """
+    start_time = time.time()
+    while time.time() - start_time < timeout:
+        temperature = get_dut_temperature(dut)
+        if temperature < target_temp:
+            break
+        time.sleep(SHORT_SLEEP)
+    elapsed_time = time.time() - start_time
+    logging.debug("DUT Final Temperature: {}C. Cooldown duration: {}".format(
+        temperature, elapsed_time))
+
+
+def health_check(dut, batt_thresh=5, temp_threshold=53, cooldown=1):
+    """Function to check health status of a DUT.
+
+    The function checks both battery levels and temperature to avoid DUT
+    powering off during the test.
+
+    Args:
+        dut: AndroidDevice of interest
+        batt_thresh: battery level threshold
+        temp_threshold: temperature threshold
+        cooldown: flag to wait for DUT to cool down when overheating
+    Returns:
+        health_check: boolean confirming device is healthy
+    """
+    health_check = True
+    battery_level = utils.get_battery_level(dut)
+    if battery_level < batt_thresh:
+        logging.warning("Battery level low ({}%)".format(battery_level))
+        health_check = False
+    else:
+        logging.debug("Battery level = {}%".format(battery_level))
+
+    temperature = get_dut_temperature(dut)
+    if temperature > temp_threshold:
+        if cooldown:
+            logging.warning(
+                "Waiting for DUT to cooldown. ({} C)".format(temperature))
+            wait_for_dut_cooldown(dut, target_temp=temp_threshold - 5)
+        else:
+            logging.warning("DUT Overheating ({} C)".format(temperature))
+            health_check = False
+    else:
+        logging.debug("DUT Temperature = {} C".format(temperature))
+    return health_check
+
+
+# Wifi Device utils
+def detect_wifi_platform(dut):
+    ini_check = len(dut.get_file_names('/vendor/firmware/wlan/qca_cld/'))
+    if ini_check:
+        wifi_platform = 'qcom'
+    else:
+        wifi_platform = 'brcm'
+    return wifi_platform
+
+
+def detect_wifi_decorator(f):
+    def wrap(*args, **kwargs):
+        if 'dut' in kwargs:
+            dut = kwargs['dut']
+        else:
+            dut = next(arg for arg in args if type(arg) == AndroidDevice)
+        f_decorated = '{}_{}'.format(f.__name__, detect_wifi_platform(dut))
+        f_decorated = globals()[f_decorated]
+        return (f_decorated(*args, **kwargs))
+
+    return wrap
+
+
+# Rssi Utilities
+def empty_rssi_result():
+    return collections.OrderedDict([('data', []), ('mean', None),
+                                    ('stdev', None)])
+
+
+@detect_wifi_decorator
+def get_connected_rssi(dut,
+                       num_measurements=1,
+                       polling_frequency=SHORT_SLEEP,
+                       first_measurement_delay=0,
+                       disconnect_warning=True,
+                       ignore_samples=0,
+                       interface=None):
+    """Gets all RSSI values reported for the connected access point/BSSID.
+
+    Args:
+        dut: android device object from which to get RSSI
+        num_measurements: number of scans done, and RSSIs collected
+        polling_frequency: time to wait between RSSI measurements
+        disconnect_warning: boolean controlling disconnection logging messages
+        ignore_samples: number of leading samples to ignore
+    Returns:
+        connected_rssi: dict containing the measurements results for
+        all reported RSSI values (signal_poll, per chain, etc.) and their
+        statistics
+    """
+    pass
+
+
+@nonblocking
+def get_connected_rssi_nb(dut,
+                          num_measurements=1,
+                          polling_frequency=SHORT_SLEEP,
+                          first_measurement_delay=0,
+                          disconnect_warning=True,
+                          ignore_samples=0,
+                          interface=None):
+    return get_connected_rssi(dut, num_measurements, polling_frequency,
+                              first_measurement_delay, disconnect_warning,
+                              ignore_samples, interface)
+
+
+def get_connected_rssi_qcom(dut,
+                            num_measurements=1,
+                            polling_frequency=SHORT_SLEEP,
+                            first_measurement_delay=0,
+                            disconnect_warning=True,
+                            ignore_samples=0,
+                            interface=None):
+    # yapf: disable
+    connected_rssi = collections.OrderedDict(
+        [('time_stamp', []),
+         ('bssid', []), ('ssid', []), ('frequency', []),
+         ('signal_poll_rssi', empty_rssi_result()),
+         ('signal_poll_avg_rssi', empty_rssi_result()),
+         ('chain_0_rssi', empty_rssi_result()),
+         ('chain_1_rssi', empty_rssi_result())])
+    # yapf: enable
+    previous_bssid = 'disconnected'
+    t0 = time.time()
+    time.sleep(first_measurement_delay)
+    for idx in range(num_measurements):
+        measurement_start_time = time.time()
+        connected_rssi['time_stamp'].append(measurement_start_time - t0)
+        # Get signal poll RSSI
+        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
+
+
+def get_connected_rssi_brcm(dut,
+                            num_measurements=1,
+                            polling_frequency=SHORT_SLEEP,
+                            first_measurement_delay=0,
+                            disconnect_warning=True,
+                            ignore_samples=0,
+                            interface=None):
+    # yapf: disable
+    connected_rssi = collections.OrderedDict(
+        [('time_stamp', []),
+         ('bssid', []), ('ssid', []), ('frequency', []),
+         ('signal_poll_rssi', empty_rssi_result()),
+         ('signal_poll_avg_rssi', empty_rssi_result()),
+         ('chain_0_rssi', empty_rssi_result()),
+         ('chain_1_rssi', empty_rssi_result())])
+
+    # yapf: enable
+    previous_bssid = 'disconnected'
+    t0 = time.time()
+    time.sleep(first_measurement_delay)
+    for idx in range(num_measurements):
+        measurement_start_time = time.time()
+        connected_rssi['time_stamp'].append(measurement_start_time - t0)
+        # Get signal poll RSSI
+        status_output = dut.adb.shell('wl assoc')
+        match = re.search('BSSID:.*', status_output)
+
+        if match:
+            current_bssid = match.group(0).split('\t')[0]
+            current_bssid = current_bssid.split(' ')[1]
+            connected_rssi['bssid'].append(current_bssid)
+
+        else:
+            current_bssid = 'disconnected'
+            connected_rssi['bssid'].append(current_bssid)
+            if disconnect_warning and previous_bssid != 'disconnected':
+                logging.warning('WIFI DISCONNECT DETECTED!')
+
+        previous_bssid = current_bssid
+        match = re.search('SSID:.*', status_output)
+        if match:
+            ssid = match.group(0).split(': ')[1]
+            connected_rssi['ssid'].append(ssid)
+        else:
+            connected_rssi['ssid'].append('disconnected')
+
+        #TODO: SEARCH MAP ; PICK CENTER CHANNEL
+        match = re.search('Primary channel:.*', status_output)
+        if match:
+            frequency = int(match.group(0).split(':')[1])
+            connected_rssi['frequency'].append(frequency)
+        else:
+            connected_rssi['frequency'].append(RSSI_ERROR_VAL)
+
+        try:
+            per_chain_rssi = dut.adb.shell('wl phy_rssi_ant')
+        except:
+            per_chain_rssi = DISCONNECTION_MESSAGE_BRCM
+        if DISCONNECTION_MESSAGE_BRCM not in per_chain_rssi:
+            per_chain_rssi = per_chain_rssi.split(' ')
+            chain_0_rssi = int(per_chain_rssi[1])
+            chain_1_rssi = int(per_chain_rssi[4])
+            connected_rssi['chain_0_rssi']['data'].append(chain_0_rssi)
+            connected_rssi['chain_1_rssi']['data'].append(chain_1_rssi)
+            combined_rssi = math.pow(10, chain_0_rssi / 10) + math.pow(
+                10, chain_1_rssi / 10)
+            combined_rssi = 10 * math.log10(combined_rssi)
+            connected_rssi['signal_poll_rssi']['data'].append(combined_rssi)
+            connected_rssi['signal_poll_avg_rssi']['data'].append(
+                combined_rssi)
+        else:
+            connected_rssi['chain_0_rssi']['data'].append(RSSI_ERROR_VAL)
+            connected_rssi['chain_1_rssi']['data'].append(RSSI_ERROR_VAL)
+            connected_rssi['signal_poll_rssi']['data'].append(RSSI_ERROR_VAL)
+            connected_rssi['signal_poll_avg_rssi']['data'].append(
+                RSSI_ERROR_VAL)
+        measurement_elapsed_time = time.time() - measurement_start_time
+        time.sleep(max(0, polling_frequency - measurement_elapsed_time))
+
+    # Statistics, Statistics
+    for key, val in connected_rssi.copy().items():
+        if 'data' not in val:
+            continue
+        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
+        if len(filtered_rssi_values) > ignore_samples:
+            filtered_rssi_values = filtered_rssi_values[ignore_samples:]
+        if filtered_rssi_values:
+            connected_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
+            if len(filtered_rssi_values) > 1:
+                connected_rssi[key]['stdev'] = statistics.stdev(
+                    filtered_rssi_values)
+            else:
+                connected_rssi[key]['stdev'] = 0
+        else:
+            connected_rssi[key]['mean'] = RSSI_ERROR_VAL
+            connected_rssi[key]['stdev'] = RSSI_ERROR_VAL
+
+    return connected_rssi
+
+
+@detect_wifi_decorator
+def get_scan_rssi(dut, tracked_bssids, num_measurements=1):
+    """Gets scan RSSI for specified BSSIDs.
+
+    Args:
+        dut: android device object from which to get RSSI
+        tracked_bssids: array of BSSIDs to gather RSSI data for
+        num_measurements: number of scans done, and RSSIs collected
+    Returns:
+        scan_rssi: dict containing the measurement results as well as the
+        statistics of the scan RSSI for all BSSIDs in tracked_bssids
+    """
+    pass
+
+
+@nonblocking
+def get_scan_rssi_nb(dut, tracked_bssids, num_measurements=1):
+    return get_scan_rssi(dut, tracked_bssids, num_measurements)
+
+
+def get_scan_rssi_qcom(dut, tracked_bssids, num_measurements=1):
+    scan_rssi = collections.OrderedDict()
+    for bssid in tracked_bssids:
+        scan_rssi[bssid] = empty_rssi_result()
+    for idx in range(num_measurements):
+        scan_output = dut.adb.shell(SCAN)
+        time.sleep(MED_SLEEP)
+        scan_output = dut.adb.shell(SCAN_RESULTS)
+        for bssid in tracked_bssids:
+            bssid_result = re.search(bssid + '.*',
+                                     scan_output,
+                                     flags=re.IGNORECASE)
+            if bssid_result:
+                bssid_result = bssid_result.group(0).split('\t')
+                scan_rssi[bssid]['data'].append(int(bssid_result[2]))
+            else:
+                scan_rssi[bssid]['data'].append(RSSI_ERROR_VAL)
+    # Compute mean RSSIs. Only average valid readings.
+    # Output RSSI_ERROR_VAL if no readings found.
+    for key, val in scan_rssi.items():
+        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
+        if filtered_rssi_values:
+            scan_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
+            if len(filtered_rssi_values) > 1:
+                scan_rssi[key]['stdev'] = statistics.stdev(
+                    filtered_rssi_values)
+            else:
+                scan_rssi[key]['stdev'] = 0
+        else:
+            scan_rssi[key]['mean'] = RSSI_ERROR_VAL
+            scan_rssi[key]['stdev'] = RSSI_ERROR_VAL
+    return scan_rssi
+
+
+def get_scan_rssi_brcm(dut, tracked_bssids, num_measurements=1):
+    scan_rssi = collections.OrderedDict()
+    for bssid in tracked_bssids:
+        scan_rssi[bssid] = empty_rssi_result()
+    for idx in range(num_measurements):
+        scan_output = dut.adb.shell('cmd wifi start-scan')
+        time.sleep(MED_SLEEP)
+        scan_output = dut.adb.shell('cmd wifi list-scan-results')
+        for bssid in tracked_bssids:
+            bssid_result = re.search(bssid + '.*',
+                                     scan_output,
+                                     flags=re.IGNORECASE)
+            if bssid_result:
+                bssid_result = bssid_result.group(0).split()
+                print(bssid_result)
+                scan_rssi[bssid]['data'].append(int(bssid_result[2]))
+            else:
+                scan_rssi[bssid]['data'].append(RSSI_ERROR_VAL)
+    # Compute mean RSSIs. Only average valid readings.
+    # Output RSSI_ERROR_VAL if no readings found.
+    for key, val in scan_rssi.items():
+        filtered_rssi_values = [x for x in val['data'] if not math.isnan(x)]
+        if filtered_rssi_values:
+            scan_rssi[key]['mean'] = statistics.mean(filtered_rssi_values)
+            if len(filtered_rssi_values) > 1:
+                scan_rssi[key]['stdev'] = statistics.stdev(
+                    filtered_rssi_values)
+            else:
+                scan_rssi[key]['stdev'] = 0
+        else:
+            scan_rssi[key]['mean'] = RSSI_ERROR_VAL
+            scan_rssi[key]['stdev'] = RSSI_ERROR_VAL
+    return scan_rssi
+
+
+@detect_wifi_decorator
+def get_sw_signature(dut):
+    """Function that checks the signature for wifi firmware and config files.
+
+    Returns:
+        bdf_signature: signature consisting of last three digits of bdf cksums
+        fw_signature: floating point firmware version, i.e., major.minor
+    """
+    pass
+
+
+def get_sw_signature_qcom(dut):
+    bdf_output = dut.adb.shell('cksum /vendor/firmware/bdwlan*')
+    logging.debug('BDF Checksum output: {}'.format(bdf_output))
+    bdf_signature = sum(
+        [int(line.split(' ')[0]) for line in bdf_output.splitlines()]) % 1000
+
+    fw_output = dut.adb.shell('halutil -logger -get fw')
+    logging.debug('Firmware version output: {}'.format(fw_output))
+    fw_version = re.search(FW_REGEX, fw_output).group('firmware')
+    fw_signature = fw_version.split('.')[-3:-1]
+    fw_signature = float('.'.join(fw_signature))
+    serial_hash = int(hashlib.md5(dut.serial.encode()).hexdigest(), 16) % 1000
+    return {
+        'config_signature': bdf_signature,
+        'fw_signature': fw_signature,
+        'serial_hash': serial_hash
+    }
+
+
+def get_sw_signature_brcm(dut):
+    bdf_output = dut.adb.shell('cksum /vendor/etc/wifi/bcmdhd*')
+    logging.debug('BDF Checksum output: {}'.format(bdf_output))
+    bdf_signature = sum(
+        [int(line.split(' ')[0]) for line in bdf_output.splitlines()]) % 1000
+
+    fw_output = dut.adb.shell('getprop vendor.wlan.firmware.version')
+    logging.debug('Firmware version output: {}'.format(fw_output))
+    fw_version = fw_output.split('.')[-1]
+    driver_output = dut.adb.shell('getprop vendor.wlan.driver.version')
+    driver_version = driver_output.split('.')[-1]
+    fw_signature = float('{}.{}'.format(fw_version, driver_version))
+    serial_hash = int(hashlib.md5(dut.serial.encode()).hexdigest(), 16) % 1000
+    return {
+        'config_signature': bdf_signature,
+        'fw_signature': fw_signature,
+        'serial_hash': serial_hash
+    }
+
+
+@detect_wifi_decorator
+def push_config(dut, config_file):
+    """Function to push Wifi BDF files
+
+    This function checks for existing wifi bdf files and over writes them all,
+    for simplicity, with the bdf file provided in the arguments. The dut is
+    rebooted for the bdf file to take effect
+
+    Args:
+        dut: dut to push bdf file to
+        config_file: path to bdf_file to push
+    """
+    pass
+
+
+def push_config_qcom(dut, config_file):
+    config_files_list = dut.adb.shell(
+        'ls /vendor/firmware/bdwlan*').splitlines()
+    for dst_file in config_files_list:
+        dut.push_system_file(config_file, dst_file)
+    dut.reboot()
+
+
+def push_config_brcm(dut, config_file):
+    config_files_list = dut.adb.shell('ls /vendor/etc/*.cal').splitlines()
+    for dst_file in config_files_list:
+        dut.push_system_file(config_file, dst_file)
+    dut.reboot()
+
+
+def push_firmware(dut, firmware_files):
+    """Function to push Wifi firmware files
+
+    Args:
+        dut: dut to push bdf file to
+        firmware_files: path to wlanmdsp.mbn file
+        datamsc_file: path to Data.msc file
+    """
+    for file in firmware_files:
+        dut.push_system_file(file, '/vendor/firmware/')
+    dut.reboot()
+
+
+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)
+
+
+# Link layer stats utilities
+class LinkLayerStats():
+    def __new__(self, dut, llstats_enabled=True):
+        if detect_wifi_platform(dut) == 'qcom':
+            return LinkLayerStatsQcom(dut, llstats_enabled=True)
+        else:
+            return LinkLayerStatsBrcm(dut, llstats_enabled=True)
+
+
+class LinkLayerStatsQcom():
+
+    LLSTATS_CMD = 'cat /d/wlan0/ll_stats'
+    PEER_REGEX = 'LL_STATS_PEER_ALL'
+    MCS_REGEX = re.compile(
+        r'preamble: (?P<mode>\S+), nss: (?P<num_streams>\S+), bw: (?P<bw>\S+), '
+        'mcs: (?P<mcs>\S+), bitrate: (?P<rate>\S+), txmpdu: (?P<txmpdu>\S+), '
+        'rxmpdu: (?P<rxmpdu>\S+), mpdu_lost: (?P<mpdu_lost>\S+), '
+        'retries: (?P<retries>\S+), retries_short: (?P<retries_short>\S+), '
+        'retries_long: (?P<retries_long>\S+)')
+    MCS_ID = collections.namedtuple(
+        'mcs_id', ['mode', 'num_streams', 'bandwidth', 'mcs', 'rate'])
+    MODE_MAP = {'0': '11a/g', '1': '11b', '2': '11n', '3': '11ac'}
+    BW_MAP = {'0': 20, '1': 40, '2': 80}
+
+    def __init__(self, dut, llstats_enabled=True):
+        self.dut = dut
+        self.llstats_enabled = llstats_enabled
+        self.llstats_cumulative = self._empty_llstats()
+        self.llstats_incremental = self._empty_llstats()
+
+    def update_stats(self):
+        if self.llstats_enabled:
+            try:
+                llstats_output = self.dut.adb.shell(self.LLSTATS_CMD,
+                                                    timeout=0.1)
+            except:
+                llstats_output = ''
+        else:
+            llstats_output = ''
+        self._update_stats(llstats_output)
+
+    def reset_stats(self):
+        self.llstats_cumulative = self._empty_llstats()
+        self.llstats_incremental = self._empty_llstats()
+
+    def _empty_llstats(self):
+        return collections.OrderedDict(mcs_stats=collections.OrderedDict(),
+                                       summary=collections.OrderedDict())
+
+    def _empty_mcs_stat(self):
+        return collections.OrderedDict(txmpdu=0,
+                                       rxmpdu=0,
+                                       mpdu_lost=0,
+                                       retries=0,
+                                       retries_short=0,
+                                       retries_long=0)
+
+    def _mcs_id_to_string(self, mcs_id):
+        mcs_string = '{} {}MHz Nss{} MCS{} {}Mbps'.format(
+            mcs_id.mode, mcs_id.bandwidth, mcs_id.num_streams, mcs_id.mcs,
+            mcs_id.rate)
+        return mcs_string
+
+    def _parse_mcs_stats(self, llstats_output):
+        llstats_dict = {}
+        # Look for per-peer stats
+        match = re.search(self.PEER_REGEX, llstats_output)
+        if not match:
+            self.reset_stats()
+            return collections.OrderedDict()
+        # Find and process all matches for per stream stats
+        match_iter = re.finditer(self.MCS_REGEX, llstats_output)
+        for match in match_iter:
+            current_mcs = self.MCS_ID(self.MODE_MAP[match.group('mode')],
+                                      int(match.group('num_streams')) + 1,
+                                      self.BW_MAP[match.group('bw')],
+                                      int(match.group('mcs')),
+                                      int(match.group('rate'), 16) / 1000)
+            current_stats = collections.OrderedDict(
+                txmpdu=int(match.group('txmpdu')),
+                rxmpdu=int(match.group('rxmpdu')),
+                mpdu_lost=int(match.group('mpdu_lost')),
+                retries=int(match.group('retries')),
+                retries_short=int(match.group('retries_short')),
+                retries_long=int(match.group('retries_long')))
+            llstats_dict[self._mcs_id_to_string(current_mcs)] = current_stats
+        return llstats_dict
+
+    def _diff_mcs_stats(self, new_stats, old_stats):
+        stats_diff = collections.OrderedDict()
+        for stat_key in new_stats.keys():
+            stats_diff[stat_key] = new_stats[stat_key] - old_stats[stat_key]
+        return stats_diff
+
+    def _generate_stats_summary(self, llstats_dict):
+        llstats_summary = collections.OrderedDict(common_tx_mcs=None,
+                                                  common_tx_mcs_count=0,
+                                                  common_tx_mcs_freq=0,
+                                                  common_rx_mcs=None,
+                                                  common_rx_mcs_count=0,
+                                                  common_rx_mcs_freq=0)
+        txmpdu_count = 0
+        rxmpdu_count = 0
+        for mcs_id, mcs_stats in llstats_dict['mcs_stats'].items():
+            if mcs_stats['txmpdu'] > llstats_summary['common_tx_mcs_count']:
+                llstats_summary['common_tx_mcs'] = mcs_id
+                llstats_summary['common_tx_mcs_count'] = mcs_stats['txmpdu']
+            if mcs_stats['rxmpdu'] > llstats_summary['common_rx_mcs_count']:
+                llstats_summary['common_rx_mcs'] = mcs_id
+                llstats_summary['common_rx_mcs_count'] = mcs_stats['rxmpdu']
+            txmpdu_count += mcs_stats['txmpdu']
+            rxmpdu_count += mcs_stats['rxmpdu']
+        if txmpdu_count:
+            llstats_summary['common_tx_mcs_freq'] = (
+                llstats_summary['common_tx_mcs_count'] / txmpdu_count)
+        if rxmpdu_count:
+            llstats_summary['common_rx_mcs_freq'] = (
+                llstats_summary['common_rx_mcs_count'] / rxmpdu_count)
+        return llstats_summary
+
+    def _update_stats(self, llstats_output):
+        # Parse stats
+        new_llstats = self._empty_llstats()
+        new_llstats['mcs_stats'] = self._parse_mcs_stats(llstats_output)
+        # Save old stats and set new cumulative stats
+        old_llstats = self.llstats_cumulative.copy()
+        self.llstats_cumulative = new_llstats.copy()
+        # Compute difference between new and old stats
+        self.llstats_incremental = self._empty_llstats()
+        for mcs_id, new_mcs_stats in new_llstats['mcs_stats'].items():
+            old_mcs_stats = old_llstats['mcs_stats'].get(
+                mcs_id, self._empty_mcs_stat())
+            self.llstats_incremental['mcs_stats'][
+                mcs_id] = self._diff_mcs_stats(new_mcs_stats, old_mcs_stats)
+        # Generate llstats summary
+        self.llstats_incremental['summary'] = self._generate_stats_summary(
+            self.llstats_incremental)
+        self.llstats_cumulative['summary'] = self._generate_stats_summary(
+            self.llstats_cumulative)
+
+
+class LinkLayerStatsBrcm():
+    def __init__(self, dut, llstats_enabled=True):
+        self.dut = dut
+        self.llstats_enabled = llstats_enabled
+        self.llstats_incremental = self._empty_llstats()
+        self.llstats_cumulative = self.llstats_incremental
+
+    def _empty_llstats(self):
+        return collections.OrderedDict(mcs_stats=collections.OrderedDict(),
+                                       summary=collections.OrderedDict())
+
+    def update_stats(self):
+        self.llstats_incremental = self._empty_llstats()
+        self.llstats_incremental['summary'] = collections.OrderedDict(
+            common_tx_mcs=None,
+            common_tx_mcs_count=1,
+            common_tx_mcs_freq=1,
+            common_rx_mcs=None,
+            common_rx_mcs_count=1,
+            common_rx_mcs_freq=1)
+        if self.llstats_enabled:
+            try:
+                rate_info = self.dut.adb.shell('wl rate_info', timeout=0.1)
+                self.llstats_incremental['summary'][
+                    'common_tx_mcs'] = "{} Mbps".format(
+                        re.findall('\[Tx\]:'
+                                   ' (\d+[.]*\d* Mbps)', rate_info))
+                self.llstats_incremental['summary'][
+                    'common_rx_mcs'] = "{} Mbps".format(
+                        re.findall('\[Rx\]:'
+                                   ' (\d+[.]*\d* Mbps)', rate_info))
+            except:
+                pass
diff --git a/acts_tests/acts_contrib/test_utils/wifi/wifi_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..7fbfafc
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_retail_ap.py
@@ -0,0 +1,1560 @@
+#!/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", "RAX80"): "NetgearRAX80AP",
+        ("Netgear", "RAX120"): "NetgearRAX120AP",
+        ("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"
+        }
+        self.security_mode_values = {
+            '2G': {
+                'Disable': 'security_disable',
+                'WPA2-PSK': 'security_wpa2'
+            },
+            '5G_1': {
+                'Disable': 'security_an_disable',
+                'WPA2-PSK': 'security_an_wpa2'
+            }
+        }
+
+    def read_ap_settings(self):
+        """Function to read ap wireless settings."""
+        # Get radio status (on/off)
+        self.read_radio_on_off()
+        # Get radio configuration. Note that if both radios are off, the below
+        # code will result in an error
+        with BlockingBrowser(self.ap_settings["headless_browser"],
+                             900) as browser:
+            browser.visit_persistent(self.config_page,
+                                     BROWSER_WAIT_MED,
+                                     10,
+                                     check_for_element="wireless")
+            wireless_button = browser.find_by_id("wireless").first
+            wireless_button.click()
+            time.sleep(BROWSER_WAIT_MED)
+
+            with browser.get_iframe("formframe") as iframe:
+                for key, value in self.config_page_fields.items():
+                    if "bandwidth" in key:
+                        config_item = iframe.find_by_name(value).first
+                        self.ap_settings["{}_{}".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:
+                        security_option = browser.driver.find_element_by_id(
+                            self.security_mode_values[key[0]][self.ap_settings[
+                                "{}_{}".format(key[1], key[0])]])
+                        action = selenium.webdriver.common.action_chains.ActionChains(
+                            browser.driver)
+                        action.move_to_element(
+                            security_option).click().perform()
+                        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 NetgearRAX80AP(NetgearR7000AP):
+    """Class that implements Netgear RAX AP.
+
+    Since most of the class' implementation is shared with the R7000, this
+    class inherits from NetgearR7000AP and simply redefines config parameters
+    """
+    def init_gui_data(self):
+        super().init_gui_data()
+        # Overwrite minor differences from R7000 AP
+        self.config_page = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/WLG_wireless_dual_band_r10.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.config_page_nologin = (
+            "{protocol}://{ip_address}:{port}/"
+            "WLG_wireless_dual_band_r10.htm").format(
+                protocol=self.ap_settings["protocol"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.config_page_advanced = (
+            "{protocol}://{username}:{password}@"
+            "{ip_address}:{port}/WLG_adv_dual_band2.htm").format(
+                protocol=self.ap_settings["protocol"],
+                username=self.ap_settings["admin_username"],
+                password=self.ap_settings["admin_password"],
+                ip_address=self.ap_settings["ip_address"],
+                port=self.ap_settings["port"])
+        self.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 NetgearRAX120AP(NetgearR7500AP):
+    """Class that implements Netgear RAX120 AP.
+
+    Since most of the class' implementation is shared with the R7500, this
+    class inherits from NetgearR7500AP and simply redefines config parameters
+    """
+    def __init__(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 = {
+            "Africa": "Africa",
+            "Asia": "Asia",
+            "Australia": "Australia",
+            "Canada": "Canada",
+            "Europe": "Europe",
+            "Israel": "Israel",
+            "Japan": "Japan",
+            "Korea": "Korea",
+            "Mexico": "Mexico",
+            "South America": "South America",
+            "United States": "United States",
+            "China": "China",
+            "India": "India",
+            "Malaysia": "Malaysia",
+            "Algeria": "Middle East(Algeria/Syria/Yemen)",
+            "Iran": "Middle East(Iran/Labanon/Qatar)",
+            "Egypt": "Middle East(Egypt/Tunisia/Kuwait)",
+            "Turkey": "Middle East(Turkey)",
+            "Saudi": "Middle East(Saudi Arabia/United Arab Emirates)",
+            "Russia": "Russia",
+            "Singapore": "Singapore",
+            "Taiwan": "Taiwan",
+            "Hong Kong": "Hong Kong",
+            "Vietnam": "Vietnam",
+        }
+        self.bw_mode_text_2g = {
+            "11g": "Up to 54 Mbps (11g)",
+            "VHT20": "Up to 573.5 Mbps (11ax, HT20, 1024-QAM)",
+            "VHT40": "Up to 1147 Mbps (11ax, HT40, 1024-QAM)"
+        }
+        self.bw_mode_text_5g = {
+            "VHT20": "Up to 1147 Mbps (11ax, HT20, 1024-QAM)",
+            "VHT40": "Up to 2294 Mbps (11ax, HT40, 1024-QAM)",
+            "VHT80": "Up to 4803 Mbps (80MHz) (11ax, HT80, 1024-QAM)",
+            "VHT160": "Up to 4803 Mbps (160MHz) (11ax, HT160, 1024-QAM)"
+        }
+        self.bw_mode_values = {
+            "54": "11g",
+            "573.5": "VHT20",
+            "1146": "VHT40",
+            "1147": "VHT20",
+            "2294": "VHT40",
+            "4803-HT80": "VHT80",
+            "4803-HT160": "VHT160",
+        }
+        self.security_mode_values = {
+            '2G': {
+                'Disable': 'security_disable',
+                'WPA2-PSK': 'security_wpa2'
+            },
+            '5G_1': {
+                'Disable': 'security_an_disable',
+                'WPA2-PSK': 'security_an_wpa2'
+            }
+        }
+
+
+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..419f09f
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils/wifi/wifi_test_utils.py
@@ -0,0 +1,2626 @@
+#!/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"
+
+CNSS_DIAG_CONFIG_PATH = "/data/vendor/wifi/cnss_diag/"
+CNSS_DIAG_CONFIG_FILE = "cnss_diag.conf"
+
+ROAMING_ATTN = {
+        "AP1_on_AP2_off": [
+            0,
+            0,
+            95,
+            95
+        ],
+        "AP1_off_AP2_on": [
+            95,
+            95,
+            0,
+            0
+        ],
+        "default": [
+            0,
+            0,
+            0,
+            0
+        ]
+    }
+
+
+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"
+        GERMANY = "DE"
+        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", ignore_status=True)
+    ad.log.info("wpa_supplicant log change status: %s", output)
+    utils.sync_device_time(ad)
+    ad.droid.telephonyToggleDataConnection(False)
+    set_wifi_country_code(ad, WifiEnums.CountryCode.US)
+    utils.set_ambient_display(ad, False)
+
+def set_wifi_country_code(ad, country_code):
+    """Sets the wifi country code on the device.
+
+    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(
+                ) and ad.droid.connectivityGetIPv4DefaultGateway():
+            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):
+    """Pulls dumps in the ssrdump dir
+    Args:
+        ad: android device object.
+    """
+    logs = ad.get_file_names("/data/vendor/ssrdump/")
+    if logs:
+        ad.log.info("Pulling ssrdumps %s", logs)
+        log_path = os.path.join(ad.device_log_path, "SSRDUMPS_%s" % ad.serial)
+        os.makedirs(log_path, exist_ok=True)
+        ad.pull_files(logs, log_path)
+    ad.adb.shell("find /data/vendor/ssrdump/ -type f -delete",
+            ignore_status=True)
+
+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, cnss_diag_file, pixel_models):
+    for ad in ads:
+        start_cnss_diag(ad, cnss_diag_file, pixel_models)
+
+
+def start_cnss_diag(ad, cnss_diag_file, pixel_models):
+    """Start cnss_diag to record extra wifi logs
+
+    Args:
+        ad: android device object.
+        cnss_diag_file: cnss diag config file to push to device.
+        pixel_models: pixel devices.
+    """
+    if ad.model not in pixel_models:
+        ad.log.info("Device not supported to collect pixel logger")
+        return
+    if ad.model in wifi_constants.DEVICES_USING_LEGACY_PROP:
+        prop = wifi_constants.LEGACY_CNSS_DIAG_PROP
+    else:
+        prop = wifi_constants.CNSS_DIAG_PROP
+    if ad.adb.getprop(prop) != 'true':
+        if not int(ad.adb.shell("ls -l %s%s | wc -l" %
+                                (CNSS_DIAG_CONFIG_PATH,
+                                 CNSS_DIAG_CONFIG_FILE))):
+            ad.adb.push("%s %s" % (cnss_diag_file, CNSS_DIAG_CONFIG_PATH))
+        ad.adb.shell("find /data/vendor/wifi/cnss_diag/wlan_logs/ -type f -delete",
+                ignore_status=True)
+        ad.adb.shell("setprop %s true" % prop, ignore_status=True)
+
+
+def stop_cnss_diags(ads, pixel_models):
+    for ad in ads:
+        stop_cnss_diag(ad, pixel_models)
+
+
+def stop_cnss_diag(ad, pixel_models):
+    """Stops cnss_diag
+
+    Args:
+        ad: android device object.
+        pixel_models: pixel devices.
+    """
+    if ad.model not in pixel_models:
+        ad.log.info("Device not supported to collect pixel logger")
+        return
+    if ad.model in wifi_constants.DEVICES_USING_LEGACY_PROP:
+        prop = wifi_constants.LEGACY_CNSS_DIAG_PROP
+    else:
+        prop = wifi_constants.CNSS_DIAG_PROP
+    ad.adb.shell("setprop %s false" % prop, ignore_status=True)
+
+
+def get_cnss_diag_log(ad):
+    """Pulls the cnss_diag logs in the wlan_logs dir
+    Args:
+        ad: android device object.
+    """
+    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/config_wrapper_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/config_wrapper_test.py
new file mode 100755
index 0000000..3633f76
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/config_wrapper_test.py
@@ -0,0 +1,264 @@
+#!/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 mock
+import unittest
+
+from acts import context
+from acts_contrib.test_utils.instrumentation.config_wrapper import ConfigWrapper
+from acts_contrib.test_utils.instrumentation.config_wrapper import ContextualConfigWrapper
+from acts_contrib.test_utils.instrumentation.config_wrapper import InvalidParamError
+from acts_contrib.test_utils.instrumentation.config_wrapper import merge
+
+REAL_PATHS = ['realpath/1', 'realpath/2']
+MOCK_CONFIG = {
+    'big_int': 50000,
+    'small_int': 5,
+    'float': 7.77,
+    'string': 'insert text here',
+    'real_paths_only': REAL_PATHS,
+    'real_and_fake_paths': [
+        'realpath/1', 'fakepath/0'
+    ],
+    'inner_config': {
+        'inner_val': 16
+    }
+}
+
+MOCK_TEST_OPTIONS = {
+    'MockController': {
+        'param1': 'param1_global_level',
+        'param2': 'param2_global_level'
+    },
+    'MockInstrumentationBaseTest': {
+        'MockController': {
+            'param2': 'param2_within_test_class',
+            'param3': 'param3_within_test_class'
+        },
+        'test_case_1': {
+            'MockController': {
+                'param3': 'param3_within_test_case_1'
+            }
+        },
+        'test_case_2': {
+            'MockController': {
+                'param3': 'param3_within_test_case_2'
+            }
+        }
+    }
+}
+
+
+def reset_context():
+    context._contexts = [context.RootContext()]
+
+
+class ContextualConfigWrapperTest(unittest.TestCase):
+    @mock.patch('acts.context.get_current_context')
+    def test_get_controller_config_for_test_case(self,
+                                                 mock_get_current_context):
+        """Test that ContextualConfigWrapper returns the corresponding config
+        for the current test case.
+        """
+        mock_case_context = mock.MagicMock(spec=context.TestCaseContext)
+        mock_case_context.test_class_name = 'MockInstrumentationBaseTest'
+        mock_case_context.test_case_name = 'test_case_1'
+        mock_get_current_context.return_value = mock_case_context
+
+        config = ContextualConfigWrapper(
+            MOCK_TEST_OPTIONS).get_config('MockController')
+
+        self.assertEqual(config.get('param1'), 'param1_global_level')
+        self.assertEqual(config.get('param2'), 'param2_within_test_class')
+        self.assertEqual(config.get('param3'), 'param3_within_test_case_1')
+        reset_context()
+
+    @mock.patch('acts.context.get_current_context')
+    def test_get_controller_config_for_test_class(self,
+                                                  mock_get_current_context):
+        """Test that ContextualConfigWrapper returns the corresponding config
+        for the current test case (while no test case is running).
+        """
+        mock_class_context = mock.MagicMock(spec=context.TestClassContext)
+        mock_class_context.test_class_name = 'MockInstrumentationBaseTest'
+        mock_get_current_context.return_value = mock_class_context
+
+        config = ContextualConfigWrapper(
+            MOCK_TEST_OPTIONS).get_config('MockController')
+
+        self.assertEqual(config.get('param1'), 'param1_global_level')
+        self.assertEqual(config.get('param2'), 'param2_within_test_class')
+        self.assertEqual(config.get('param3'), 'param3_within_test_class')
+        reset_context()
+
+    @mock.patch('acts.context.get_current_context')
+    def test_configs_updates_on_context_events(self,
+                                               mock_get_current_context):
+        """Test that ContextualConfigWrapper returns the corresponding config
+        for the current test case (while no test case is running).
+        """
+        mock_class_context = mock.MagicMock(spec=context.TestClassContext)
+        mock_class_context.test_class_name = 'MockInstrumentationBaseTest'
+        mock_get_current_context.return_value = mock_class_context
+
+        context_config = ContextualConfigWrapper(MOCK_TEST_OPTIONS)
+
+        # validate the class level values
+        config = context_config.get_config('MockController')
+        self.assertEqual(config.get('param1'), 'param1_global_level')
+        self.assertEqual(config.get('param2'), 'param2_within_test_class')
+        self.assertEqual(config.get('param3'), 'param3_within_test_class')
+
+        # emulate change to test_case_1 context
+        mock_case_context = mock.MagicMock(spec=context.TestCaseContext)
+        mock_case_context.test_class_name = 'MockInstrumentationBaseTest'
+        mock_case_context.test_case_name = 'test_case_1'
+        mock_get_current_context.return_value = mock_case_context
+        event = mock.Mock(spec=context.TestCaseBeginEvent)
+        event.test_class = mock.Mock()
+        event.test_case = mock.Mock()
+        context._update_test_case_context(event)
+
+        # validate the test_case_1 values
+        config = context_config.get_config('MockController')
+        self.assertEqual(config.get('param1'), 'param1_global_level')
+        self.assertEqual(config.get('param2'), 'param2_within_test_class')
+        self.assertEqual(config.get('param3'), 'param3_within_test_case_1')
+
+        # emulate change to test_case_2 context
+        mock_case_context.test_case_name = 'test_case_2'
+        context._update_test_case_context(event)
+
+        # validate the test_case_2 values
+        config = context_config.get_config('MockController')
+        self.assertEqual(config.get('param1'), 'param1_global_level')
+        self.assertEqual(config.get('param2'), 'param2_within_test_class')
+        self.assertEqual(config.get('param3'), 'param3_within_test_case_2')
+
+    @mock.patch('acts.context.get_current_context')
+    def test_original_config_is_accessible(self, mock_get_current_context):
+        """Test that _get_controller_config returns the corresponding
+        controller config for the current test case.
+        """
+        mock_case_context = mock.MagicMock(spec=context.TestCaseContext)
+        mock_case_context.test_class_name = 'MockInstrumentationBaseTest'
+        mock_case_context.test_case_name = 'test_case'
+        mock_get_current_context.return_value = mock_case_context
+
+        config = ContextualConfigWrapper(MOCK_TEST_OPTIONS)
+
+        self.assertEqual(config.original_config, MOCK_TEST_OPTIONS)
+
+
+class ConfigWrapperTest(unittest.TestCase):
+    """Unit tests for the Config Wrapper."""
+
+    def setUp(self):
+        self.mock_config = ConfigWrapper(MOCK_CONFIG)
+
+    def test_get_returns_correct_value(self):
+        """Test that get() returns the correct param value."""
+        self.assertEqual(self.mock_config.get('big_int'),
+                         MOCK_CONFIG['big_int'])
+
+    def test_get_missing_param_returns_default(self):
+        """Test that get() returns the default value if no param with the
+        requested name is found.
+        """
+        default_val = 17
+        self.assertEqual(self.mock_config.get('missing', default=default_val),
+                         default_val)
+
+    def test_get_with_custom_verification_method(self):
+        """Test that get() verifies the param with the user-provided test
+        function.
+        """
+        verifier = lambda i: i > 100
+        msg = 'Value too small'
+        self.assertEqual(self.mock_config.get('big_int', verify_fn=verifier,
+                                              failure_msg=msg),
+                         MOCK_CONFIG['big_int'])
+        with self.assertRaisesRegex(InvalidParamError, msg):
+            self.mock_config.get('small_int', verify_fn=verifier,
+                                 failure_msg=msg)
+
+    def test_get_config(self):
+        """Test that get_config() returns an empty ConfigWrapper if no
+        sub-config exists with the given name.
+        """
+        ret = self.mock_config.get_config('missing')
+        self.assertIsInstance(ret, ConfigWrapper)
+        self.assertFalse(ret)
+
+    def test_get_int(self):
+        """Test that get_int() returns the value if it is an int, and raises
+        an exception if it isn't.
+        """
+        self.assertEqual(self.mock_config.get_int('small_int'),
+                         MOCK_CONFIG['small_int'])
+        with self.assertRaisesRegex(InvalidParamError, 'of type int'):
+            self.mock_config.get_int('float')
+
+    def test_get_numeric(self):
+        """Test that get_numeric() returns the value if it is an int or float,
+        and raises an exception if it isn't.
+        """
+        self.assertEqual(self.mock_config.get_numeric('small_int'),
+                         MOCK_CONFIG['small_int'])
+        self.assertEqual(self.mock_config.get_numeric('float'),
+                         MOCK_CONFIG['float'])
+        with self.assertRaisesRegex(InvalidParamError, 'of type int or float'):
+            self.mock_config.get_numeric('string')
+
+    def test_config_wrapper_wraps_recursively(self):
+        """Test that dict values within the input config get transformed into
+        ConfigWrapper objects themselves.
+        """
+        self.assertTrue(
+            isinstance(self.mock_config.get('inner_config'), ConfigWrapper))
+        self.assertEqual(
+            self.mock_config.get('inner_config').get_int('inner_val'), 16)
+
+    def test_merge_appending_values(self):
+        """Test that the merge function appends non-conflicting values.
+        """
+        a = ConfigWrapper({'a': 1})
+        b = ConfigWrapper({'b': 2})
+        result = merge(a, b)
+        self.assertEqual(result, ConfigWrapper({'a': 1, 'b': 2}))
+
+    def test_update_conflicting_values(self):
+        """Test that the merge function appends non-conflicting values.
+        """
+        a = ConfigWrapper({'a': 1})
+        b = ConfigWrapper({'a': [1, 2, 3]})
+        result = merge(a, b)
+        self.assertEqual(result, ConfigWrapper({'a': [1, 2, 3]}))
+
+    def test_merge_merges_sub_dictionaries_recursively(self):
+        """Test that the merge function merges internal dictionaries
+        recursively.
+        """
+        a = ConfigWrapper({'dic': {'a': 0, 'c': 3, 'sub': {'x': 1}}})
+        b = ConfigWrapper({'dic': {'a': 2, 'b': 2, 'sub': {'y': 2}}})
+        result = merge(a, b)
+        self.assertEqual(result,
+                         ConfigWrapper({'dic': {'a': 2, 'b': 2, 'c': 3,
+                                                'sub': {'x': 1, 'y': 2}}}))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample.instrumentation_data_proto b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample.instrumentation_data_proto
new file mode 100644
index 0000000..42892d5
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample.instrumentation_data_proto
@@ -0,0 +1,6 @@
+a]INSTRUMENTATION_FAILED: com.google.android.powertests/androidx.test.runner.AndroidJUnitRunner
+¬"§
+†
+Error}Unable to find instrumentation info for: ComponentInfo{com.google.android.powertests/androidx.test.runner.AndroidJUnitRunner}
+
+idActivityManagerService
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample_instrumentation_proto.txt b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample_instrumentation_proto.txt
new file mode 100644
index 0000000..6de10d7
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample_instrumentation_proto.txt
@@ -0,0 +1,17 @@
+test_status {
+  result_code: -1
+  results {
+    entries {
+      key: "Error"
+      value_string: "Unable to find instrumentation info for: ComponentInfo{com.google.android.powertests/androidx.test.runner.AndroidJUnitRunner}"
+    }
+    entries {
+      key: "id"
+      value_string: "ActivityManagerService"
+    }
+  }
+}
+session_status {
+  status_code: SESSION_ABORTED
+  error_text: "INSTRUMENTATION_FAILED: com.google.android.powertests/androidx.test.runner.AndroidJUnitRunner"
+}
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample_timestamp.instrumentation_data_proto b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample_timestamp.instrumentation_data_proto
new file mode 100644
index 0000000..ecc75a5
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample_timestamp.instrumentation_data_proto
Binary files differ
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample_timestamp_proto.txt b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample_timestamp_proto.txt
new file mode 100644
index 0000000..5ac75cd
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/sample_timestamp_proto.txt
@@ -0,0 +1,110 @@
+test_status {
+  result_code: 1
+  results {
+    entries {
+      key: "class"
+      value_string: "com.google.android.powertests.PartialWakelock"
+    }
+    entries {
+      key: "current"
+      value_int: 1
+    }
+    entries {
+      key: "id"
+      value_string: "AndroidJUnitRunner"
+    }
+    entries {
+      key: "numtests"
+      value_int: 1
+    }
+    entries {
+      key: "stream"
+      value_string: "\ncom.google.android.powertests.PartialWakelock:"
+    }
+    entries {
+      key: "test"
+      value_string: "partialWakelock"
+    }
+  }
+}
+test_status {
+  results {
+    entries {
+      key: "class"
+      value_string: "com.google.android.powertests.PartialWakelock"
+    }
+    entries {
+      key: "start-timestamp"
+    }
+    entries {
+      key: "test"
+      value_string: "partialWakelock"
+    }
+    entries {
+      key: "timestamp"
+      value_long: 1567029917802
+    }
+    entries {
+      key: "timestamps-message"
+    }
+  }
+}
+test_status {
+  results {
+    entries {
+      key: "class"
+      value_string: "com.google.android.powertests.PartialWakelock"
+    }
+    entries {
+      key: "end-timestamp"
+    }
+    entries {
+      key: "test"
+      value_string: "partialWakelock"
+    }
+    entries {
+      key: "timestamp"
+      value_long: 1567029932879
+    }
+    entries {
+      key: "timestamps-message"
+    }
+  }
+}
+test_status {
+  results {
+    entries {
+      key: "class"
+      value_string: "com.google.android.powertests.PartialWakelock"
+    }
+    entries {
+      key: "current"
+      value_int: 1
+    }
+    entries {
+      key: "id"
+      value_string: "AndroidJUnitRunner"
+    }
+    entries {
+      key: "numtests"
+      value_int: 1
+    }
+    entries {
+      key: "stream"
+      value_string: "."
+    }
+    entries {
+      key: "test"
+      value_string: "partialWakelock"
+    }
+  }
+}
+session_status {
+  result_code: -1
+  results {
+    entries {
+      key: "stream"
+      value_string: "\n\nTime: 16.333\n\nOK (1 test)\n\n"
+    }
+  }
+}
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/string_values.prototxt b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/string_values.prototxt
new file mode 100644
index 0000000..5d86164
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_proto_parser/string_values.prototxt
@@ -0,0 +1,118 @@
+test_status {
+  result_code: 1
+  results {
+    entries {
+      key: "class"
+      value_string: "com.google.android.platform.powertests.IdleTestCase"
+    }
+    entries {
+      key: "current"
+      value_string: "1"
+    }
+    entries {
+      key: "id"
+      value_string: "AndroidJUnitRunner"
+    }
+    entries {
+      key: "numtests"
+      value_string: "1"
+    }
+    entries {
+      key: "stream"
+      value_string: "\ncom.google.android.platform.powertests.IdleTestCase:"
+    }
+    entries {
+      key: "test"
+      value_string: "testIdleScreenOff"
+    }
+  }
+}
+test_status {
+  result_code: 0
+  results {
+    entries {
+      key: "class"
+      value_string: "legacy-apk"
+    }
+    entries {
+      key: "start-timestamp"
+      value_string: "true"
+    }
+    entries {
+      key: "test"
+      value_string: "partialWakeLock"
+    }
+    entries {
+      key: "timestamp"
+      value_string: "1587695669034"
+    }
+    entries {
+      key: "timestamps-message"
+      value_string: "true"
+    }
+  }
+}
+test_status {
+  result_code: 0
+  results {
+    entries {
+      key: "class"
+      value_string: "legacy-apk"
+    }
+    entries {
+      key: "end-timestamp"
+      value_string: "true"
+    }
+    entries {
+      key: "test"
+      value_string: "partialWakeLock"
+    }
+    entries {
+      key: "timestamp"
+      value_string: "1587695674043"
+    }
+    entries {
+      key: "timestamps-message"
+      value_string: "true"
+    }
+  }
+}
+test_status {
+  result_code: 0
+  results {
+    entries {
+      key: "class"
+      value_string: "com.google.android.platform.powertests.IdleTestCase"
+    }
+    entries {
+      key: "current"
+      value_string: "1"
+    }
+    entries {
+      key: "id"
+      value_string: "AndroidJUnitRunner"
+    }
+    entries {
+      key: "numtests"
+      value_string: "1"
+    }
+    entries {
+      key: "stream"
+      value_string: "."
+    }
+    entries {
+      key: "test"
+      value_string: "testIdleScreenOff"
+    }
+  }
+}
+session_status {
+  result_code: -1
+  results {
+    entries {
+      key: "stream"
+      value_string: "\n\nTime: 25.735\n\nOK (1 test)\n\n"
+    }
+  }
+}
+
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/long_instrumentation_result.txt b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/long_instrumentation_result.txt
new file mode 100644
index 0000000..8867988
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/long_instrumentation_result.txt
@@ -0,0 +1,15 @@
+INSTRUMENTATION_STATUS: unimportant=ipsum
+INSTRUMENTATION_STATUS_CODE: 1
+INSTRUMENTATION_STATUS: unimportant=lorem
+INSTRUMENTATION_STATUS_CODE: 0
+INSTRUMENTATION_RESULT: long_result=never
+gonna
+give
+you
+up
+never
+gonna
+let
+you
+down
+INSTRUMENTATION_CODE: -1
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/long_statuses.txt b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/long_statuses.txt
new file mode 100644
index 0000000..6d9ad87
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/long_statuses.txt
@@ -0,0 +1,18 @@
+INSTRUMENTATION_STATUS: long_message=lorem
+ipsum
+dolor
+INSTRUMENTATION_STATUS_CODE: 1
+INSTRUMENTATION_STATUS: longer_message=lorem
+ipsum
+dolor
+sit
+amet
+INSTRUMENTATION_STATUS_CODE: 0
+INSTRUMENTATION_RESULT: stream=
+
+Time: 25.735
+
+OK (1 test)
+
+
+INSTRUMENTATION_CODE: -1
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/malformed.txt b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/malformed.txt
new file mode 100644
index 0000000..44571ab
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/malformed.txt
@@ -0,0 +1,10 @@
+INSTRUMENTATION_STATUS: wellformed=wellformedvalue
+INSTRUMENTATIONINSTRUMENTATION_STATUS: malformedmultiline=line0
+line1
+line2
+INSTRUMENTATIONSTAINSTRUMENTATION_STATUS: malformedoneline=malformedonelinevalue
+INSTRUINSTRUMENTATION_STATUS_CODE: 33
+INSINSTRUMENTATION_RESULT: result_string=result_string0
+result_string1
+result_string2
+INSTRUINSTRUMENTATION_CODE: 44
\ No newline at end of file
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/session_result_code_is_20.txt b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/session_result_code_is_20.txt
new file mode 100644
index 0000000..557b505
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/session_result_code_is_20.txt
@@ -0,0 +1,6 @@
+INSTRUMENTATION_STATUS: unimportant=ipsum
+INSTRUMENTATION_STATUS_CODE: 1
+INSTRUMENTATION_STATUS: unimportant=lorem
+INSTRUMENTATION_STATUS_CODE: 0
+INSTRUMENTATION_RESULT: unimportant=whatever
+INSTRUMENTATION_CODE: 20
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/test_status_result_code_is_42.txt b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/test_status_result_code_is_42.txt
new file mode 100644
index 0000000..9733b21
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/data/instrumentation_text_output_parser/test_status_result_code_is_42.txt
@@ -0,0 +1,4 @@
+INSTRUMENTATION_STATUS: unimportant=ipsum
+INSTRUMENTATION_STATUS_CODE: 42
+INSTRUMENTATION_RESULT: unimportant=whatever
+INSTRUMENTATION_CODE: -1
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/apps/__init__.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/dismiss_dialogs_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/dismiss_dialogs_test.py
new file mode 100644
index 0000000..19ada84
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/dismiss_dialogs_test.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+import mock
+from acts_contrib.test_utils.instrumentation.device.apps.dismiss_dialogs import \
+    DialogDismissalUtil
+from acts_contrib.test_utils.instrumentation.device.apps.dismiss_dialogs import \
+    DISMISS_DIALOGS_TIMEOUT
+
+
+class MockDialogDismissalUtil(DialogDismissalUtil):
+    """Mock DialogDismissalUtil for unit testing"""
+    def __init__(self):
+        self._dut = mock.MagicMock()
+        self._dismiss_dialogs_apk = mock.MagicMock()
+        self._dismiss_dialogs_apk.pkg_name = 'dismiss.dialogs'
+
+
+class DialogDismissalUtilTest(unittest.TestCase):
+    def setUp(self):
+        self._dismiss_dialogs_util = MockDialogDismissalUtil()
+
+    def test_dismiss_dialog_zero_apps(self):
+        """Test that no command is run if the apps arg is empty."""
+        apps = []
+        self._dismiss_dialogs_util.dismiss_dialogs(apps)
+        self._dismiss_dialogs_util._dut.adb.shell.assert_not_called()
+
+    def test_dismiss_dialog_single_app(self):
+        """
+        Test that the correct command is run when a single app is specified.
+        """
+        apps = ['sample.app.one']
+        self._dismiss_dialogs_util.dismiss_dialogs(apps)
+        expected_cmd = (
+            'am instrument -w -e apps sample.app.one '
+            '-e screenshots true -e quitOnError true '
+            'dismiss.dialogs/.DismissDialogsInstrumentation'
+        )
+        self._dismiss_dialogs_util._dut.adb.shell.assert_called_with(
+            expected_cmd, timeout=DISMISS_DIALOGS_TIMEOUT)
+
+    def test_dismiss_dialog_multiple_apps(self):
+        """
+        Test that the correct command is run when multiple apps are specified.
+        """
+        apps = ['sample.app.one', 'sample.app.two']
+        self._dismiss_dialogs_util.dismiss_dialogs(apps)
+        expected_cmd = (
+            'am instrument -w -e apps sample.app.one,sample.app.two '
+            '-e screenshots true -e quitOnError true '
+            'dismiss.dialogs/.DismissDialogsInstrumentation'
+        )
+        self._dismiss_dialogs_util._dut.adb.shell.assert_called_with(
+            expected_cmd, timeout=DISMISS_DIALOGS_TIMEOUT)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/hotword_model_extractor_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/hotword_model_extractor_test.py
new file mode 100644
index 0000000..e4115a3
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/hotword_model_extractor_test.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import unittest
+
+import mock
+from acts_contrib.test_utils.instrumentation.device.apps.hotword_model_extractor import \
+    HotwordModelExtractor
+from acts_contrib.test_utils.instrumentation.device.apps.hotword_model_extractor import \
+    MODEL_DIR
+
+GOOD_PACKAGE = 'good_package'
+GOOD_MODEL = 'good_model'
+BAD_PACKAGE = 'bad_package'
+BAD_MODEL = 'bad_model'
+
+
+def mock_pull_from_device(_, hotword_pkg, __):
+    """Mocks the AppInstaller.pull_from_device method."""
+    return mock.MagicMock() if hotword_pkg == GOOD_PACKAGE else None
+
+
+class MockZipFile(object):
+    """Class for mocking zipfile.ZipFile"""
+    def extract(self, path, _):
+        if path == os.path.join(MODEL_DIR, GOOD_MODEL):
+            return path
+        raise KeyError
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *_):
+        pass
+
+
+@mock.patch('acts_contrib.test_utils.instrumentation.device.apps.app_installer.'
+            'AppInstaller.pull_from_device', side_effect=mock_pull_from_device)
+@mock.patch('zipfile.ZipFile', return_value=MockZipFile())
+class HotwordModelExtractorTest(unittest.TestCase):
+    """Unit tests for HotwordModelExtractor."""
+    def setUp(self):
+        self.extractor = HotwordModelExtractor(mock.MagicMock())
+
+    def test_package_not_installed(self, *_):
+        result = self.extractor._extract(BAD_PACKAGE, GOOD_MODEL, '')
+        self.assertIsNone(result)
+
+    def test_voice_model_not_found(self, *_):
+        result = self.extractor._extract(GOOD_PACKAGE, BAD_MODEL, '')
+        self.assertIsNone(result)
+
+    def test_extract_model(self, *_):
+        result = self.extractor._extract(GOOD_PACKAGE, GOOD_MODEL, '')
+        self.assertEqual(result, 'res/raw/good_model')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/permissions_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/permissions_test.py
new file mode 100644
index 0000000..836f53c
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/apps/permissions_test.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+#
+# Copyright 2020 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import unittest
+
+import mock
+from acts_contrib.test_utils.instrumentation.device.apps.permissions import \
+    PermissionsUtil
+
+
+class MockPermissionsUtil(PermissionsUtil):
+    """Mock PermissionsUtil for unit testing"""
+    def __init__(self):
+        self._dut = mock.MagicMock()
+        self._permissions_apk = mock.MagicMock()
+        self._permissions_apk.pkg_name = 'permissions.util'
+
+
+class PermissionsUtilTest(unittest.TestCase):
+    def setUp(self):
+        self._permissions_util = MockPermissionsUtil()
+
+    def test_grant_all(self):
+        """Test the grant-all command."""
+        self._permissions_util.grant_all()
+        expected_cmd = (
+            'am instrument -w -r -e command grant-all '
+            'permissions.util/.PermissionInstrumentation'
+        )
+        self._permissions_util._dut.adb.shell.assert_called_with(
+            expected_cmd)
+
+
+if __name__ == '__main__':
+    unittest.main()
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/adb_command_types_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/adb_command_types_test.py
new file mode 100755
index 0000000..abb3f26
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/adb_command_types_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.adb_command_types import \
+    DeviceBinaryCommandSeries
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+    DeviceGServices
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+    DeviceSetprop
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+    DeviceSetting
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+    DeviceState
+
+
+class AdbCommandTypesTest(unittest.TestCase):
+
+    def test_device_state(self):
+        """Tests that DeviceState returns the correct ADB command with
+        set_value.
+        """
+        base_cmd = 'run command with vals'
+        val1 = 15
+        val2 = 24
+        device_state = DeviceState(base_cmd)
+        self.assertEqual(device_state.set_value(val1, val2).cmd,
+                         'run command with vals 15 24')
+
+    def test_device_state_with_base_cmd_as_format_string(self):
+        """Tests that DeviceState returns the correct ADB command if the base
+        command is given as a format string.
+        """
+        base_cmd = 'echo %s > /test/data'
+        val = 23
+        device_state = DeviceState(base_cmd)
+        self.assertEqual(device_state.set_value(val).cmd, 'echo 23 > /test/data')
+
+    def test_device_binary_state(self):
+        """Tests that DeviceState returns the correct ADB commands with toggle.
+        """
+        on_cmd = 'enable this service'
+        off_cmd = 'disable the service'
+        device_binary_state = DeviceState('', on_cmd, off_cmd)
+        self.assertEqual(device_binary_state.toggle(True).cmd, on_cmd)
+        self.assertEqual(device_binary_state.toggle(False).cmd, off_cmd)
+
+    def test_device_setprop(self):
+        """Tests that DeviceSetprop returns the correct ADB command with
+        set_value.
+        """
+        prop = 'some.property'
+        val = 3
+        device_setprop = DeviceSetprop(prop)
+        self.assertEqual(device_setprop.set_value(val).cmd,
+                         'setprop some.property 3')
+
+    def test_device_binary_setprop(self):
+        """Tests that DeviceSetprop returns the correct ADB commands with
+        toggle.
+        """
+        prop = 'some.other.property'
+        on_val = True
+        off_val = False
+        device_binary_setprop = DeviceSetprop(prop, on_val, off_val)
+        self.assertEqual(device_binary_setprop.toggle(True).cmd,
+                         'setprop some.other.property True')
+        self.assertEqual(device_binary_setprop.toggle(False).cmd,
+                         'setprop some.other.property False')
+
+    def test_device_setting(self):
+        """Tests that DeviceSetting returns the correct ADB command with
+        set_value.
+        """
+        namespace = 'global'
+        setting = 'some_new_setting'
+        val = 10
+        device_setting = DeviceSetting(namespace, setting)
+        self.assertEqual(device_setting.set_value(val).cmd,
+                         'settings put global some_new_setting 10')
+
+    def test_device_binary_setting(self):
+        """Tests that DeviceSetting returns the correct ADB commands with
+        toggle.
+        """
+        namespace = 'system'
+        setting = 'some_other_setting'
+        on_val = 'on'
+        off_val = 'off'
+        device_binary_setting = DeviceSetting(
+            namespace, setting, on_val, off_val)
+        self.assertEqual(
+            device_binary_setting.toggle(True).cmd,
+            'settings put system some_other_setting on')
+        self.assertEqual(
+            device_binary_setting.toggle(False).cmd,
+            'settings put system some_other_setting off')
+
+    def test_device_gservices(self):
+        """Tests that DeviceGServices returns the correct ADB command with
+        set_value.
+        """
+        setting = 'some_gservice'
+        val = 22
+        device_gservices = DeviceGServices(setting)
+        self.assertEqual(
+            device_gservices.set_value(val).cmd,
+            'am broadcast -a '
+            'com.google.gservices.intent.action.GSERVICES_OVERRIDE '
+            '--ei some_gservice 22')
+
+    def test_device_binary_command_series(self):
+        """Tests that DeviceBinaryCommandSuite returns the correct ADB
+        commands.
+        """
+        on_cmds = [
+            'settings put global test_setting on',
+            'setprop test.prop 1',
+            'svc test_svc enable'
+        ]
+        off_cmds = [
+            'settings put global test_setting off',
+            'setprop test.prop 0',
+            'svc test_svc disable'
+        ]
+        device_binary_command_series = DeviceBinaryCommandSeries(
+            [
+                DeviceSetting('global', 'test_setting', 'on', 'off'),
+                DeviceSetprop('test.prop'),
+                DeviceState('svc test_svc', 'enable', 'disable')
+            ]
+        )
+        self.assertEqual(list(map(lambda c: c.cmd,
+                                  device_binary_command_series.toggle(True))),
+                         on_cmds)
+        self.assertEqual(list(map(lambda c: c.cmd,
+                                  device_binary_command_series.toggle(False))),
+                         off_cmds)
+
+
+if __name__ == "__main__":
+    unittest.main()
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/instrumentation/device/command/intent_builder_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/intent_builder_test.py
new file mode 100644
index 0000000..838eeaa
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/device/command/intent_builder_test.py
@@ -0,0 +1,102 @@
+#!/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.intent_builder import \
+    IntentBuilder
+
+
+class IntentBuilderTest(unittest.TestCase):
+    """Unit tests for IntentBuilder"""
+
+    def test_set_action(self):
+        """Test that a set action yields the correct intent call"""
+        builder = IntentBuilder('am start')
+        builder.set_action('android.intent.action.SOME_ACTION')
+        self.assertEqual(builder.build(),
+                         'am start -a android.intent.action.SOME_ACTION')
+
+    def test_set_component_with_package_only(self):
+        """Test that the intent call is built properly with only the package
+        name specified.
+        """
+        builder = IntentBuilder('am broadcast')
+        builder.set_component('android.package.name')
+        self.assertEqual(builder.build(),
+                         'am broadcast -n android.package.name')
+
+    def test_set_component_with_package_and_component(self):
+        """Test that the intent call is built properly with both the package
+        and component name specified.
+        """
+        builder = IntentBuilder('am start')
+        builder.set_component('android.package.name', '.AndroidComponent')
+        self.assertEqual(
+            builder.build(),
+            'am start -n android.package.name/.AndroidComponent')
+
+    def test_set_data_uri(self):
+        """Test that a set data URI yields the correct intent call"""
+        builder = IntentBuilder()
+        builder.set_data_uri('file://path/to/file')
+        self.assertEqual(builder.build(), '-d file://path/to/file')
+
+    def test_add_flag(self):
+        """Test that additional flags are added properly"""
+        builder = IntentBuilder('am start')
+        builder.add_flag('--flag-numero-uno')
+        builder.add_flag('--flag-numero-dos')
+        self.assertEqual(
+            builder.build(), 'am start --flag-numero-uno --flag-numero-dos')
+
+    def test_add_key_value_with_empty_value(self):
+        """Test that a param with an empty value is added properly."""
+        builder = IntentBuilder('am broadcast')
+        builder.add_key_value_param('empty_param')
+        self.assertEqual(builder.build(), 'am broadcast --esn empty_param')
+
+    def test_add_key_value_with_nonempty_values(self):
+        """Test that a param with various non-empty values is added properly."""
+        builder = IntentBuilder('am start')
+        builder.add_key_value_param('bool_param', False)
+        builder.add_key_value_param('string_param', 'enabled')
+        builder.add_key_value_param('int_param', 5)
+        builder.add_key_value_param('float_param', 12.1)
+        self.assertEqual(
+            builder.build(),
+            'am start --ez bool_param false --es string_param enabled '
+            '--ei int_param 5 --ef float_param 12.1')
+
+    def test_full_intent_command(self):
+        """Test a full intent command with all possible components."""
+        builder = IntentBuilder('am broadcast')
+        builder.set_action('android.intent.action.TEST_ACTION')
+        builder.set_component('package.name', '.ComponentName')
+        builder.set_data_uri('file://path/to/file')
+        builder.add_key_value_param('empty')
+        builder.add_key_value_param('numeric_param', 11.6)
+        builder.add_key_value_param('bool_param', True)
+        builder.add_flag('--unit-test')
+        self.assertEqual(
+            builder.build(),
+            'am broadcast -a android.intent.action.TEST_ACTION '
+            '-n package.name/.ComponentName -d file://path/to/file --unit-test '
+            '--esn empty --ef numeric_param 11.6 --ez bool_param true')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/instrumentation_base_test_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/instrumentation_base_test_test.py
new file mode 100755
index 0000000..6b4ab1d
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/instrumentation_base_test_test.py
@@ -0,0 +1,114 @@
+#!/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 mock
+
+import unittest
+from unittest.mock import MagicMock
+
+from acts_contrib.test_utils.instrumentation.config_wrapper import ConfigWrapper
+
+
+from acts_contrib.test_utils.instrumentation.device.command.adb_command_types import \
+    GenericCommand
+from acts_contrib.test_utils.instrumentation.instrumentation_base_test import \
+    InstrumentationBaseTest
+
+MOCK_TEST_OPTIONS = {
+    'MockController': {
+        'param1': 1,
+        'param2': 4
+    },
+    'MockInstrumentationBaseTest': {
+        'MockController': {
+            'param2': 2,
+            'param3': 5
+        },
+        'test_case': {
+            'MockController': {
+                'param3': 3
+            }
+        }
+    }
+}
+
+
+class MockInstrumentationBaseTest(InstrumentationBaseTest):
+    """Mock test class to initialize required attributes."""
+
+    def __init__(self):
+        self.current_test_name = None
+        self.ad_dut = mock.Mock()
+        self.log = mock.Mock()
+        self._test_options = ConfigWrapper(MOCK_TEST_OPTIONS)
+        self._class_config = self._test_options.get_config(
+            self.__class__.__name__)
+
+
+class InstrumentationBaseTestTest(unittest.TestCase):
+    def setUp(self):
+        self.instrumentation_test = MockInstrumentationBaseTest()
+
+    def test_adb_run_literal_commands(self):
+        result = self.instrumentation_test.adb_run('ls /something')
+        self.assertIn('ls /something', result.keys())
+
+        result = self.instrumentation_test.adb_run(
+            ['ls /something', 'ls /other'])
+        self.assertIn('ls /something', result.keys())
+        self.assertIn('ls /other', result.keys())
+
+    def test_adb_run_generic_commands(self):
+        result = self.instrumentation_test.adb_run(
+            GenericCommand('ls /something'))
+        self.assertIn('ls /something', result.keys())
+
+        result = self.instrumentation_test.adb_run(
+            [GenericCommand('ls /something'),
+             GenericCommand('ls /other')])
+        self.assertIn('ls /something', result.keys())
+        self.assertIn('ls /other', result.keys())
+
+    def test_bugreport_on_fail_by_default(self):
+        self.instrumentation_test._test_options = ConfigWrapper({})
+        self.instrumentation_test._take_bug_report = MagicMock()
+
+        self.instrumentation_test.on_exception('test', 0)
+        self.assertEqual(1,
+                         self.instrumentation_test._take_bug_report.call_count)
+        self.instrumentation_test.on_pass('test', 0)
+        self.assertEqual(2,
+                         self.instrumentation_test._take_bug_report.call_count)
+        self.instrumentation_test.on_fail('test', 0)
+        self.assertEqual(3,
+                         self.instrumentation_test._take_bug_report.call_count)
+
+    def test_bugreport_on_end_events_can_be_disabled(self):
+        self.instrumentation_test._test_options = ConfigWrapper({
+                'bugreport_on_pass': False,
+                'bugreport_on_exception': False,
+                'bugreport_on_fail': False
+            })
+        self.instrumentation_test._take_bug_report = MagicMock()
+
+        self.instrumentation_test.on_exception('test', 0)
+        self.instrumentation_test.on_pass('test', 0)
+        self.instrumentation_test.on_fail('test', 0)
+        self.assertFalse(self.instrumentation_test._take_bug_report.called)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/instrumentation_proto_parser_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/instrumentation_proto_parser_test.py
new file mode 100644
index 0000000..a741840
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/instrumentation_proto_parser_test.py
@@ -0,0 +1,136 @@
+#!/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 unittest
+
+import mock
+from acts_contrib.test_utils.instrumentation import instrumentation_proto_parser as parser
+from acts_contrib.test_utils.instrumentation.instrumentation_proto_parser import ProtoParserError
+from acts_contrib.test_utils.instrumentation.proto.gen import instrumentation_data_pb2
+from google.protobuf import text_format
+
+DEST_DIR = 'dest/proto_dir'
+SOURCE_PATH = 'source/proto/protofile'
+SAMPLE_PROTO = 'data/instrumentation_proto_parser/sample.instrumentation_data_proto'
+SAMPLE_TIMESTAMP_PROTO = 'data/instrumentation_proto_parser/sample_timestamp.instrumentation_data_proto'
+SAMPLE_STRING_TIMESTAMP_PROTO = 'data/instrumentation_proto_parser/string_values.prototxt'
+
+SAMPLE_ERROR_TEXT = 'INSTRUMENTATION_FAILED: com.google.android.powertests/' \
+                    'androidx.test.runner.AndroidJUnitRunner'
+SAMPLE_STREAM = '\n\nTime: 16.333\n\nOK (1 test)\n\n'
+
+
+class InstrumentationProtoParserTest(unittest.TestCase):
+    """Unit tests for instrumentation proto parser."""
+
+    def setUp(self):
+        self.ad = mock.MagicMock()
+        self.ad.external_storage_path = ''
+
+    @mock.patch('os.path.exists', return_value=True)
+    def test_pull_proto_returns_correct_path_given_source(self, *_):
+        self.assertEqual(parser.pull_proto(self.ad, DEST_DIR, SOURCE_PATH),
+                         'dest/proto_dir/protofile')
+
+    @mock.patch('os.path.exists', return_value=True)
+    def test_pull_proto_returns_correct_path_from_default_location(self, *_):
+        self.ad.adb.shell.return_value = 'default'
+        self.assertEqual(parser.pull_proto(self.ad, DEST_DIR),
+                         'dest/proto_dir/default')
+
+    def test_pull_proto_fails_if_no_default_proto_found(self, *_):
+        self.ad.adb.shell.return_value = None
+        with self.assertRaisesRegex(
+            ProtoParserError, 'No instrumentation result'):
+            parser.pull_proto(self.ad, DEST_DIR)
+
+    @mock.patch('os.path.exists', return_value=False)
+    def test_pull_proto_fails_if_adb_pull_fails(self, *_):
+        with self.assertRaisesRegex(ProtoParserError, 'Failed to pull'):
+            parser.pull_proto(self.ad, DEST_DIR, SOURCE_PATH)
+
+    def test_has_instrumentation_proto_with_default_location__existing_proto(
+        self):
+        # Emulates finding a file named default.proto
+        self.ad.adb.shell.return_value = 'default.proto'
+        self.assertTrue(parser.has_instrumentation_proto(self.ad))
+
+    def test_has_instrumentation_proto_with_default_location__non_existing_proto(
+        self):
+        # Emulates not finding a default proto
+        self.ad.adb.shell.return_value = ''
+        self.assertFalse(parser.has_instrumentation_proto(self.ad))
+
+    def test_parser_converts_valid_proto(self):
+        proto_file = os.path.join(os.path.dirname(__file__), SAMPLE_PROTO)
+        self.assertIsInstance(parser.get_session_from_local_file(proto_file),
+                              instrumentation_data_pb2.Session)
+
+    def test_get_test_timestamps(self):
+        proto_file = os.path.join(os.path.dirname(__file__),
+                                  SAMPLE_TIMESTAMP_PROTO)
+        session = parser.get_session_from_local_file(proto_file)
+        timestamps = parser.get_test_timestamps(session)
+        self.assertEqual(
+            timestamps['partialWakelock'][parser.START_TIMESTAMP],
+            1567029917802)
+        self.assertEqual(
+            timestamps['partialWakelock'][parser.END_TIMESTAMP], 1567029932879)
+
+    def test_get_test_timestamps_when_defined_as_strings(self):
+        proto_file = os.path.join(os.path.dirname(__file__),
+                                  SAMPLE_STRING_TIMESTAMP_PROTO)
+        session = instrumentation_data_pb2.Session()
+        with open(proto_file) as f:
+            text_format.Parse(f.read(), session)
+
+        timestamps = parser.get_test_timestamps(session)
+        self.assertEqual(
+            timestamps['partialWakeLock'][
+                parser.START_TIMESTAMP], 1587695669034
+
+        )
+        self.assertEqual(
+            timestamps['partialWakeLock'][parser.END_TIMESTAMP],
+            1587695674043)
+
+    def test_get_instrumentation_result_with_session_aborted(self):
+        proto_file = os.path.join(os.path.dirname(__file__), SAMPLE_PROTO)
+        session = parser.get_session_from_local_file(proto_file)
+        expected = {
+            'status_code': 1,
+            'result_code': 0,
+            'error_text': SAMPLE_ERROR_TEXT
+        }
+        self.assertDictEqual(
+            parser.get_instrumentation_result(session), expected)
+
+    def test_get_instrumentation_result_with_session_completed(self):
+        proto_file = os.path.join(os.path.dirname(__file__),
+                                  SAMPLE_TIMESTAMP_PROTO)
+        session = parser.get_session_from_local_file(proto_file)
+        expected = {
+            'status_code': 0,
+            'result_code': -1,
+            'stream': SAMPLE_STREAM
+        }
+        self.assertDictEqual(
+            parser.get_instrumentation_result(session), expected)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/instrumentation_text_output_parser_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/instrumentation_text_output_parser_test.py
new file mode 100644
index 0000000..3334ab9
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/instrumentation_text_output_parser_test.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import os
+import unittest
+
+from acts_contrib.test_utils.instrumentation import instrumentation_text_output_parser
+
+SAMPLE_WITH_LONG_STATUSES = \
+    'data/instrumentation_text_output_parser/long_statuses.txt'
+SAMPLE_WITH_LONG_INSTRUMENTATION_RESULT = \
+    'data/instrumentation_text_output_parser/long_instrumentation_result.txt'
+SAMPLE_WITH_20_AS_SESSION_RESULT_CODE = \
+    'data/instrumentation_text_output_parser/session_result_code_is_20.txt'
+SAMPLE_WITH_42_AS_TEST_STATUS_RESULT_CODE = \
+    'data/instrumentation_text_output_parser/test_status_result_code_is_42.txt'
+SAMPLE_MALFORMED = 'data/instrumentation_text_output_parser/malformed.txt'
+
+
+class InstrumentationTextOutputParserTest(unittest.TestCase):
+
+    def test_get_test_result_code(self):
+        code = instrumentation_text_output_parser._extract_status_code(
+            'INSTRUMENTATION_STATUS_CODE: 9',
+            instrumentation_text_output_parser._Markers.STATUS_CODE)
+
+        self.assertEqual(code, 9)
+
+    # sometimes instrumentation output is malformed on some device kinds,
+    # the best we can do is account for the common errors.
+    def test_get_test_result_code_on_malformed_lines(self):
+        code = instrumentation_text_output_parser._extract_status_code(
+            'INSINSTRUMENTATION_STATUS_CODE: 9',
+            instrumentation_text_output_parser._Markers.STATUS_CODE)
+
+        self.assertEqual(code, 9)
+
+    def test_get_session_result_code(self):
+        code = instrumentation_text_output_parser._extract_status_code(
+            'INSTRUMENTATION_CODE: 7',
+            instrumentation_text_output_parser._Markers.CODE)
+
+        self.assertEqual(code, 7)
+
+    # sometimes instrumentation output is malformed on some device kinds,
+    # the best we can do is account for the common errors.
+    def test_get_session_result_code_on_malformed_lines(self):
+        code = instrumentation_text_output_parser._extract_status_code(
+            'ININSTRUMENTATION_CODE: 7',
+            instrumentation_text_output_parser._Markers.CODE)
+
+        self.assertEqual(code, 7)
+
+    def test_get_test_status_key_value(self):
+        (key, value) = instrumentation_text_output_parser._extract_key_value(
+            'INSTRUMENTATION_STATUS: hello=world',
+            instrumentation_text_output_parser._Markers.STATUS)
+
+        self.assertEqual(key, 'hello')
+        self.assertEqual(value, 'world')
+
+    # sometimes instrumentation output is malformed on some device kinds,
+    # the best we can do is account for the common errors.
+    def test_get_test_status_key_value_on_malformed_lines(self):
+        (key, value) = instrumentation_text_output_parser._extract_key_value(
+            'INSTRUMENINSTRUMENTATION_STATUS: hello=world',
+            instrumentation_text_output_parser._Markers.STATUS)
+
+        self.assertEqual(key, 'hello')
+        self.assertEqual(value, 'world')
+
+    def test_get_session_result_key_value(self):
+        (key, value) = instrumentation_text_output_parser._extract_key_value(
+            'INSTRUMENTATION_RESULT: hola=mundo',
+            instrumentation_text_output_parser._Markers.RESULT)
+
+        self.assertEqual(key, 'hola')
+        self.assertEqual(value, 'mundo')
+
+    # sometimes instrumentation output is malformed on some device kinds,
+    # the best we can do is account for the common errors.
+    def test_get_session_result_key_value_on_malformed_lines(self):
+        (key, value) = instrumentation_text_output_parser._extract_key_value(
+            'INSTINSTRUMENTATION_RESULT: hello=world',
+            instrumentation_text_output_parser._Markers.RESULT)
+
+        self.assertEqual(key, 'hello')
+        self.assertEqual(value, 'world')
+
+    def test_multiline_status_gets_parsed_correctly(self):
+        file = os.path.join(os.path.dirname(__file__),
+                            SAMPLE_WITH_LONG_STATUSES)
+
+        session = instrumentation_text_output_parser.parse_from_file(
+            file)
+
+        long_message_entry = session.test_status[0].results.entries[0]
+        self.assertEqual(long_message_entry.key, 'long_message')
+        self.assertEqual(long_message_entry.value_string.split('\n'),
+                         ['lorem', 'ipsum', 'dolor'])
+        longer_message_entry = session.test_status[1].results.entries[0]
+        self.assertEqual(longer_message_entry.key, 'longer_message')
+        self.assertEqual(longer_message_entry.value_string.split('\n'),
+                         ['lorem', 'ipsum', 'dolor', 'sit', 'amet'])
+
+    def test_multiline_instrumentation_result_gets_parsed_correctly(self):
+        file = os.path.join(os.path.dirname(__file__),
+                            SAMPLE_WITH_LONG_INSTRUMENTATION_RESULT)
+
+        session = instrumentation_text_output_parser.parse_from_file(
+            file)
+
+        entry = session.session_status.results.entries[0]
+        self.assertEqual(entry.key, 'long_result')
+        self.assertEqual(entry.value_string.split('\n'),
+                         ['never', 'gonna', 'give', 'you', 'up', 'never',
+                          'gonna', 'let', 'you', 'down'])
+
+    def test_session_result_code(self):
+        file = os.path.join(os.path.dirname(__file__),
+                            SAMPLE_WITH_20_AS_SESSION_RESULT_CODE)
+
+        session = instrumentation_text_output_parser.parse_from_file(
+            file)
+
+        self.assertEqual(session.session_status.result_code, 20)
+
+    def test_test_status_result_code(self):
+        file = os.path.join(os.path.dirname(__file__),
+                            SAMPLE_WITH_42_AS_TEST_STATUS_RESULT_CODE)
+
+        session = instrumentation_text_output_parser.parse_from_file(
+            file)
+
+        self.assertEqual(session.test_status[0].result_code, 42)
+
+    def test_malformed_case(self):
+        file = os.path.join(os.path.dirname(__file__),
+                            SAMPLE_MALFORMED)
+
+        session = instrumentation_text_output_parser.parse_from_file(
+            file)
+
+        entry0 = session.test_status[0].results.entries[0]
+        self.assertEqual(entry0.key, 'wellformed')
+        self.assertEqual(entry0.value_string, 'wellformedvalue')
+
+        entry1 = session.test_status[0].results.entries[1]
+        self.assertEqual(entry1.key, 'malformedmultiline')
+        self.assertEqual(entry1.value_string.split('\n'),
+                         ['line0', 'line1', 'line2'])
+
+        entry2 = session.test_status[0].results.entries[2]
+        self.assertEqual(entry2.key, 'malformedoneline')
+        self.assertEqual(entry2.value_string, 'malformedonelinevalue')
+
+        self.assertEqual(session.test_status[0].result_code, 33)
+
+        session_entry = session.session_status.results.entries[0]
+        self.assertEqual(session_entry.key, 'result_string')
+        self.assertEqual(session_entry.value_string.split('\n'),
+                         ['result_string0', 'result_string1', 'result_string2'])
+        self.assertEqual(session.session_status.result_code, 44)
+
+
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/power/__init__.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/power/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/power/__init__.py
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/power/instrumentation_power_test_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/power/instrumentation_power_test_test.py
new file mode 100644
index 0000000..5ab8b24
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/power/instrumentation_power_test_test.py
@@ -0,0 +1,280 @@
+#!/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 import signals
+from acts.controllers import power_metrics
+from acts_contrib.test_utils.instrumentation import instrumentation_proto_parser as parser
+from acts_contrib.test_utils.instrumentation.config_wrapper import ConfigWrapper
+from acts_contrib.test_utils.instrumentation.power.instrumentation_power_test import ACCEPTANCE_THRESHOLD
+from acts_contrib.test_utils.instrumentation.power.instrumentation_power_test import InstrumentationPowerTest
+import mock
+
+# avg: 2.214, stdev: 1.358, max: 4.78, min: 0.61
+AMPS = [1.64, 2.98, 1.72, 3.45, 1.31, 4.78, 3.43, 0.61, 1.19, 1.03]
+RAW_DATA = list(zip(range(10), AMPS))
+# timestamps that cover all samples
+TIMESTAMP_LIMITS = {parser.START_TIMESTAMP: 0,
+                    parser.END_TIMESTAMP: 10_000}
+
+TIMESTAMPS = {'instrTest1': TIMESTAMP_LIMITS,
+              'instrTest2': TIMESTAMP_LIMITS}
+
+METRICS = power_metrics.generate_test_metrics(
+    raw_data=RAW_DATA, voltage=4.2, timestamps=TIMESTAMPS)
+
+
+class MockInstrumentationPowerTest(InstrumentationPowerTest):
+    """Mock test class to initialize required attributes."""
+
+    def __init__(self):
+        self.log = mock.Mock()
+        self.metric_logger = mock.Mock()
+        self.current_test_name = 'test_case'
+        self._test_options = ConfigWrapper({ACCEPTANCE_THRESHOLD: {}})
+
+    def set_criteria(self, criteria):
+        """Set the acceptance criteria for metrics validation."""
+        self._test_options = ConfigWrapper(
+            {ACCEPTANCE_THRESHOLD: criteria})
+
+
+class InstrumentationPowerTestTest(unittest.TestCase):
+    """Unit tests for InstrumentationPowerTest."""
+
+    def setUp(self):
+        self.instrumentation_power_test = MockInstrumentationPowerTest()
+
+    def test_validate_power_results_lower_and_upper_limit_accept(self):
+        """Test that validate_power_results accept passing measurements
+        given a lower and upper limit.
+        """
+        criteria_accept = {
+            'instrTest1': {
+                'avg_current': {
+                    'unit_type': 'current',
+                    'unit': 'A',
+                    'lower_limit': 1.5,
+                    'upper_limit': 2.5
+                },
+                'max_current': {
+                    'unit_type': 'current',
+                    'unit': 'mA',
+                    'upper_limit': 5000
+                }
+            }
+        }
+        self.instrumentation_power_test.set_criteria(criteria_accept)
+        with self.assertRaises(signals.TestPass):
+            self.instrumentation_power_test.validate_metrics(METRICS,
+                                                             'instrTest1')
+
+    def test_validate_power_results_lower_and_upper_limit_reject(self):
+        """Test that validate_power_results reject failing measurements
+        given a lower and upper limit.
+        """
+        criteria_reject = {
+            'instrTest1': {
+                'avg_current': {
+                    'unit_type': 'current',
+                    'unit': 'A',
+                    'lower_limit': 1.5,
+                    'upper_limit': 2
+                },
+                'max_current': {
+                    'unit_type': 'current',
+                    'unit': 'mA',
+                    'upper_limit': 4000
+                }
+            }
+        }
+        self.instrumentation_power_test.set_criteria(criteria_reject)
+        with self.assertRaises(signals.TestFailure):
+            self.instrumentation_power_test.validate_metrics(METRICS,
+                                                             'instrTest1')
+
+    def test_validate_power_results_expected_value_and_deviation_accept(self):
+        """Test that validate_power_results accept passing measurements
+        given an expected value and percent deviation.
+        """
+        criteria_accept = {
+            'instrTest1': {
+                'stdev_current': {
+                    'unit_type': 'current',
+                    'unit': 'A',
+                    'expected_value': 1.5,
+                    'percent_deviation': 20
+                }
+            }
+        }
+        self.instrumentation_power_test.set_criteria(criteria_accept)
+        with self.assertRaises(signals.TestPass):
+            self.instrumentation_power_test.validate_metrics(METRICS,
+                                                             'instrTest1')
+
+    def test_validate_power_results_expected_value_and_deviation_reject(self):
+        """Test that validate_power_results reject failing measurements
+        given an expected value and percent deviation.
+        """
+        criteria_reject = {
+            'instrTest1': {
+                'min_current': {
+                    'unit_type': 'current',
+                    'unit': 'mA',
+                    'expected_value': 500,
+                    'percent_deviation': 10
+                }
+            }
+        }
+        self.instrumentation_power_test.set_criteria(criteria_reject)
+        with self.assertRaises(signals.TestFailure):
+            self.instrumentation_power_test.validate_metrics(METRICS,
+                                                             'instrTest1')
+
+    def test_validate_power_results_no_such_test(self):
+        """Test that validate_power_results skip validation if there are no
+        criteria matching the specified instrumentation test name.
+        """
+        criteria_wrong_test = {
+            'instrTest2': {
+                'min_current': {
+                    'unit_type': 'current',
+                    'unit': 'A',
+                    'expected_value': 2,
+                    'percent_deviation': 20
+                }
+            }
+        }
+        self.instrumentation_power_test.set_criteria(criteria_wrong_test)
+        with self.assertRaises(signals.TestPass):
+            self.instrumentation_power_test.validate_metrics(METRICS,
+                                                             'instrTest1')
+
+    def test_validate_power_results_no_such_metric(self):
+        """Test that validate_power_results skip validation if the specified
+        metric is invalid.
+        """
+        criteria_invalid_metric = {
+            'instrTest1': {
+                'no_such_metric': {
+                    'unit_type': 'current',
+                    'unit': 'A',
+                    'lower_limit': 5,
+                    'upper_limit': 7
+                }
+            }
+        }
+        self.instrumentation_power_test.set_criteria(criteria_invalid_metric)
+        with self.assertRaises(signals.TestPass):
+            self.instrumentation_power_test.validate_metrics(METRICS,
+                                                             'instrTest1')
+
+    def test_validate_power_results_criteria_missing_params(self):
+        """Test that validate_power_results skip validation if the specified
+        metric has missing parameters.
+        """
+        criteria_missing_params = {
+            'instrTest1': {
+                'avg_current': {
+                    'unit': 'A',
+                    'lower_limit': 1,
+                    'upper_limit': 2
+                }
+            }
+        }
+        self.instrumentation_power_test.set_criteria(criteria_missing_params)
+        with self.assertRaises(signals.TestPass):
+            self.instrumentation_power_test.validate_metrics(METRICS,
+                                                             'instrTest1')
+
+    def test_validate_power_results_pass_if_all_tests_accept(self):
+        """Test that validate_power_results succeeds if it accepts the results
+        of all instrumentation tests.
+        """
+        criteria_multi_test_accept = {
+            'instrTest1': {
+                'avg_current': {
+                    'unit_type': 'current',
+                    'unit': 'A',
+                    'lower_limit': 2
+                },
+                'stdev_current': {
+                    'unit_type': 'current',
+                    'unit': 'mA',
+                    'expected_value': 1250,
+                    'percent_deviation': 30
+                }
+            },
+            'instrTest2': {
+                'max_current': {
+                    'unit_type': 'current',
+                    'unit': 'A',
+                    'upper_limit': 5
+                },
+                'avg_power': {
+                    'unit_type': 'power',
+                    'unit': 'W',
+                    'upper_limit': 10
+                }
+            }
+        }
+        self.instrumentation_power_test.set_criteria(criteria_multi_test_accept)
+        with self.assertRaises(signals.TestPass):
+            self.instrumentation_power_test.validate_metrics(METRICS,
+                                                             'instrTest1',
+                                                             'instrTest2')
+
+    def test_validate_power_results_fail_if_at_least_one_test_rejects(self):
+        """Test that validate_power_results fails if it rejects the results
+        of at least one instrumentation test.
+        """
+        criteria_multi_test_reject = {
+            'instrTest1': {
+                'avg_current': {
+                    'unit_type': 'current',
+                    'unit': 'A',
+                    'lower_limit': 2
+                },
+                'stdev_current': {
+                    'unit_type': 'current',
+                    'unit': 'mA',
+                    'expected_value': 1250,
+                    'percent_deviation': 30
+                }
+            },
+            'instrTest2': {
+                'max_current': {
+                    'unit_type': 'current',
+                    'unit': 'A',
+                    'upper_limit': 5
+                },
+                'avg_power': {
+                    'unit_type': 'power',
+                    'unit': 'W',
+                    'upper_limit': 8
+                }
+            }
+        }
+        self.instrumentation_power_test.set_criteria(criteria_multi_test_reject)
+        with self.assertRaises(signals.TestFailure):
+            self.instrumentation_power_test.validate_metrics(METRICS,
+                                                             'instrTest1',
+                                                             'instrTest2')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/power/thresholds_test.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/power/thresholds_test.py
new file mode 100644
index 0000000..d93add3
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/power/thresholds_test.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2020 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import unittest
+
+from acts.controllers.power_metrics import Metric
+from acts_contrib.test_utils.instrumentation import config_wrapper
+from acts_contrib.test_utils.instrumentation.power.thresholds import AbsoluteThresholds
+
+
+class AbsoluteThresholdsTest(unittest.TestCase):
+    def test_build_from_absolutes(self):
+        thresholds = AbsoluteThresholds(lower=1, upper=2,
+                                        unit_type='power',
+                                        unit='mW')
+        self.assertEqual(thresholds.lower, Metric(1, 'power', 'mW'))
+        self.assertEqual(thresholds.upper, Metric(2, 'power', 'mW'))
+
+    def test_build_from_percentual_deviation(self):
+        """Test construction of thresholds defined by percentual deviation."""
+        thresholds = (AbsoluteThresholds
+                      .from_percentual_deviation(expected=100,
+                                                 percentage=2,
+                                                 unit_type='power',
+                                                 unit='mW'))
+        self.assertEqual(thresholds.lower, Metric(98, 'power', 'mW'))
+        self.assertEqual(thresholds.upper, Metric(102, 'power', 'mW'))
+
+    def test_build_from_absolutes_config(self):
+        """Test that thresholds by absolute values can be built through configs.
+        """
+        config = config_wrapper.ConfigWrapper(
+            {'lower_limit': 1, 'upper_limit': 2,
+             'unit_type': 'power', 'unit': 'mW'})
+        thresholds = AbsoluteThresholds.from_threshold_conf(config)
+        self.assertEqual(thresholds.lower, Metric(1, 'power', 'mW'))
+        self.assertEqual(thresholds.upper, Metric(2, 'power', 'mW'))
+
+    def test_build_from_deviation_config(self):
+        """Test that thresholds for percentual deviations can be built."""
+        config = config_wrapper.ConfigWrapper(
+            {'expected_value': 100, 'percent_deviation': 2,
+             'unit_type': 'power', 'unit': 'mW'})
+        thresholds = AbsoluteThresholds.from_threshold_conf(config)
+        self.assertEqual(thresholds.lower, Metric(98, 'power', 'mW'))
+        self.assertEqual(thresholds.upper, Metric(102, 'power', 'mW'))
+
+    def test_build_from_config_without_unit_type(self):
+        """Test that from_threshold_conf raises an error if not given a unit
+        type."""
+        config = config_wrapper.ConfigWrapper(
+            {'expected_value': 100, 'percent_deviation': 2,
+             'unit_type': 'power'})
+        expected_msg = 'A threshold config must contain a unit'
+        with self.assertRaisesRegex(ValueError, expected_msg):
+            AbsoluteThresholds.from_threshold_conf(config)
+
+    def test_build_from_config_without_unit(self):
+        """Test that from_threshold_conf raises an error if not given a unit."""
+        config = config_wrapper.ConfigWrapper(
+            {'expected_value': 100, 'percent_deviation': 2,
+             'unit': 'mW'})
+        expected_msg = 'A threshold config must contain a unit_type'
+        with self.assertRaisesRegex(ValueError, expected_msg):
+            AbsoluteThresholds.from_threshold_conf(config)
+
+    def test_build_from_config_without_limits_nor_deviation(self):
+        """Test that from_threshold_conf raises an error if not given a limits
+        nor percentual deviation arguments."""
+        config = config_wrapper.ConfigWrapper(
+            {'unit_type': 'power',
+             'unit': 'mW'})
+        expected_msg = ('Thresholds must be either absolute .* or defined by '
+                        'percentual deviation')
+        with self.assertRaisesRegex(ValueError, expected_msg):
+            AbsoluteThresholds.from_threshold_conf(config)
+
+    def test_build_from_deviation_config_without_expected_value(self):
+        """Test that from_threshold_conf raises an error if percentual deviation
+        definition is missing a expected value."""
+        config = config_wrapper.ConfigWrapper(
+            {'percent_deviation': 2,
+             'unit_type': 'power', 'unit': 'mW'})
+        expected_msg = ('Incomplete definition of a threshold by percentual '
+                        'deviation. percent_deviation given, but missing '
+                        'expected_value')
+        with self.assertRaisesRegex(ValueError, expected_msg):
+            AbsoluteThresholds.from_threshold_conf(config)
+
+    def test_build_from_deviation_config_without_percent_deviation(self):
+        """Test that from_threshold_conf raises an error if percentual deviation
+        definition is missing a percent deviation value."""
+        config = config_wrapper.ConfigWrapper(
+            {'expected_value': 100,
+             'unit_type': 'power', 'unit': 'mW'})
+        expected_msg = ('Incomplete definition of a threshold by percentual '
+                        'deviation. expected_value given, but missing '
+                        'percent_deviation')
+        with self.assertRaisesRegex(ValueError, expected_msg):
+            AbsoluteThresholds.from_threshold_conf(config)
diff --git a/acts_tests/acts_contrib/test_utils_tests/instrumentation/unit_test_suite.py b/acts_tests/acts_contrib/test_utils_tests/instrumentation/unit_test_suite.py
new file mode 100755
index 0000000..d253cb3
--- /dev/null
+++ b/acts_tests/acts_contrib/test_utils_tests/instrumentation/unit_test_suite.py
@@ -0,0 +1,32 @@
+#!/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 sys
+import unittest
+
+
+def main():
+    suite = unittest.TestLoader().discover(
+        start_dir=os.path.dirname(__file__), pattern='*_test.py')
+    return suite
+
+
+if __name__ == '__main__':
+    test_suite = main()
+    runner = unittest.TextTestRunner()
+    test_run = runner.run(test_suite)
+    sys.exit(not test_run.wasSuccessful())
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/LabTtffTest.py b/acts_tests/tests/google/gnss/LabTtffTest.py
index 59e525c..3eaa053 100644
--- a/acts_tests/tests/google/gnss/LabTtffTest.py
+++ b/acts_tests/tests/google/gnss/LabTtffTest.py
@@ -26,8 +26,8 @@
 from pandas import DataFrame
 from collections import namedtuple
 from acts.controllers.spectracom_lib import gsg6
-from acts.test_utils.gnss import gnss_test_utils as gutils
-from acts.test_utils.gnss import gnss_testlog_utils as glogutils
+from acts_contrib.test_utils.gnss import gnss_test_utils as gutils
+from acts_contrib.test_utils.gnss import gnss_testlog_utils as glogutils
 
 DEVICE_GPSLOG_FOLDER = '/sdcard/Android/data/com.android.gpstool/files/'
 GPS_PKG_NAME = 'com.android.gpstool'
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/instrumentation/power/apollo/ApolloIdleTest.py b/acts_tests/tests/google/instrumentation/power/apollo/ApolloIdleTest.py
index 6a37b84..7ea2635 100644
--- a/acts_tests/tests/google/instrumentation/power/apollo/ApolloIdleTest.py
+++ b/acts_tests/tests/google/instrumentation/power/apollo/ApolloIdleTest.py
@@ -1,4 +1,4 @@
-from acts.test_utils.instrumentation.power.apollo.ApolloBaseTest import ApolloBaseTest
+from acts_contrib.test_utils.instrumentation.power.apollo.ApolloBaseTest import ApolloBaseTest
 
 
 class ApolloIdleTest(ApolloBaseTest):
diff --git a/acts_tests/tests/google/instrumentation/power/apollo/ApolloNoSetupTest.py b/acts_tests/tests/google/instrumentation/power/apollo/ApolloNoSetupTest.py
index 194869a..ae93451 100644
--- a/acts_tests/tests/google/instrumentation/power/apollo/ApolloNoSetupTest.py
+++ b/acts_tests/tests/google/instrumentation/power/apollo/ApolloNoSetupTest.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
 
 
 class ApolloNoSetupTest(instrumentation_power_test.InstrumentationPowerTest):
diff --git a/acts_tests/tests/google/instrumentation/power/apps/ChromeTest.py b/acts_tests/tests/google/instrumentation/power/apps/ChromeTest.py
index 2e59650..69d6383 100644
--- a/acts_tests/tests/google/instrumentation/power/apps/ChromeTest.py
+++ b/acts_tests/tests/google/instrumentation/power/apps/ChromeTest.py
@@ -14,10 +14,10 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.device.apps.dismiss_dialogs import \
+from acts_contrib.test_utils.instrumentation.device.apps.dismiss_dialogs import \
   DialogDismissalUtil
-from acts.test_utils.instrumentation.power import instrumentation_power_test
-from acts.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
 
 
 BIG_FILE_PUSH_TIMEOUT = 600
diff --git a/acts_tests/tests/google/instrumentation/power/apps/DuoTest.py b/acts_tests/tests/google/instrumentation/power/apps/DuoTest.py
index 9e9ac6e..39d064c 100644
--- a/acts_tests/tests/google/instrumentation/power/apps/DuoTest.py
+++ b/acts_tests/tests/google/instrumentation/power/apps/DuoTest.py
@@ -15,10 +15,10 @@
 #   limitations under the License.
 
 
-from acts.test_utils.instrumentation.power import instrumentation_power_test
-from acts.test_utils.instrumentation.device.apps.app_installer import \
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import \
   AppInstaller
-from acts.test_utils.instrumentation.device.command.adb_commands import goog
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import goog
 
 
 class DuoTest(instrumentation_power_test.InstrumentationPowerTest):
diff --git a/acts_tests/tests/google/instrumentation/power/apps/FlightSimulatorTest.py b/acts_tests/tests/google/instrumentation/power/apps/FlightSimulatorTest.py
index 4991dca..72291be 100644
--- a/acts_tests/tests/google/instrumentation/power/apps/FlightSimulatorTest.py
+++ b/acts_tests/tests/google/instrumentation/power/apps/FlightSimulatorTest.py
@@ -15,8 +15,8 @@
 #   limitations under the License.
 
 
-from acts.test_utils.instrumentation.power import instrumentation_power_test
-from acts.test_utils.instrumentation.device.apps.app_installer import \
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.apps.app_installer import \
   AppInstaller
 
 
diff --git a/acts_tests/tests/google/instrumentation/power/apps/LauncherTest.py b/acts_tests/tests/google/instrumentation/power/apps/LauncherTest.py
index 169d276..d8a7c80 100644
--- a/acts_tests/tests/google/instrumentation/power/apps/LauncherTest.py
+++ b/acts_tests/tests/google/instrumentation/power/apps/LauncherTest.py
@@ -15,7 +15,7 @@
 #   limitations under the License.
 
 
-from acts.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
 
 class LauncherTest(instrumentation_power_test.InstrumentationPowerTest):
   """Test class for running app launcher test cases."""
diff --git a/acts_tests/tests/google/instrumentation/power/apps/WallpaperTest.py b/acts_tests/tests/google/instrumentation/power/apps/WallpaperTest.py
index 304f5d3..28d398a 100644
--- a/acts_tests/tests/google/instrumentation/power/apps/WallpaperTest.py
+++ b/acts_tests/tests/google/instrumentation/power/apps/WallpaperTest.py
@@ -15,7 +15,7 @@
 #   limitations under the License.
 
 
-from acts.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
 
 SMALL_FILE_PUSH_TIMEOUT = 10
 
diff --git a/acts_tests/tests/google/instrumentation/power/camera/ImageCaptureTest.py b/acts_tests/tests/google/instrumentation/power/camera/ImageCaptureTest.py
index cbd025e..b3ab8a1 100644
--- a/acts_tests/tests/google/instrumentation/power/camera/ImageCaptureTest.py
+++ b/acts_tests/tests/google/instrumentation/power/camera/ImageCaptureTest.py
@@ -14,8 +14,8 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.power import instrumentation_power_test
-from acts.test_utils.instrumentation.device.apps.dismiss_dialogs import \
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.apps.dismiss_dialogs import \
     DialogDismissalUtil
 
 
diff --git a/acts_tests/tests/google/instrumentation/power/chre/ChrePowerTest.py b/acts_tests/tests/google/instrumentation/power/chre/ChrePowerTest.py
index 61c9e64..dd38663 100644
--- a/acts_tests/tests/google/instrumentation/power/chre/ChrePowerTest.py
+++ b/acts_tests/tests/google/instrumentation/power/chre/ChrePowerTest.py
@@ -14,9 +14,9 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.device.command.adb_commands import common
-from acts.test_utils.instrumentation.device.command.adb_commands import goog
-from acts.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import goog
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
 
 
 BIG_FILE_PUSH_TIMEOUT = 600
diff --git a/acts_tests/tests/google/instrumentation/power/comms/bluetooth/BluetoothEnableTest.py b/acts_tests/tests/google/instrumentation/power/comms/bluetooth/BluetoothEnableTest.py
index 502ba8c..581b903 100644
--- a/acts_tests/tests/google/instrumentation/power/comms/bluetooth/BluetoothEnableTest.py
+++ b/acts_tests/tests/google/instrumentation/power/comms/bluetooth/BluetoothEnableTest.py
@@ -14,8 +14,8 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.power import instrumentation_power_test
-from acts.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
 
 
 class BluetoothEnableTest(instrumentation_power_test.InstrumentationPowerTest):
diff --git a/acts_tests/tests/google/instrumentation/power/comms/telephony/LteTest.py b/acts_tests/tests/google/instrumentation/power/comms/telephony/LteTest.py
index 0022fee..7f3b245 100644
--- a/acts_tests/tests/google/instrumentation/power/comms/telephony/LteTest.py
+++ b/acts_tests/tests/google/instrumentation/power/comms/telephony/LteTest.py
@@ -15,9 +15,9 @@
 #   limitations under the License.
 import time
 
-from acts.test_utils.instrumentation.power.instrumentation_power_test \
+from acts_contrib.test_utils.instrumentation.power.instrumentation_power_test \
     import InstrumentationPowerTest
-from acts.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
 
 from acts import signals
 from acts.libs.proc.job import TimeoutError
diff --git a/acts_tests/tests/google/instrumentation/power/comms/telephony/ThreeGVoiceCallTest.py b/acts_tests/tests/google/instrumentation/power/comms/telephony/ThreeGVoiceCallTest.py
index 18af944..7252a14 100644
--- a/acts_tests/tests/google/instrumentation/power/comms/telephony/ThreeGVoiceCallTest.py
+++ b/acts_tests/tests/google/instrumentation/power/comms/telephony/ThreeGVoiceCallTest.py
@@ -14,9 +14,9 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.power.instrumentation_power_test \
+from acts_contrib.test_utils.instrumentation.power.instrumentation_power_test \
     import InstrumentationPowerTest
-from acts.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
 
 
 class ThreeGVoiceCallTest(InstrumentationPowerTest):
diff --git a/acts_tests/tests/google/instrumentation/power/idle/IdleTest.py b/acts_tests/tests/google/instrumentation/power/idle/IdleTest.py
index 90427ca..8ea1926 100644
--- a/acts_tests/tests/google/instrumentation/power/idle/IdleTest.py
+++ b/acts_tests/tests/google/instrumentation/power/idle/IdleTest.py
@@ -19,9 +19,9 @@
 from acts import signals
 from acts.libs.proc.job import TimeoutError
 from acts.controllers.android_lib.errors import AndroidDeviceError
-from acts.test_utils.instrumentation.power import instrumentation_power_test
-from acts.test_utils.instrumentation.device.command.adb_commands import common
-from acts.test_utils.instrumentation.device.command.adb_commands import goog
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import goog
 
 
 class IdleTest(instrumentation_power_test.InstrumentationPowerTest):
diff --git a/acts_tests/tests/google/instrumentation/power/idle/PartialWakeLockTest.py b/acts_tests/tests/google/instrumentation/power/idle/PartialWakeLockTest.py
index 35d3d12..4c0bb3b 100644
--- a/acts_tests/tests/google/instrumentation/power/idle/PartialWakeLockTest.py
+++ b/acts_tests/tests/google/instrumentation/power/idle/PartialWakeLockTest.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
 
 
 class PartialWakeLockTest(instrumentation_power_test.InstrumentationPowerTest):
diff --git a/acts_tests/tests/google/instrumentation/power/idle/PowerPresubmitTest.py b/acts_tests/tests/google/instrumentation/power/idle/PowerPresubmitTest.py
index acb3a46..ae5808b 100644
--- a/acts_tests/tests/google/instrumentation/power/idle/PowerPresubmitTest.py
+++ b/acts_tests/tests/google/instrumentation/power/idle/PowerPresubmitTest.py
@@ -14,8 +14,8 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation import config_wrapper
-from acts.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation import config_wrapper
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
 
 
 class PowerPresubmitTest(instrumentation_power_test.InstrumentationPowerTest):
diff --git a/acts_tests/tests/google/instrumentation/power/media/LocalMusicPlaybackTest.py b/acts_tests/tests/google/instrumentation/power/media/LocalMusicPlaybackTest.py
index 5dbc215..f93f467 100644
--- a/acts_tests/tests/google/instrumentation/power/media/LocalMusicPlaybackTest.py
+++ b/acts_tests/tests/google/instrumentation/power/media/LocalMusicPlaybackTest.py
@@ -14,10 +14,10 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.device.apps.dismiss_dialogs import \
+from acts_contrib.test_utils.instrumentation.device.apps.dismiss_dialogs import \
     DialogDismissalUtil
-from acts.test_utils.instrumentation.device.command.adb_commands import common
-from acts.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
 
 BIG_FILE_PUSH_TIMEOUT = 600
 
diff --git a/acts_tests/tests/google/instrumentation/power/media/LocalYoutubeMusicPlaybackTest.py b/acts_tests/tests/google/instrumentation/power/media/LocalYoutubeMusicPlaybackTest.py
index d0dd746..5762e55 100644
--- a/acts_tests/tests/google/instrumentation/power/media/LocalYoutubeMusicPlaybackTest.py
+++ b/acts_tests/tests/google/instrumentation/power/media/LocalYoutubeMusicPlaybackTest.py
@@ -14,10 +14,10 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.device.apps.dismiss_dialogs import \
+from acts_contrib.test_utils.instrumentation.device.apps.dismiss_dialogs import \
     DialogDismissalUtil
-from acts.test_utils.instrumentation.device.command.adb_commands import common
-from acts.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
 
 from acts import signals
 from acts.controllers.android_lib.errors import AndroidDeviceError
diff --git a/acts_tests/tests/google/instrumentation/power/media/VideoPlaybackTest.py b/acts_tests/tests/google/instrumentation/power/media/VideoPlaybackTest.py
index 7981bb4..7eb6bbf 100644
--- a/acts_tests/tests/google/instrumentation/power/media/VideoPlaybackTest.py
+++ b/acts_tests/tests/google/instrumentation/power/media/VideoPlaybackTest.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.power import instrumentation_power_test
+from acts_contrib.test_utils.instrumentation.power import instrumentation_power_test
 
 BIG_FILE_PUSH_TIMEOUT = 600
 
diff --git a/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationBrowserTest.py b/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationBrowserTest.py
index 41a3d20..6c84eab 100644
--- a/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationBrowserTest.py
+++ b/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationBrowserTest.py
@@ -15,10 +15,10 @@
 #   limitations under the License.
 
 from acts.test_decorators import repeated_test
-from acts.test_utils.instrumentation.power.vzw_dou_automation import \
+from acts_contrib.test_utils.instrumentation.power.vzw_dou_automation import \
   vzw_dou_automation_base_test
-from acts.test_utils.instrumentation.device.command.adb_commands import goog
-from acts.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import goog
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
 
 
 class VzWDoUAutomationBrowserTest(
diff --git a/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationEmailTest.py b/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationEmailTest.py
index 6161cf7..83feefd 100644
--- a/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationEmailTest.py
+++ b/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationEmailTest.py
@@ -15,7 +15,7 @@
 #   limitations under the License.
 
 from acts.test_decorators import repeated_test
-from acts.test_utils.instrumentation.power.vzw_dou_automation import \
+from acts_contrib.test_utils.instrumentation.power.vzw_dou_automation import \
   vzw_dou_automation_base_test
 
 
diff --git a/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationFileTest.py b/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationFileTest.py
index 8fd81d3..d30e845 100644
--- a/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationFileTest.py
+++ b/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationFileTest.py
@@ -15,7 +15,7 @@
 #   limitations under the License.
 
 from acts.test_decorators import repeated_test
-from acts.test_utils.instrumentation.power.vzw_dou_automation import \
+from acts_contrib.test_utils.instrumentation.power.vzw_dou_automation import \
   vzw_dou_automation_base_test
 
 
diff --git a/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationIdleTest.py b/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationIdleTest.py
index 486dc54..adf9b8d 100644
--- a/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationIdleTest.py
+++ b/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationIdleTest.py
@@ -15,10 +15,10 @@
 #   limitations under the License.
 
 from acts.test_decorators import repeated_test
-from acts.test_utils.instrumentation.power.vzw_dou_automation import \
+from acts_contrib.test_utils.instrumentation.power.vzw_dou_automation import \
     vzw_dou_automation_base_test
-from acts.test_utils.instrumentation.device.command.adb_commands import common
-from acts.test_utils.instrumentation.device.command.adb_commands import goog
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import goog
 
 
 class VzWDoUAutomationIdleTest(
diff --git a/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationPhoneCallTest.py b/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationPhoneCallTest.py
index ed76a7d..4185219 100644
--- a/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationPhoneCallTest.py
+++ b/acts_tests/tests/google/instrumentation/power/vzw_dou_automation/VzWDoUAutomationPhoneCallTest.py
@@ -17,12 +17,12 @@
 import time
 
 from acts.test_decorators import repeated_test
-from acts.test_utils.instrumentation.device.command.adb_commands import goog
-from acts.test_utils.instrumentation.power.vzw_dou_automation import \
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import goog
+from acts_contrib.test_utils.instrumentation.power.vzw_dou_automation import \
   vzw_dou_automation_comp_base_test
-from acts.test_utils.instrumentation.power.vzw_dou_automation import \
+from acts_contrib.test_utils.instrumentation.power.vzw_dou_automation import \
   vzw_dou_automation_base_test
-from acts.test_utils.instrumentation.device.command.adb_commands import common
+from acts_contrib.test_utils.instrumentation.device.command.adb_commands import common
 
 class VzWDoUAutomationPhoneCallTest(
     vzw_dou_automation_comp_base_test.VzWDoUAutomationCompBaseTest):
diff --git a/acts_tests/tests/google/instrumentation/power/vzwdou/VzwDoUIdleTest.py b/acts_tests/tests/google/instrumentation/power/vzwdou/VzwDoUIdleTest.py
index 4ad6d64..527bad2 100644
--- a/acts_tests/tests/google/instrumentation/power/vzwdou/VzwDoUIdleTest.py
+++ b/acts_tests/tests/google/instrumentation/power/vzwdou/VzwDoUIdleTest.py
@@ -14,7 +14,7 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-from acts.test_utils.instrumentation.power.vzwdou import vzw_dou_base_test
+from acts_contrib.test_utils.instrumentation.power.vzwdou import vzw_dou_base_test
 
 class VzwDoUIdleTest(vzw_dou_base_test.VzwDoUBaseTest):
   """Class for running VZW DoU idle test cases"""
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 35e9582..726b274 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 ae69271..3305a9a 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 bdba6e1..e957f59 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 77a83ec..1056dd6 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 14eafab..8165c72 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 503583c..b44829d 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 7f2ad68..f6767bc 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 9e5a46f..a166b9d 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 64f7f25..e9b9a94 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 05b3549..b33579c 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 aee773c..222a505 100644
--- a/acts_tests/tests/google/wifi/WifiEnterpriseTest.py
+++ b/acts_tests/tests/google/wifi/WifiEnterpriseTest.py
@@ -21,8 +21,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/WifiHiddenSSIDTest.py b/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
index da2c066..88736f8 100644
--- a/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
+++ b/acts_tests/tests/google/wifi/WifiHiddenSSIDTest.py
@@ -15,10 +15,10 @@
 #   limitations under the License.
 
 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.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 ac2022f..ef24d4d 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 3ea3b3b..973fc08 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 ed0d8d7..9669cdd 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 3a00d0c..8244fb8 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 de202b8..8dd1a97 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 f3b5102..d779529 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 8b4c197..e220c2a 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 81aa86d..af28280 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 7de603c..0d13a0b 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 3290091..db6dbab 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 59d65e7..f52dd6e 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 4998ac4..0c230f7 100755
--- a/acts_tests/tests/google/wifi/WifiPasspointTest.py
+++ b/acts_tests/tests/google/wifi/WifiPasspointTest.py
@@ -19,15 +19,15 @@
 import queue
 import time
 
-from acts.test_utils.net import ui_utils as uutils
-import acts.test_utils.wifi.wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.net import ui_utils as uutils
+import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 import WifiManagerTest
 from acts import asserts
 from acts import signals
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel.tel_test_utils import get_operator_name
+from acts_contrib.test_utils.tel.tel_test_utils import get_operator_name
 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 97d9374..4492090 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 879ca0d..a2caefd 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 0ef6bb6..21f1cbf 100644
--- a/acts_tests/tests/google/wifi/WifiRoamingTest.py
+++ b/acts_tests/tests/google/wifi/WifiRoamingTest.py
@@ -15,8 +15,8 @@
 
 from acts import asserts
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 
 class WifiRoamingTest(WifiBaseTest):
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 da88012..fb75b60 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 f267078..af84ce4 100755
--- a/acts_tests/tests/google/wifi/WifiScannerScanTest.py
+++ b/acts_tests/tests/google/wifi/WifiScannerScanTest.py
@@ -22,9 +22,9 @@
 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_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 afe2a84..b656b7b 100644
--- a/acts_tests/tests/google/wifi/WifiServiceApiTest.py
+++ b/acts_tests/tests/google/wifi/WifiServiceApiTest.py
@@ -22,9 +22,9 @@
 from acts import signals
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.wifi import wifi_constants
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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
 
 
 class WifiServiceApiTest(WifiBaseTest):
diff --git a/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py b/acts_tests/tests/google/wifi/WifiSoftApAcsTest.py
index a3cbdfe..5595f4b 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 375ae93..4e4fb89 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 bcaab8d..42bede1 100644
--- a/acts_tests/tests/google/wifi/WifiSoftApTest.py
+++ b/acts_tests/tests/google/wifi/WifiSoftApTest.py
@@ -22,15 +22,15 @@
 from acts import asserts
 from acts import utils
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.net import socket_test_utils as sutils
-from acts.test_utils.tel import tel_defines
-from acts.test_utils.tel import tel_test_utils as tel_utils
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_AUTO
-from acts.test_utils.wifi import wifi_constants
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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 035cac3..6694cb9 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 10ba578..102c7cd 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 56c6427..6612faa 100644
--- a/acts_tests/tests/google/wifi/WifiTetheringTest.py
+++ b/acts_tests/tests/google/wifi/WifiTetheringTest.py
@@ -23,17 +23,17 @@
 from acts import utils
 from acts.controllers import adb
 from acts.test_decorators import test_tracker_info
-from acts.test_utils.tel import tel_defines
-from acts.test_utils.tel.tel_data_utils import wait_for_cell_data_connection
-from acts.test_utils.tel.tel_test_utils import get_operator_name
-from acts.test_utils.tel.tel_test_utils import verify_http_connection
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_2G
-from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
-from acts.test_utils.net import socket_test_utils as sutils
-from acts.test_utils.net import arduino_test_utils as dutils
-from acts.test_utils.net import net_test_utils as nutils
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
+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.net import socket_test_utils as sutils
+from acts_contrib.test_utils.net import arduino_test_utils as dutils
+from acts_contrib.test_utils.net import net_test_utils as nutils
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 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 52ab2fd..2dc7b20 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 0bfeed3..6a4ba4b 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 a3c70f3..02ca6cf 100644
--- a/acts_tests/tests/google/wifi/WifiWpa3OweTest.py
+++ b/acts_tests/tests/google/wifi/WifiWpa3OweTest.py
@@ -14,10 +14,10 @@
 #   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
 
 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 WifiWpa3OweTest(WifiBaseTest):
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 a92552b..ef8857a 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/performance/WifiAwareRvrTest.py b/acts_tests/tests/google/wifi/aware/performance/WifiAwareRvrTest.py
index b1e71e1..add1e12 100644
--- a/acts_tests/tests/google/wifi/aware/performance/WifiAwareRvrTest.py
+++ b/acts_tests/tests/google/wifi/aware/performance/WifiAwareRvrTest.py
@@ -24,12 +24,12 @@
 from acts.controllers import iperf_server as ipf
 from acts.controllers import iperf_client as ipc
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.test_utils.wifi import ota_sniffer
-from acts.test_utils.wifi import wifi_retail_ap as retail_ap
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi import wifi_performance_test_utils as wputils
-from acts.test_utils.wifi.aware import aware_const as aconsts
-from acts.test_utils.wifi.aware import aware_test_utils as autils
+from acts_contrib.test_utils.wifi import ota_sniffer
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi.aware import aware_const as aconsts
+from acts_contrib.test_utils.wifi.aware import aware_test_utils as autils
 from WifiRvrTest import WifiRvrTest
 
 AccessPointTuple = collections.namedtuple(('AccessPointTuple'),
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 2d2c514..85fcd26 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/p2p/performance/WifiP2pRvrTest.py b/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py
index 85b9a0d..c4b2dc6 100644
--- a/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py
+++ b/acts_tests/tests/google/wifi/p2p/performance/WifiP2pRvrTest.py
@@ -25,12 +25,12 @@
 from acts.controllers import iperf_server as ipf
 from acts.controllers import iperf_client as ipc
 from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
-from acts.test_utils.wifi import ota_sniffer
-from acts.test_utils.wifi import wifi_retail_ap as retail_ap
-from acts.test_utils.wifi import wifi_test_utils as wutils
-from acts.test_utils.wifi import wifi_performance_test_utils as wputils
-from acts.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
-from acts.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
+from acts_contrib.test_utils.wifi import ota_sniffer
+from acts_contrib.test_utils.wifi import wifi_retail_ap as retail_ap
+from acts_contrib.test_utils.wifi import wifi_test_utils as wutils
+from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wputils
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_const as p2pconsts
+from acts_contrib.test_utils.wifi.p2p import wifi_p2p_test_utils as wp2putils
 from WifiRvrTest import WifiRvrTest
 
 AccessPointTuple = collections.namedtuple(('AccessPointTuple'),
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 a40647b..cdb1293 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(RttBaseTest, WifiBaseTest):
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 a3262b1..1051fc4 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 703efac..f3b39a5 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):