Merge "ACTS: Fix regex in get_current_user" am: ffaeca04f7 am: c2583f8ed3 am: 93c85c9dc9

Change-Id: I722b11a3ba4d457e7e40e7cc5695a1be37bad7e2
diff --git a/acts/framework/acts/controllers/buds_lib/apollo_lib.py b/acts/framework/acts/controllers/buds_lib/apollo_lib.py
index 9b971ff..7b0f80f 100644
--- a/acts/framework/acts/controllers/buds_lib/apollo_lib.py
+++ b/acts/framework/acts/controllers/buds_lib/apollo_lib.py
@@ -46,7 +46,7 @@
 import time
 
 import serial
-from acts import tracelogger
+from acts.controllers.buds_lib import tako_trace_logger
 from acts.controllers.buds_lib import logserial
 from acts.controllers.buds_lib.b29_lib import B29Device
 from acts.controllers.buds_lib.dev_utils import apollo_log_decoder
@@ -55,7 +55,7 @@
 from logging import Logger
 from retry import retry
 
-logging = tracelogger.TakoTraceLogger(Logger('apollo'))
+logging = tako_trace_logger.TakoTraceLogger(Logger('apollo'))
 
 BAUD_RATE = 115200
 BYTE_SIZE = 8
diff --git a/acts/framework/acts/controllers/buds_lib/b29_lib.py b/acts/framework/acts/controllers/buds_lib/b29_lib.py
index bb4ea7d..df6a163 100644
--- a/acts/framework/acts/controllers/buds_lib/b29_lib.py
+++ b/acts/framework/acts/controllers/buds_lib/b29_lib.py
@@ -28,12 +28,12 @@
 import os
 import re
 import time
-
-from acts import tracelogger
-from acts import utils
 from logging import Logger
 
-logging = tracelogger.TakoTraceLogger(Logger(__file__))
+from acts import utils
+from acts.controllers.buds_lib import tako_trace_logger
+
+logging = tako_trace_logger.TakoTraceLogger(Logger(__file__))
 DEVICE_REGEX = (
     r'_(?P<device_serial>[A-Z0-9]+)-(?P<interface>\w+)\s->\s'
     r'(\.\./){2}(?P<port>\w+)'
diff --git a/acts/framework/acts/controllers/buds_lib/logserial.py b/acts/framework/acts/controllers/buds_lib/logserial.py
index 58bf154..6b18e3c 100644
--- a/acts/framework/acts/controllers/buds_lib/logserial.py
+++ b/acts/framework/acts/controllers/buds_lib/logserial.py
@@ -21,15 +21,15 @@
 import sys
 import time
 import uuid
+from logging import Logger
 from threading import Thread
 
 import serial
 from serial.tools import list_ports
 
-from acts import tracelogger
-from logging import Logger
+from acts.controllers.buds_lib import tako_trace_logger
 
-logging = tracelogger.TakoTraceLogger(Logger(__file__))
+logging = tako_trace_logger.TakoTraceLogger(Logger(__file__))
 
 RETRIES = 0
 
diff --git a/acts/framework/acts/controllers/buds_lib/tako_trace_logger.py b/acts/framework/acts/controllers/buds_lib/tako_trace_logger.py
new file mode 100644
index 0000000..ff31840
--- /dev/null
+++ b/acts/framework/acts/controllers/buds_lib/tako_trace_logger.py
@@ -0,0 +1,56 @@
+#!/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
+
+
+class TakoTraceLogger(tracelogger.TraceLogger):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.d = self.debug
+        self.e = self.error
+        self.i = self.info
+        self.t = self.step
+        self.w = self.warning
+
+    def _logger_level(self, level_name):
+        level = logging.getLevelName(level_name)
+        return lambda *args, **kwargs: self._logger.log(level, *args, **kwargs)
+
+    def step(self, msg, *args, **kwargs):
+        """Delegate a step call to the underlying logger."""
+        self._log_with(self._logger_level('STEP'), 1, msg, *args, **kwargs)
+
+    def device(self, msg, *args, **kwargs):
+        """Delegate a device call to the underlying logger."""
+        self._log_with(self._logger_level('DEVICE'), 1, msg, *args, **kwargs)
+
+    def suite(self, msg, *args, **kwargs):
+        """Delegate a device call to the underlying logger."""
+        self._log_with(self._logger_level('SUITE'), 1, msg, *args, **kwargs)
+
+    def case(self, msg, *args, **kwargs):
+        """Delegate a case call to the underlying logger."""
+        self._log_with(self._logger_level('CASE'), 1, msg, *args, **kwargs)
+
+    def flush_log(self):
+        """This function exists for compatibility with Tako's logserial module.
+
+        Note that flushing the log is handled automatically by python's logging
+        module.
+        """
+        pass
diff --git a/acts/framework/acts/controllers/buds_lib/test_actions/audio_utils.py b/acts/framework/acts/controllers/buds_lib/test_actions/audio_utils.py
index 42f8c46..e94fc52 100644
--- a/acts/framework/acts/controllers/buds_lib/test_actions/audio_utils.py
+++ b/acts/framework/acts/controllers/buds_lib/test_actions/audio_utils.py
@@ -19,8 +19,8 @@
 import datetime
 import time
 
-from acts import tracelogger
 from acts import utils
+from acts.controllers.buds_lib import tako_trace_logger
 
 
 class AudioUtilsError(Exception):
@@ -36,7 +36,7 @@
     """
 
     def __init__(self):
-        self.logger = tracelogger.TakoTraceLogger()
+        self.logger = tako_trace_logger.TakoTraceLogger()
 
     def play_audio_into_device(self, audio_file_path, audio_player, dut):
         """Open mic on DUT, play audio into DUT, close mic on DUT.
diff --git a/acts/framework/acts/controllers/buds_lib/test_actions/base_test_actions.py b/acts/framework/acts/controllers/buds_lib/test_actions/base_test_actions.py
index 7b6cbc4..8f4b37a 100644
--- a/acts/framework/acts/controllers/buds_lib/test_actions/base_test_actions.py
+++ b/acts/framework/acts/controllers/buds_lib/test_actions/base_test_actions.py
@@ -22,7 +22,7 @@
 import inspect
 import time
 
-from acts import tracelogger
+from acts.controllers.buds_lib import tako_trace_logger
 from acts.libs.utils.timer import TimeRecorder
 
 # All methods start with "_" are considered hidden.
@@ -37,7 +37,7 @@
         func_name = self._convert_default_action_name(method.__name__)
         if not func_name:
             func_name = method.__name__
-        self.logger.step('%s...' % func_name)
+        self.log_step('%s...' % func_name)
         self.timer.start_timer(func_name, True)
         result = method(self, *args, **kw)
         # TODO: Method run time collected can be used for automatic KPI checks
@@ -135,9 +135,11 @@
 
     def __init__(self, logger=None):
         if logger is None:
-            self.logger = tracelogger.TakoTraceLogger()
+            self.logger = tako_trace_logger.TakoTraceLogger()
+            self.log_step = self.logger.step
         else:
             self.logger = logger
+            self.log_step = self.logger.info
         self.timer = TimeRecorder()
         self._fill_default_action_map()
 
@@ -172,15 +174,16 @@
                exceptions
         """
         num_acts = len(self._action_map)
-        self.logger.i('I can do %d action%s:' %
+
+        self.logger.info('I can do %d action%s:' %
                       (num_acts, 's' if num_acts != 1 else ''))
         for act in self._action_map.keys():
-            self.logger.i(' - %s' % act)
+            self.logger.info(' - %s' % act)
         return True
 
     @timed_action
     def sleep(self, seconds):
-        self.logger.i('%s seconds' % seconds)
+        self.logger.info('%s seconds' % seconds)
         time.sleep(seconds)
 
 
diff --git a/acts/framework/acts/controllers/buds_lib/test_actions/bt_utils.py b/acts/framework/acts/controllers/buds_lib/test_actions/bt_utils.py
index 2582402..f0ac041 100644
--- a/acts/framework/acts/controllers/buds_lib/test_actions/bt_utils.py
+++ b/acts/framework/acts/controllers/buds_lib/test_actions/bt_utils.py
@@ -31,12 +31,13 @@
 """
 import queue
 import time
-
-from acts import tracelogger
-from acts.utils import wait_until
-from acts.utils import TimeoutError
 from logging import Logger
 
+from acts import asserts
+from acts.controllers.buds_lib import tako_trace_logger
+from acts.utils import TimeoutError
+from acts.utils import wait_until
+
 # Add connection profile for future devices in this dictionary
 WEARABLE_BT_PROTOCOLS = {
     'rio': {
@@ -69,7 +70,7 @@
 
     def __init__(self):
         self.default_timeout = 60
-        self.logger = tracelogger.TakoTraceLogger(Logger(__file__))
+        self.logger = tako_trace_logger.TakoTraceLogger(Logger(__file__))
 
     def bt_pair_and_connect(self, pri_device, sec_device):
         """Pair and connect a pri_device to a sec_device.
@@ -198,8 +199,9 @@
             return True, 0
         self.logger.debug('Unpairing from %s' % target_address)
         start_time = end_time = time.time()
-        assert (True is pri_device.droid.bluetoothUnbond(target_address),
-                'Failed to request device unpairing.')
+        asserts.assert_true(
+            pri_device.droid.bluetoothUnbond(target_address),
+            'Failed to request device unpairing.')
 
         # Check that devices have unpaired successfully.
         self.logger.debug('Verifying devices are unpaired')
@@ -290,4 +292,3 @@
             if expected[key] != actual[key]:
                 return False
         return True
-
diff --git a/acts/framework/acts/controllers/iperf_server.py b/acts/framework/acts/controllers/iperf_server.py
index e51f815..923bcb2 100755
--- a/acts/framework/acts/controllers/iperf_server.py
+++ b/acts/framework/acts/controllers/iperf_server.py
@@ -17,7 +17,6 @@
 import json
 import logging
 import math
-import IPy
 import os
 import shlex
 import subprocess
@@ -243,8 +242,8 @@
         """
         if not self._has_data():
             return None
-        instantaneous_rates = self.instantaneous_rates[
-            iperf_ignored_interval:-1]
+        instantaneous_rates = self.instantaneous_rates[iperf_ignored_interval:
+                                                       -1]
         avg_rate = math.fsum(instantaneous_rates) / len(instantaneous_rates)
         sqd_deviations = ([(rate - avg_rate)**2
                            for rate in instantaneous_rates])
@@ -295,25 +294,6 @@
         """
         raise NotImplementedError('stop() must be specified.')
 
-    def get_interface_ip_addresses(self, interface):
-        """Gets all of the ip addresses, ipv4 and ipv6, associated with a
-           particular interface name.
-
-        Args:
-            interface: The interface name on the device, ie eth0
-
-        Returns:
-            A list of dictionaries of the the various IP addresses:
-                ipv4_private_local_addresses: Any 192.168, 172.16, or 10
-                    addresses
-                ipv4_public_addresses: Any IPv4 public addresses
-                ipv6_link_local_addresses: Any fe80:: addresses
-                ipv6_private_local_addresses: Any fd00:: addresses
-                ipv6_public_addresses: Any publicly routable addresses
-        """
-        raise NotImplementedError('get_interface_ip_addresses'
-                                  ' must be specified.')
-
     def _get_full_file_path(self, tag=None):
         """Returns the full file path for the IPerfServer log file.
 
@@ -432,24 +412,6 @@
 
         return self._current_log_file
 
-    def get_interface_ip_addresses(self, interface):
-        """Gets all of the ip addresses, ipv4 and ipv6, associated with a
-           particular interface name.
-
-        Args:
-            interface: The interface name on the device, ie eth0
-
-        Returns:
-            A list of dictionaries of the the various IP addresses:
-                ipv4_private_local_addresses: Any 192.168, 172.16, or 10
-                    addresses
-                ipv4_public_addresses: Any IPv4 public addresses
-                ipv6_link_local_addresses: Any fe80:: addresses
-                ipv6_private_local_addresses: Any fd00:: addresses
-                ipv6_public_addresses: Any publicly routable addresses
-        """
-        return utils.get_interface_ip_addresses(job, interface)
-
     def __del__(self):
         self.stop()
 
@@ -506,15 +468,15 @@
         Args:
             interface: The interface name on the device, ie eth0
 
-        Returns:
-            A list of dictionaries of the the various IP addresses:
-                ipv4_private_local_addresses: Any 192.168, 172.16, or 10
-                    addresses
-                ipv4_public_addresses: Any IPv4 public addresses
-                ipv6_link_local_addresses: Any fe80:: addresses
-                ipv6_private_local_addresses: Any fd00:: addresses
-                ipv6_public_addresses: Any publicly routable addresses
-        """
+    Returns:
+        A list of dictionaries of the the various IP addresses:
+            ipv4_private_local_addresses: Any 192.168, 172.16, or 10
+                addresses
+            ipv4_public_addresses: Any IPv4 public addresses
+            ipv6_link_local_addresses: Any fe80:: addresses
+            ipv6_private_local_addresses: Any fd00:: addresses
+            ipv6_public_addresses: Any publicly routable addresses
+    """
         return utils.get_interface_ip_addresses(self._ssh_session, interface)
 
     def renew_test_interface_ip_address(self):
@@ -716,22 +678,3 @@
 
         self._iperf_process = None
         return log_file
-
-    def get_interface_ip_addresses(self, interface):
-        """Gets all of the ip addresses, ipv4 and ipv6, associated with a
-           particular interface name.
-
-        Args:
-            interface: The interface name on the device, ie eth0
-
-        Returns:
-            A list of dictionaries of the the various IP addresses:
-                ipv4_private_local_addresses: Any 192.168, 172.16, or 10
-                    addresses
-                ipv4_public_addresses: Any IPv4 public addresses
-                ipv6_link_local_addresses: Any fe80:: addresses
-                ipv6_private_local_addresses: Any fd00:: addresses
-                ipv6_public_addresses: Any publicly routable addresses
-        """
-        return utils.get_interface_ip_addresses(self._android_device_or_serial,
-                                                interface)
diff --git a/acts/framework/acts/controllers/monsoon_lib/api/monsoon.py b/acts/framework/acts/controllers/monsoon_lib/api/monsoon.py
index a55fc8a..6a7e4af 100644
--- a/acts/framework/acts/controllers/monsoon_lib/api/monsoon.py
+++ b/acts/framework/acts/controllers/monsoon_lib/api/monsoon.py
@@ -152,6 +152,8 @@
                                    'seconds.' % time_limit_seconds)
             self._set_usb_passthrough_mode(expected_state)
             time.sleep(1)
+        self._log.info('Monsoon usbPassthroughMode is now "%s"',
+                       state)
 
         if expected_state in [PassthroughStates.ON]:
             self._on_reconnect()
@@ -261,10 +263,10 @@
             TimeoutError upon failure to reconnect over USB.
         """
         self._log.info('Reconnecting dut.')
-        # Wait for one second to ensure that the relay is ready, then
+        # Wait for two seconds to ensure that the device is ready, then
         # attempt to reconnect. If reconnect times out, reset the passthrough
         # state and try again.
-        time.sleep(1)
+        time.sleep(2)
         try:
             self.on_reconnect()
         except TimeoutError as err:
diff --git a/acts/framework/acts/controllers/spectracom_lib/__init__.py b/acts/framework/acts/controllers/spectracom_lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/acts/controllers/spectracom_lib/__init__.py
diff --git a/acts/framework/acts/controllers/spectracom_lib/gsg6.py b/acts/framework/acts/controllers/spectracom_lib/gsg6.py
new file mode 100644
index 0000000..6b96456
--- /dev/null
+++ b/acts/framework/acts/controllers/spectracom_lib/gsg6.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python3
+#
+#   Copyright 2019 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#           http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+"""Python module for Spectracom/Orolia GSG-6 GNSS simulator."""
+
+from acts.controllers import abstract_inst
+
+
+class GSG6Error(abstract_inst.SocketInstrumentError):
+    """GSG-6 Instrument Error Class."""
+
+
+class GSG6(abstract_inst.SocketInstrument):
+    """GSG-6 Class, inherted from abstract_inst SocketInstrument."""
+
+    def __init__(self, ip_addr, ip_port):
+        """Init method for GSG-6.
+
+        Args:
+            ip_addr: IP Address.
+                Type, str.
+            ip_port: TCPIP Port.
+                Type, str.
+        """
+        super(GSG6, self).__init__(ip_addr, ip_port)
+
+        self.idn = ''
+
+    def connect(self):
+        """Init and Connect to GSG-6."""
+        self._connect_socket()
+
+        self.get_idn()
+
+        infmsg = 'Connected to GSG-6, with ID: {}'.format(self.idn)
+        self._logger.debug(infmsg)
+
+    def close(self):
+        """Close GSG-6."""
+        self._close_socket()
+
+        self._logger.debug('Closed connection to GSG-6')
+
+    def get_idn(self):
+        """Get the Idenification of GSG-6.
+
+        Returns:
+            GSG-6 Identifier
+        """
+        self.idn = self._query('*IDN?')
+
+        return self.idn
+
+    def start_scenario(self, scenario=''):
+        """Start to run scenario.
+
+        Args:
+            scenario: Scenario to run.
+                Type, str.
+                Default, '', which will run current selected one.
+        """
+        if scenario:
+            cmd = 'SOUR:SCEN:LOAD ' + scenario
+            self._send(cmd)
+
+        self._send('SOUR:SCEN:CONT START')
+
+        if scenario:
+            infmsg = 'Started running scenario {}'.format(scenario)
+        else:
+            infmsg = 'Started running current scenario'
+
+        self._logger.debug(infmsg)
+
+    def stop_scenario(self):
+        """Stop the running scenario."""
+
+        self._send('SOUR:SCEN:CONT STOP')
+
+        self._logger.debug('Stopped running scenario')
+
+    def preset(self):
+        """Preset GSG-6 to default status."""
+        self._send('*RST')
+
+        self._logger.debug('Reset GSG-6')
+
+    def set_power(self, power_level):
+        """set GSG-6 transmit power on all bands.
+
+        Args:
+            power_level: transmit power level
+                Type, float.
+                Decimal, unit [dBm]
+
+        Raises:
+            GSG6Error: raise when power level is not in [-160, -65] range.
+        """
+        if not -160 <= power_level <= -65:
+            errmsg = ('"power_level" must be within [-160, -65], '
+                      'current input is {}').format(str(power_level))
+            raise GSG6Error(error=errmsg, command='set_power')
+
+        self._send(':SOUR:POW ' + str(round(power_level, 1)))
+
+        infmsg = 'Set GSG-6 transmit power to "{}"'.format(
+            round(power_level, 1))
+        self._logger.debug(infmsg)
diff --git a/acts/framework/acts/controllers/spirent_lib/__init__.py b/acts/framework/acts/controllers/spirent_lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/acts/controllers/spirent_lib/__init__.py
diff --git a/acts/framework/acts/controllers/spirent_lib/gss6450.py b/acts/framework/acts/controllers/spirent_lib/gss6450.py
new file mode 100644
index 0000000..aa84575
--- /dev/null
+++ b/acts/framework/acts/controllers/spirent_lib/gss6450.py
@@ -0,0 +1,381 @@
+#!/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 Spirent GSS6450 GNSS RPS."""
+
+import datetime
+import numbers
+from acts.controllers import abstract_inst
+
+
+class GSS6450Error(abstract_inst.SocketInstrumentError):
+    """GSS6450 Instrument Error Class."""
+
+
+class GSS6450(abstract_inst.RequestInstrument):
+    """GSS6450 Class, inherted from abstract_inst RequestInstrument."""
+
+    def __init__(self, ip_addr):
+        """Init method for GSS6450.
+
+        Args:
+            ip_addr: IP Address.
+                Type, str.
+        """
+        super(GSS6450, self).__init__(ip_addr)
+
+        self.idn = 'Spirent-GSS6450'
+
+    def _put(self, cmd):
+        """Send put command via GSS6450 HTTP Request and get response.
+
+        Args:
+            cmd: parameters listed in SHM_PUT.
+                Type, Str.
+
+        Returns:
+            resp: Response from the _query method.
+                Type, Str.
+        """
+        put_cmd = 'shm_put.shtml?' + cmd
+        resp = self._query(put_cmd)
+
+        return resp
+
+    def _get(self, cmd):
+        """Send get command via GSS6450 HTTP Request and get response.
+
+        Args:
+            cmd: parameters listed in SHM_GET.
+                Type, Str.
+
+        Returns:
+          resp: Response from the _query method.
+              Type, Str.
+        """
+        get_cmd = 'shm_get.shtml?' + cmd
+        resp = self._query(get_cmd)
+
+        return resp
+
+    def get_scenario_filename(self):
+        """Get the scenario filename of GSS6450.
+
+        Returns:
+            filename: RPS Scenario file name.
+                Type, Str.
+        """
+        resp_raw = self._get('-f')
+        filename = resp_raw.split(':')[-1].strip(' ')
+        self._logger.debug('Got scenario file name: "%s".', filename)
+
+        return filename
+
+    def get_scenario_description(self):
+        """Get the scenario description of GSS6450.
+
+        Returns:
+            description: RPS Scenario description.
+                Type, Str.
+        """
+        resp_raw = self._get('-d')
+        description = resp_raw.split('-d')[-1].strip(' ')
+
+        if description:
+            self._logger.debug('Got scenario description: "%s".', description)
+        else:
+            self._logger.warning('Got scenario description with empty string.')
+
+        return description
+
+    def get_scenario_location(self):
+        """Get the scenario location of GSS6450.
+
+        Returns:
+            location: RPS Scenario location.
+                Type, Str.
+        """
+        resp_raw = self._get('-i')
+        location = resp_raw.split('-i')[-1].strip(' ')
+
+        if location:
+            self._logger.debug('Got scenario location: "%s".', location)
+        else:
+            self._logger.warning('Got scenario location with empty string.')
+
+        return location
+
+    def get_operation_mode(self):
+        """Get the operation mode of GSS6450.
+
+        Returns:
+            mode: RPS Operation Mode.
+                Type, Str.
+                Option, STOPPED/PLAYING/RECORDING
+        """
+        resp_raw = self._get('-m')
+        mode = resp_raw.split('-m')[-1].strip(' ')
+        self._logger.debug('Got operation mode: "%s".', mode)
+
+        return mode
+
+    def get_battery_level(self):
+        """Get the battery level of GSS6450.
+
+        Returns:
+            batterylevel: RPS Battery Level.
+                Type, float.
+        """
+        resp_raw = self._get('-l')
+        batterylevel = float(resp_raw.split('-l')[-1].strip(' '))
+        self._logger.debug('Got battery level: %s%%.', batterylevel)
+
+        return batterylevel
+
+    def get_rfport_voltage(self):
+        """Get the RF port voltage of GSS6450.
+
+        Returns:
+            voltageout: RPS RF port voltage.
+                Type, str
+        """
+        resp_raw = self._get('-v')
+        voltageout = resp_raw.split('-v')[-1].strip(' ')
+        self._logger.debug('Got RF port voltage: "%s".', voltageout)
+
+        return voltageout
+
+    def get_storage_media(self):
+        """Get the storage media of GSS6450.
+
+        Returns:
+            media: RPS storage.
+                Type, str
+
+        Raises:
+            GSS6450Error: raise when request response is not support.
+        """
+        resp_raw = self._get('-M')
+        resp_num = resp_raw.split('-M')[-1].strip(' ')
+
+        if resp_num == '1':
+            media = '1-INTERNAL'
+        elif resp_num == '2':
+            media = '2-REMOVABLE'
+        else:
+            errmsg = ('"{}" is not recognized as GSS6450 valid storage media'
+                      ' type'.format(resp_num))
+            raise GSS6450Error(error=errmsg, command='get_storage_media')
+
+        self._logger.debug('Got current storage media: %s.', media)
+
+        return media
+
+    def get_attenuation(self):
+        """Get the attenuation of GSS6450.
+
+        Returns:
+            attenuation: RPS attenuation level, in dB.
+                Type, list of float.
+        """
+        resp_raw = self._get('-a')
+        resp_str = resp_raw.split('-a')[-1].strip(' ')
+        self._logger.debug('Got attenuation: %s dB.', resp_str)
+        attenuation = [float(itm) for itm in resp_str.split(',')]
+
+        return attenuation
+
+    def get_elapsed_time(self):
+        """Get the running scenario elapsed time of GSS6450.
+
+        Returns:
+            etime: RPS elapsed time.
+                Type, datetime.timedelta.
+        """
+        resp_raw = self._get('-e')
+        resp_str = resp_raw.split('-e')[-1].strip(' ')
+        self._logger.debug('Got senario elapsed time: "%s".', resp_str)
+        etime_tmp = datetime.datetime.strptime(resp_str, '%H:%M:%S')
+        etime = datetime.timedelta(hours=etime_tmp.hour,
+                                   minutes=etime_tmp.minute,
+                                   seconds=etime_tmp.second)
+
+        return etime
+
+    def get_playback_offset(self):
+        """Get the running scenario playback offset of GSS6450.
+
+        Returns:
+            offset: RPS playback offset.
+                Type, datetime.timedelta.
+        """
+        resp_raw = self._get('-o')
+        offset_tmp = float(resp_raw.split('-o')[-1].strip(' '))
+        self._logger.debug('Got senario playback offset: %s sec.', offset_tmp)
+        offset = datetime.timedelta(seconds=offset_tmp)
+
+        return offset
+
+    def play_scenario(self, scenario=''):
+        """Start to play scenario in GSS6450.
+
+        Args:
+            scenario: Scenario to play.
+                Type, str.
+                Default, '', which will run current selected one.
+        """
+        if scenario:
+            cmd = '-f{},-wP'.format(scenario)
+        else:
+            cmd = '-wP'
+
+        _ = self._put(cmd)
+
+        if scenario:
+            infmsg = 'Started playing scenario: "{}".'.format(scenario)
+        else:
+            infmsg = 'Started playing current scenario.'
+
+        self._logger.debug(infmsg)
+
+    def record_scenario(self, scenario=''):
+        """Start to record scenario in GSS6450.
+
+        Args:
+            scenario: Scenario to record.
+                Type, str.
+                Default, '', which will run current selected one.
+        """
+        if scenario:
+            cmd = '-f{},-wR'.format(scenario)
+        else:
+            cmd = '-wR'
+
+        _ = self._put(cmd)
+
+        if scenario:
+            infmsg = 'Started recording scenario: "{}".'.format(scenario)
+        else:
+            infmsg = 'Started recording scenario.'
+
+        self._logger.debug(infmsg)
+
+    def stop_scenario(self):
+        """Start to stop playing/recording scenario in GSS6450."""
+        _ = self._put('-wS')
+
+        self._logger.debug('Stopped playing/recording scanrio.')
+
+    def set_rfport_voltage(self, voltageout):
+        """Set the RF port voltage of GSS6450.
+
+        Args:
+            voltageout: RPS RF port voltage.
+                Type, str
+
+        Raises:
+            GSS6450Error: raise when voltageout input is not valid.
+        """
+        if voltageout == 'OFF':
+            voltage_cmd = '0'
+        elif voltageout == '3.3V':
+            voltage_cmd = '3'
+        elif voltageout == '5V':
+            voltage_cmd = '5'
+        else:
+            errmsg = ('"{}" is not recognized as GSS6450 valid RF port voltage'
+                      ' type'.format(voltageout))
+            raise GSS6450Error(error=errmsg, command='set_rfport_voltage')
+
+        _ = self._put('-v{},-wV'.format(voltage_cmd))
+        self._logger.debug('Set RF port voltage: "%s".', voltageout)
+
+    def set_attenuation(self, attenuation):
+        """Set the attenuation of GSS6450.
+
+        Args:
+            attenuation: RPS attenuation level, in dB.
+                Type, numerical.
+
+        Raises:
+            GSS6450Error: raise when attenuation is not in range.
+        """
+        if not 0 <= attenuation <= 31:
+            errmsg = ('"attenuation" must be within [0, 31], '
+                      'current input is {}').format(str(attenuation))
+            raise GSS6450Error(error=errmsg, command='set_attenuation')
+
+        attenuation_raw = round(attenuation)
+
+        if attenuation_raw != attenuation:
+            warningmsg = ('"attenuation" must be integer, current input '
+                          'will be rounded to {}'.format(attenuation_raw))
+            self._logger.warning(warningmsg)
+
+        _ = self._put('-a{},-wA'.format(attenuation_raw))
+
+        self._logger.debug('Set attenuation: %s dB.', attenuation_raw)
+
+    def set_playback_offset(self, offset):
+        """Set the playback offset of GSS6450.
+
+        Args:
+            offset: RPS playback offset.
+                Type, datetime.timedelta, or numerical.
+
+        Raises:
+            GSS6450Error: raise when offset is not numeric or timedelta.
+        """
+        if isinstance(offset, datetime.timedelta):
+            offset_raw = offset.total_seconds()
+        elif isinstance(offset, numbers.Number):
+            offset_raw = offset
+        else:
+            raise GSS6450Error(error=('"offset" must be numerical value or '
+                                      'datetime.timedelta'),
+                               command='set_playback_offset')
+
+        _ = self._put('-o{}'.format(offset_raw))
+
+        self._logger.debug('Set playback offset: %s sec.', offset_raw)
+
+    def set_storage_media(self, media):
+        """Set the storage media of GSS6450.
+
+        Args:
+            media: RPS storage Media, Internal or External.
+                Type, str. Option, 'internal', 'removable'
+
+        Raises:
+            GSS6450Error: raise when media option is not support.
+        """
+        if media == 'internal':
+            raw_media = '1'
+        elif media == 'removable':
+            raw_media = '2'
+        else:
+            raise GSS6450Error(
+                error=('"media" input must be in ["internal", "removable"]. '
+                       ' Current input is {}'.format(media)),
+                command='set_storage_media')
+
+        _ = self._put('-M{}-wM'.format(raw_media))
+
+        resp_raw = self.get_storage_media()
+        if raw_media != resp_raw[0]:
+            raise GSS6450Error(
+                error=('Setting media "{}" is not the same as queried media '
+                       '"{}".'.format(media, resp_raw)),
+                command='set_storage_media')
diff --git a/acts/framework/acts/test_utils/instrumentation/device/apps/dismiss_dialogs.py b/acts/framework/acts/test_utils/instrumentation/device/apps/dismiss_dialogs.py
new file mode 100644
index 0000000..909326a
--- /dev/null
+++ b/acts/framework/acts/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.test_utils.instrumentation.device.apps.app_installer import \
+    AppInstaller
+from acts.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/framework/acts/test_utils/instrumentation/device/apps/hotword_model_extractor.py b/acts/framework/acts/test_utils/instrumentation/device/apps/hotword_model_extractor.py
new file mode 100644
index 0000000..57386b9
--- /dev/null
+++ b/acts/framework/acts/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.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/framework/acts/test_utils/instrumentation/device/command/adb_command_types.py b/acts/framework/acts/test_utils/instrumentation/device/command/adb_command_types.py
index 5c55734..072e1ae 100644
--- a/acts/framework/acts/test_utils/instrumentation/device/command/adb_command_types.py
+++ b/acts/framework/acts/test_utils/instrumentation/device/command/adb_command_types.py
@@ -14,6 +14,9 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+from acts.test_utils.instrumentation.device.command.intent_builder import \
+    IntentBuilder
+
 
 class DeviceState(object):
     """Class for adb commands for setting device properties to a value."""
@@ -82,6 +85,31 @@
                          on_val, off_val)
 
 
+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'):
+        """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
+        """
+        super().__init__(None, on_val, off_val)
+        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 self._intent_builder.build()
+
+
 class DeviceBinaryCommandSeries(object):
     """Class for toggling multiple settings at once."""
 
diff --git a/acts/framework/acts/test_utils/instrumentation/device/command/adb_commands/common.py b/acts/framework/acts/test_utils/instrumentation/device/command/adb_commands/common.py
index 0658419..bf853dc 100644
--- a/acts/framework/acts/test_utils/instrumentation/device/command/adb_commands/common.py
+++ b/acts/framework/acts/test_utils/instrumentation/device/command/adb_commands/common.py
@@ -72,8 +72,7 @@
 
 # Screen
 
-screen_adaptive_brightness = DeviceSetting(
-    SYSTEM, 'screen_brightness_mode')
+screen_adaptive_brightness = DeviceSetting(SYSTEM, 'screen_brightness_mode')
 
 screen_brightness = DeviceSetting(SYSTEM, 'screen_brightness')
 
@@ -83,6 +82,8 @@
 
 doze_mode = DeviceSetting(SECURE, 'doze_enabled')
 
+doze_always_on = DeviceSetting(SECURE, 'doze_always_on')
+
 wake_gesture = DeviceSetting(SECURE, 'wake_gesture_enabled')
 
 screensaver = DeviceSetting(SECURE, 'screensaver_enabled')
@@ -124,6 +125,16 @@
 disable_doze = 'dumpsys deviceidle disable'
 
 
+# Sensors
+
+disable_sensors = 'dumpsys sensorservice restrict blah'
+
+MOISTURE_DETECTION_SETTING_FILE = '/sys/class/power_supply/usb/moisture_detection_enabled'
+disable_moisture_detection = 'echo 0 > %s' % MOISTURE_DETECTION_SETTING_FILE
+
+## Ambient EQ: https://support.google.com/googlenest/answer/9137130?hl=en
+ambient_eq = DeviceSetting(SECURE, 'display_white_balance_enabled')
+
 # Miscellaneous
 
 test_harness = DeviceBinaryCommandSeries(
diff --git a/acts/framework/acts/test_utils/instrumentation/device/command/adb_commands/goog.py b/acts/framework/acts/test_utils/instrumentation/device/command/adb_commands/goog.py
new file mode 100644
index 0000000..48f6bf1
--- /dev/null
+++ b/acts/framework/acts/test_utils/instrumentation/device/command/adb_commands/goog.py
@@ -0,0 +1,77 @@
+#!/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.test_utils.instrumentation.device.command.adb_command_types \
+    import DeviceBinaryCommandSeries
+from acts.test_utils.instrumentation.device.command.adb_command_types \
+    import DeviceGServices
+from acts.test_utils.instrumentation.device.command.adb_command_types \
+    import DeviceState
+
+"""Google-internal device settings for power testing."""
+
+# TODO: add descriptions to each setting
+
+# Location
+
+location_collection = DeviceGServices(
+    'location:collection_enabled', on_val='1', off_val='0')
+
+location_opt_in = DeviceBinaryCommandSeries(
+    [
+        DeviceState('content insert --uri content://com.google.settings/'
+                    'partner --bind name:s:use_location_for_services '
+                    '--bind value:s:%s'),
+        DeviceState('content insert --uri content://com.google.settings/'
+                    'partner --bind name:s:network_location_opt_in '
+                    '--bind value:s:%s')
+    ]
+)
+
+# Cast
+
+cast_broadcast = DeviceGServices('gms:cast:mdns_device_scanner:is_enabled')
+
+
+# Apps
+
+disable_playstore = 'pm disable-user com.android.vending'
+
+
+# Volta
+
+disable_volta = 'pm disable-user com.google.android.volta'
+
+
+# CHRE
+
+disable_chre = 'setprop ctl.stop vendor.chre'
+
+
+# MusicIQ
+
+disable_musiciq = 'pm disable-user com.google.intelligence.sense'
+
+
+# Hotword
+
+disable_hotword = (
+    'am start -a com.android.intent.action.MANAGE_VOICE_KEYPHRASES '
+    '--ei com.android.intent.extra.VOICE_KEYPHRASE_ACTION 2 '
+    '--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')
diff --git a/acts/framework/acts/test_utils/instrumentation/power/__init__.py b/acts/framework/acts/test_utils/instrumentation/power/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/acts/test_utils/instrumentation/power/__init__.py
diff --git a/acts/framework/acts/test_utils/instrumentation/power/instrumentation_power_test.py b/acts/framework/acts/test_utils/instrumentation/power/instrumentation_power_test.py
new file mode 100644
index 0000000..03b65f0
--- /dev/null
+++ b/acts/framework/acts/test_utils/instrumentation/power/instrumentation_power_test.py
@@ -0,0 +1,475 @@
+#!/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 tzlocal
+from acts.controllers.android_device import SL4A_APK_NAME
+from acts.metrics.loggers.blackbox import BlackboxMappedMetricLogger
+from acts.test_utils.instrumentation import instrumentation_proto_parser \
+    as proto_parser
+from acts.test_utils.instrumentation.device.apps.app_installer import \
+    AppInstaller
+from acts.test_utils.instrumentation.device.command.adb_command_types import \
+    DeviceGServices
+from acts.test_utils.instrumentation.device.command.adb_command_types import \
+    DeviceSetprop
+from acts.test_utils.instrumentation.device.command.adb_command_types import \
+    DeviceSetting
+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.device.command.instrumentation_command_builder \
+    import DEFAULT_NOHUP_LOG
+from acts.test_utils.instrumentation.device.command.instrumentation_command_builder \
+    import InstrumentationTestCommandBuilder
+from acts.test_utils.instrumentation.instrumentation_base_test \
+    import InstrumentationBaseTest
+from acts.test_utils.instrumentation.instrumentation_base_test \
+    import InstrumentationTestError
+from acts.test_utils.instrumentation.instrumentation_proto_parser import \
+    DEFAULT_INST_LOG_DIR
+from acts.test_utils.instrumentation.power.power_metrics import Measurement
+from acts.test_utils.instrumentation.power.power_metrics import PowerMetrics
+from acts.test_utils.instrumentation.device.apps.permissions import PermissionsUtil
+
+from acts import asserts
+from acts import context
+
+ACCEPTANCE_THRESHOLD = 'acceptance_threshold'
+AUTOTESTER_LOG = 'autotester.log'
+DEFAULT_PUSH_FILE_TIMEOUT = 180
+DISCONNECT_USB_FILE = 'disconnectusb.log'
+POLLING_INTERVAL = 0.5
+
+
+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.metric_logger = BlackboxMappedMetricLogger.for_test_case()
+        self._test_apk = None
+        self._sl4a_apk = None
+        self._instr_cmd_builder = None
+        self._power_metrics = None
+
+    def setup_class(self):
+        super().setup_class()
+        self.monsoon = self.monsoons[0]
+        self._setup_monsoon()
+        self._instr_cmd_builder = self.power_instrumentation_command_builder()
+
+    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._instrumentation_config.get_file('permissions_apk'))
+        self._permissions_util.grant_all()
+        self.install_test_apk()
+
+    def _cleanup_device(self):
+        """Clean up device after power testing."""
+        if self._test_apk:
+            self._test_apk.uninstall()
+        self._permissions_util.close()
+        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()
+
+        # Test harness flag
+        self.adb_run(common.test_harness.toggle(True))
+
+        # Calling
+        self.adb_run(common.disable_dialing.toggle(True))
+
+        # Screen
+        self.adb_run(common.screen_always_on.toggle(True))
+        self.adb_run(common.screen_adaptive_brightness.toggle(False))
+
+        brightness_level = None
+        if 'brightness_level' in self._instrumentation_config:
+            brightness_level = self._instrumentation_config['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))
+        self.adb_run(common.screen_timeout_ms.set_value(1800000))
+        self.adb_run(common.notification_led.toggle(False))
+        self.adb_run(common.screensaver.toggle(False))
+        self.adb_run(common.wake_gesture.toggle(False))
+        self.adb_run(common.doze_mode.toggle(False))
+        self.adb_run(common.doze_always_on.toggle(False))
+
+        # Sensors
+        self.adb_run(common.auto_rotate.toggle(False))
+        self.adb_run(common.disable_sensors)
+        self.adb_run(common.ambient_eq.toggle(False))
+
+        if self.file_exists(common.MOISTURE_DETECTION_SETTING_FILE):
+            self.adb_run(common.disable_moisture_detection)
+
+        # Time
+        self.adb_run(common.auto_time.toggle(False))
+        self.adb_run(common.auto_timezone.toggle(False))
+        self.adb_run(common.timezone.set_value(str(tzlocal.get_localzone())))
+
+        # Location
+        self.adb_run(common.location_gps.toggle(False))
+        self.adb_run(common.location_network.toggle(False))
+
+        # Power
+        self.adb_run(common.battery_saver_mode.toggle(False))
+        self.adb_run(common.battery_saver_trigger.set_value(0))
+        self.adb_run(common.enable_full_batterystats_history)
+        self.adb_run(common.disable_doze)
+
+        # Camera
+        self.adb_run(DeviceSetprop(
+            'camera.optbar.hdr', 'true', 'false').toggle(True))
+
+        # Gestures
+        gestures = {
+            'doze_pulse_on_pick_up': False,
+            'doze_pulse_on_double_tap': False,
+            'camera_double_tap_power_gesture_disabled': True,
+            'camera_double_twist_to_flip_enabled': False,
+            'assist_gesture_enabled': False,
+            'assist_gesture_silence_alerts_enabled': False,
+            'assist_gesture_wake_enabled': False,
+            'system_navigation_keys_enabled': False,
+            'camera_lift_trigger_enabled': False,
+            'doze_always_on': False,
+            'aware_enabled': False,
+            'doze_wake_screen_gesture': False,
+            'skip_gesture': False,
+            'silence_gesture': False
+        }
+        self.adb_run(
+            [DeviceSetting(common.SECURE, k).toggle(v)
+             for k, v in gestures.items()])
+
+        # GServices
+        self.adb_run(goog.location_collection.toggle(False))
+        self.adb_run(goog.cast_broadcast.toggle(False))
+        self.adb_run(DeviceGServices(
+            'location:compact_log_enabled').toggle(True))
+        self.adb_run(DeviceGServices('gms:magictether:enable').toggle(False))
+        self.adb_run(DeviceGServices('ocr.cc_ocr_enabled').toggle(False))
+        self.adb_run(DeviceGServices(
+            'gms:phenotype:phenotype_flag:debug_bypass_phenotype').toggle(True))
+        self.adb_run(DeviceGServices(
+            'gms_icing_extension_download_enabled').toggle(False))
+
+        # Comms
+        self.adb_run(common.wifi.toggle(False))
+        self.adb_run(common.bluetooth.toggle(False))
+        self.adb_run(common.airplane_mode.toggle(True))
+
+        # Misc. Google features
+        self.adb_run(goog.disable_playstore)
+        self.adb_run(goog.disable_volta)
+        self.adb_run(goog.disable_chre)
+        self.adb_run(goog.disable_musiciq)
+        self.adb_run(goog.disable_hotword)
+
+        # Enable clock dump info
+        self.adb_run('echo 1 > /d/clk/debug_suspend')
+
+    def _setup_monsoon(self):
+        """Set up the Monsoon controller for this testclass/testcase."""
+        self.log.info('Setting up Monsoon %s' % self.monsoon.serial)
+        monsoon_config = self._get_merged_config('Monsoon')
+        self._monsoon_voltage = monsoon_config.get_numeric('voltage', 4.2)
+        self.monsoon.set_voltage_safe(self._monsoon_voltage)
+        if 'max_current' in monsoon_config:
+            self.monsoon.set_max_current(
+                monsoon_config.get_numeric('max_current'))
+
+        self.monsoon.usb('on')
+        self.monsoon.set_on_disconnect(self._on_disconnect)
+        self.monsoon.set_on_reconnect(self._on_reconnect)
+
+        self._disconnect_usb_timeout = monsoon_config.get_numeric(
+            'usb_disconnection_timeout', 240)
+
+        self._measurement_args = dict(
+            duration=monsoon_config.get_numeric('duration'),
+            hz=monsoon_config.get_numeric('frequency'),
+            measure_after_seconds=monsoon_config.get_numeric('delay')
+        )
+
+    def _on_disconnect(self):
+        """Callback invoked by device disconnection from the Monsoon."""
+        self.ad_dut.log.info('Disconnecting device.')
+        self.ad_dut.stop_services()
+        # Uninstall SL4A
+        self._sl4a_apk = AppInstaller.pull_from_device(
+            self.ad_dut, SL4A_APK_NAME, tempfile.mkdtemp(prefix='sl4a'))
+        self._sl4a_apk.uninstall()
+        time.sleep(1)
+
+    def _on_reconnect(self):
+        """Callback invoked by device reconnection to the Monsoon"""
+        # 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.
+        self.ad_dut.droid.goToSleepNow()
+        self.ad_dut.log.info('Device reconnected.')
+
+    def install_test_apk(self):
+        """Installs test apk on the device."""
+        test_apk_file = self._instrumentation_config.get_file('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 _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_NOHUP_LOG, AUTOTESTER_LOG]:
+            path = os.path.join(self.ad_dut.external_storage_path, file_name)
+            self.adb_run('rm -rf %s' % path)
+
+    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 file_exists(self, file_path):
+        cmd = '(test -f %s && echo yes) || echo no' % file_path
+        result = self.adb_run(cmd)
+        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 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
+
+    # Test runtime utils
+
+    def power_instrumentation_command_builder(self):
+        """Return the default command builder for power tests"""
+        builder = InstrumentationTestCommandBuilder.default()
+        builder.set_manifest_package(self._test_apk.pkg_name)
+        builder.set_nohup()
+        return builder
+
+    def _wait_for_disconnect_signal(self):
+        """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')
+        disconnect_file = os.path.join(
+            self.ad_dut.external_storage_path, DISCONNECT_USB_FILE)
+        start_time = time.time()
+        while time.time() < start_time + self._disconnect_usb_timeout:
+            if self.ad_dut.adb.shell('ls %s' % disconnect_file):
+                return
+            time.sleep(POLLING_INTERVAL)
+        raise InstrumentationTestError('Timeout while waiting for USB '
+                                       'disconnect signal.')
+
+    def measure_power(self):
+        """Measures power consumption with the Monsoon. See monsoon_lib API for
+        details.
+        """
+        if not hasattr(self, '_measurement_args'):
+            raise InstrumentationTestError('Missing Monsoon measurement args.')
+
+        # Start measurement after receiving disconnect signal
+        self._wait_for_disconnect_signal()
+        power_data_path = os.path.join(
+            context.get_current_context().get_full_output_path(), 'power_data')
+        self.log.info('Starting Monsoon measurement.')
+        self.monsoon.usb('auto')
+        measure_start_time = time.time()
+        result = self.monsoon.measure_power(
+            **self._measurement_args, output_path=power_data_path)
+        self.monsoon.usb('on')
+        self.log.info('Monsoon measurement complete.')
+
+        # Gather relevant metrics from measurements
+        session = self.dump_instrumentation_result_proto()
+        self._power_metrics = PowerMetrics(self._monsoon_voltage,
+                                           start_time=measure_start_time)
+        self._power_metrics.generate_test_metrics(
+            PowerMetrics.import_raw_data(power_data_path),
+            proto_parser.get_test_timestamps(session))
+        self._log_metrics()
+        return result
+
+    def run_and_measure(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 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.
+
+        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._get_merged_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.log.info('Running instrumentation call: %s' % instr_cmd)
+        self.adb_run_async(instr_cmd)
+        return self.measure_power()
+
+    def _log_metrics(self):
+        """Record the collected metrics with the metric logger."""
+        for metric_name in PowerMetrics.ALL_METRICS:
+            for instr_test_name in self._power_metrics.test_metrics:
+                metric_value = getattr(
+                    self._power_metrics.test_metrics[instr_test_name],
+                    metric_name).value
+                self.metric_logger.add_metric(
+                    '%s__%s' % (metric_name, instr_test_name), metric_value)
+
+    def validate_power_results(self, *instr_test_names):
+        """Compare power measurements with target values and set the test result
+        accordingly.
+
+        Args:
+            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._get_merged_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 = self._power_metrics.test_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)
+
+            summaries[instr_test_name] = {}
+            test_thresholds = all_thresholds.get_config(instr_test_name)
+            for metric_name, metric in test_thresholds.items():
+                try:
+                    actual_result = getattr(test_metrics, metric_name)
+                except AttributeError:
+                    continue
+
+                if 'unit_type' not in metric or 'unit' not in metric:
+                    continue
+                unit_type = metric['unit_type']
+                unit = metric['unit']
+
+                lower_value = metric.get_numeric('lower_limit', float('-inf'))
+                upper_value = metric.get_numeric('upper_limit', float('inf'))
+                if 'expected_value' in metric and 'percent_deviation' in metric:
+                    expected_value = metric.get_numeric('expected_value')
+                    percent_deviation = metric.get_numeric('percent_deviation')
+                    lower_value = expected_value * (1 - percent_deviation / 100)
+                    upper_value = expected_value * (1 + percent_deviation / 100)
+
+                lower_bound = Measurement(lower_value, unit_type, unit)
+                upper_bound = Measurement(upper_value, unit_type, unit)
+                summary_entry = {
+                    'expected': '[%s, %s]' % (lower_bound, upper_bound),
+                    'actual': str(actual_result.to_unit(unit))
+                }
+                summaries[instr_test_name][metric_name] = summary_entry
+                if not lower_bound <= actual_result <= upper_bound:
+                    failure = True
+        self.log.info('Summary of measurements: %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)
diff --git a/acts/framework/acts/test_utils/instrumentation/power/power_metrics.py b/acts/framework/acts/test_utils/instrumentation/power/power_metrics.py
new file mode 100644
index 0000000..b41ae61
--- /dev/null
+++ b/acts/framework/acts/test_utils/instrumentation/power/power_metrics.py
@@ -0,0 +1,298 @@
+#!/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 math
+
+from acts.test_utils.instrumentation import instrumentation_proto_parser \
+    as parser
+from acts.test_utils.instrumentation.instrumentation_base_test \
+    import InstrumentationTestError
+
+# Unit type constants
+CURRENT = 'current'
+POWER = 'power'
+TIME = 'time'
+
+# Unit constants
+MILLIAMP = 'mA'
+AMP = 'A'
+AMPERE = AMP
+MILLIWATT = 'mW'
+WATT = 'W'
+MILLISECOND = 'ms'
+SECOND = 's'
+MINUTE = 'm'
+HOUR = 'h'
+
+CONVERSION_TABLES = {
+    CURRENT: {
+        MILLIAMP: 0.001,
+        AMP: 1
+    },
+    POWER: {
+        MILLIWATT: 0.001,
+        WATT: 1
+    },
+    TIME: {
+        MILLISECOND: 0.001,
+        SECOND: 1,
+        MINUTE: 60,
+        HOUR: 3600
+    }
+}
+
+
+class Measurement(object):
+    """Base class for describing power measurement values. Each object contains
+    an value and a unit. Enables some basic arithmetic operations with other
+    measurements of the same unit type.
+
+    Attributes:
+        _value: Numeric value of the measurement
+        _unit_type: Unit type of the measurement (e.g. current, power)
+        _unit: Unit of the measurement (e.g. W, mA)
+    """
+
+    def __init__(self, value, unit_type, unit):
+        if unit_type not in CONVERSION_TABLES:
+            raise TypeError('%s is not a valid unit type' % unit_type)
+        self._value = value
+        self._unit_type = unit_type
+        self._unit = unit
+
+    # Convenience constructor methods
+    @staticmethod
+    def amps(amps):
+        """Create a new current measurement, in amps."""
+        return Measurement(amps, CURRENT, AMP)
+
+    @staticmethod
+    def watts(watts):
+        """Create a new power measurement, in watts."""
+        return Measurement(watts, POWER, WATT)
+
+    @staticmethod
+    def seconds(seconds):
+        """Create a new time measurement, in seconds."""
+        return Measurement(seconds, TIME, SECOND)
+
+    # Comparison methods
+
+    def __eq__(self, other):
+        return self.value == other.to_unit(self._unit).value
+
+    def __lt__(self, other):
+        return self.value < other.to_unit(self._unit).value
+
+    def __le__(self, other):
+        return self == other or self < other
+
+    # Addition and subtraction with other measurements
+
+    def __add__(self, other):
+        """Adds measurements of compatible unit types. The result will be in the
+        same units as self.
+        """
+        return Measurement(self.value + other.to_unit(self._unit).value,
+                           self._unit_type, self._unit)
+
+    def __sub__(self, other):
+        """Subtracts measurements of compatible unit types. The result will be
+        in the same units as self.
+        """
+        return Measurement(self.value - other.to_unit(self._unit).value,
+                           self._unit_type, self._unit)
+
+    # String representation
+
+    def __str__(self):
+        return '%g%s' % (self._value, self._unit)
+
+    def __repr__(self):
+        return str(self)
+
+    @property
+    def unit(self):
+        return self._unit
+
+    @property
+    def value(self):
+        return self._value
+
+    def to_unit(self, new_unit):
+        """Create an equivalent measurement under a different unit.
+        e.g. 0.5W -> 500mW
+
+        Args:
+            new_unit: Target unit. Must be compatible with current unit.
+
+        Returns: A new measurement with the converted value and unit.
+        """
+        try:
+            new_value = self._value * (
+                CONVERSION_TABLES[self._unit_type][self._unit] /
+                CONVERSION_TABLES[self._unit_type][new_unit])
+        except KeyError:
+            raise TypeError('Incompatible units: %s, %s' %
+                            (self._unit, new_unit))
+        return Measurement(new_value, self._unit_type, new_unit)
+
+
+class PowerMetrics(object):
+    """Class for processing raw power metrics generated by Monsoon measurements.
+    Provides useful metrics such as average current, max current, and average
+    power. Can generate individual test metrics.
+
+    See section "Numeric metrics" below for available metrics.
+    """
+
+    def __init__(self, voltage, start_time=0):
+        """Create a PowerMetrics.
+
+        Args:
+            voltage: Voltage of the measurement
+            start_time: Start time of the measurement. Used for generating
+                test-specific metrics.
+        """
+        self._voltage = voltage
+        self._start_time = start_time
+        self._num_samples = 0
+        self._sum_currents = 0
+        self._sum_squares = 0
+        self._max_current = None
+        self._min_current = None
+        self.test_metrics = {}
+
+    @staticmethod
+    def import_raw_data(path):
+        """Create a generator from a Monsoon data file.
+
+        Args:
+            path: path to raw data file
+
+        Returns: generator that yields (timestamp, sample) per line
+        """
+        with open(path, 'r') as f:
+            for line in f:
+                time, sample = line.split()
+                yield float(time[:-1]), float(sample)
+
+    def update_metrics(self, sample):
+        """Update the running metrics with the current sample.
+
+        Args:
+            sample: A current sample in Amps.
+        """
+        self._num_samples += 1
+        self._sum_currents += sample
+        self._sum_squares += sample ** 2
+        if self._max_current is None or sample > self._max_current:
+            self._max_current = sample
+        if self._min_current is None or sample < self._min_current:
+            self._min_current = sample
+
+    def generate_test_metrics(self, raw_data, test_timestamps=None):
+        """Split the data into individual test metrics, based on the timestamps
+        given as a dict.
+
+        Args:
+            raw_data: raw data as list or generator of (timestamp, sample)
+            test_timestamps: dict following the output format of
+                instrumentation_proto_parser.get_test_timestamps()
+        """
+
+        # Initialize metrics for each test
+        if test_timestamps is None:
+            test_timestamps = {}
+        test_starts = {}
+        test_ends = {}
+        for test_name, times in test_timestamps.items():
+            self.test_metrics[test_name] = PowerMetrics(
+                self._voltage, self._start_time)
+            try:
+                test_starts[test_name] = Measurement(
+                    times[parser.START_TIMESTAMP], TIME, MILLISECOND) \
+                                             .to_unit(SECOND).value - self._start_time
+            except KeyError:
+                raise InstrumentationTestError(
+                    'Missing start timestamp for test scenario "%s". Refer to '
+                    'instrumentation_proto.txt for details.' % test_name)
+            try:
+                test_ends[test_name] = Measurement(
+                    times[parser.END_TIMESTAMP], TIME, MILLISECOND) \
+                                           .to_unit(SECOND).value - self._start_time
+            except KeyError:
+                raise InstrumentationTestError(
+                    'Missing end timestamp for test scenario "%s". Test '
+                    'scenario may have terminated with errors. Refer to '
+                    'instrumentation_proto.txt for details.' % test_name)
+
+        # Assign data to tests based on timestamps
+        for timestamp, sample in raw_data:
+            self.update_metrics(sample)
+            for test_name in test_timestamps:
+                if test_starts[test_name] <= timestamp <= test_ends[test_name]:
+                    self.test_metrics[test_name].update_metrics(sample)
+
+    # Numeric metrics
+
+    ALL_METRICS = ('avg_current', 'max_current', 'min_current', 'stdev_current',
+                   'avg_power')
+
+    @property
+    def avg_current(self):
+        """Average current, in milliamps."""
+        if not self._num_samples:
+            return Measurement.amps(0).to_unit(MILLIAMP)
+        return (Measurement.amps(self._sum_currents / self._num_samples)
+                .to_unit(MILLIAMP))
+
+    @property
+    def max_current(self):
+        """Max current, in milliamps."""
+        return Measurement.amps(self._max_current or 0).to_unit(MILLIAMP)
+
+    @property
+    def min_current(self):
+        """Min current, in milliamps."""
+        return Measurement.amps(self._min_current or 0).to_unit(MILLIAMP)
+
+    @property
+    def stdev_current(self):
+        """Standard deviation of current values, in milliamps."""
+        if self._num_samples < 2:
+            return Measurement.amps(0).to_unit(MILLIAMP)
+        stdev = math.sqrt(
+            (self._sum_squares - (
+                self._num_samples * self.avg_current.to_unit(AMP).value ** 2))
+            / (self._num_samples - 1))
+        return Measurement.amps(stdev).to_unit(MILLIAMP)
+
+    def current_to_power(self, current):
+        """Converts a current value to a power value."""
+        return (Measurement.watts(current.to_unit(AMP).value * self._voltage))
+
+    @property
+    def avg_power(self):
+        """Average power, in milliwatts."""
+        return self.current_to_power(self.avg_current).to_unit(MILLIWATT)
+
+    @property
+    def summary(self):
+        """A summary of test metrics"""
+        return {'average_current': str(self.avg_current),
+                'max_current': str(self.max_current),
+                'average_power': str(self.avg_power)}
diff --git a/acts/framework/acts/test_utils/net/connectivity_const.py b/acts/framework/acts/test_utils/net/connectivity_const.py
index 37b8c80..c3d8d4a 100644
--- a/acts/framework/acts/test_utils/net/connectivity_const.py
+++ b/acts/framework/acts/test_utils/net/connectivity_const.py
@@ -66,6 +66,8 @@
 
 # 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"
diff --git a/acts/framework/acts/test_utils/net/connectivity_test_utils.py b/acts/framework/acts/test_utils/net/connectivity_test_utils.py
index 4b7668c..153630f 100644
--- a/acts/framework/acts/test_utils/net/connectivity_test_utils.py
+++ b/acts/framework/acts/test_utils/net/connectivity_test_utils.py
@@ -15,54 +15,95 @@
 
 from acts import asserts
 from acts.test_utils.net import connectivity_const as cconst
+from queue import Empty
 
-def start_natt_keepalive(ad, src_ip, src_port, dst_ip, interval = 10):
-    """ Start NAT-T keep alive on dut """
+def _listen_for_keepalive_event(ad, key, msg, ka_event):
+    """Listen for keepalive event and return status
 
-    ad.log.info("Starting NATT Keepalive")
-    status = None
-
-    key = ad.droid.connectivityStartNattKeepalive(
-        interval, src_ip, src_port, dst_ip)
-
-    ad.droid.connectivityNattKeepaliveStartListeningForEvent(key, "Started")
+    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("PacketKeepaliveCallback")
-        status = event["data"]["packetKeepaliveEvent"]
+        event = ad.ed.pop_event("SocketKeepaliveCallback")
+        status = event["data"]["socketKeepaliveEvent"] == ka_event
     except Empty:
-        msg = "Failed to receive confirmation of starting natt keepalive"
         asserts.fail(msg)
     finally:
-        ad.droid.connectivityNattKeepaliveStopListeningForEvent(
-            key, "Started")
-
-    if status != "Started":
-        ad.log.error("Received keepalive status: %s" % status)
-        ad.droid.connectivityRemovePacketKeepaliveReceiverKey(key)
-        return None
-    return key
-
-def stop_natt_keepalive(ad, key):
-    """ Stop NAT-T keep alive on dut """
-
-    ad.log.info("Stopping NATT keepalive")
-    status = False
-    ad.droid.connectivityStopNattKeepalive(key)
-
-    ad.droid.connectivityNattKeepaliveStartListeningForEvent(key, "Stopped")
-    try:
-        event = ad.ed.pop_event("PacketKeepaliveCallback")
-        status = event["data"]["packetKeepaliveEvent"] == "Stopped"
-    except Empty:
-        msg = "Failed to receive confirmation of stopping natt keepalive"
-        asserts.fail(msg)
-    finally:
-        ad.droid.connectivityNattKeepaliveStopListeningForEvent(
-            key, "Stopped")
-
-    ad.droid.connectivityRemovePacketKeepaliveReceiverKey(key)
+        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:
diff --git a/acts/framework/acts/test_utils/net/net_test_utils.py b/acts/framework/acts/test_utils/net/net_test_utils.py
index 6e62ad7..a0d3361 100644
--- a/acts/framework/acts/test_utils/net/net_test_utils.py
+++ b/acts/framework/acts/test_utils/net/net_test_utils.py
@@ -14,9 +14,11 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 import logging
+import os
 
 from acts.controllers import adb
 from acts import asserts
+from acts import signals
 from acts import utils
 from acts.controllers.adb import AdbError
 from acts.logger import epoch_to_log_line_timestamp
@@ -44,6 +46,9 @@
 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"""
@@ -290,6 +295,85 @@
     file_name = "tcpdump_%s_%s.pcap" % (ad.serial, test_name)
     return "%s/%s" % (log_path, file_name)
 
+def start_tcpdump_gce_server(ad, test_name, dest_port, gce):
+    """ Start tcpdump on gce server
+
+    Args:
+        ad: android device object
+        test_name: test case name
+        dest_port: port to collect tcpdump
+        gce: dictionary of gce instance
+
+    Returns:
+       process id and pcap file path from gce server
+    """
+    ad.log.info("Starting tcpdump on gce server")
+
+    # pcap file name
+    fname = "/tmp/%s_%s_%s_%s" % \
+        (test_name, ad.model, ad.serial,
+         time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime(time.time())))
+
+    # start tcpdump
+    tcpdump_cmd = "sudo bash -c \'tcpdump -i %s -w %s.pcap port %s > \
+        %s.txt 2>&1 & echo $!\'" % (gce["interface"], fname, dest_port, fname)
+    gcloud_ssh_cmd = "%s --project=%s --zone=%s %s@%s --command " % \
+        (GCE_SSH, gce["project"], gce["zone"], gce["username"], gce["hostname"])
+    gce_ssh_cmd = '%s "%s"' % (gcloud_ssh_cmd, tcpdump_cmd)
+    utils.exe_cmd(gce_ssh_cmd)
+
+    # get process id
+    ps_cmd = '%s "ps aux | grep tcpdump | grep %s"' % (gcloud_ssh_cmd, fname)
+    tcpdump_pid = utils.exe_cmd(ps_cmd).decode("utf-8", "ignore").split()
+    if not tcpdump_pid:
+        raise signals.TestFailure("Failed to start tcpdump on gce server")
+    return tcpdump_pid[1], fname
+
+def stop_tcpdump_gce_server(ad, tcpdump_pid, fname, gce):
+    """ Stop and pull tcpdump file from gce server
+
+    Args:
+        ad: android device object
+        tcpdump_pid: process id for tcpdump file
+        fname: tcpdump file path
+        gce: dictionary of gce instance
+
+    Returns:
+       pcap file from gce server
+    """
+    ad.log.info("Stop and pull pcap file from gce server")
+
+    # stop tcpdump
+    tcpdump_cmd = "sudo kill %s" % tcpdump_pid
+    gcloud_ssh_cmd = "%s --project=%s --zone=%s %s@%s --command " % \
+        (GCE_SSH, gce["project"], gce["zone"], gce["username"], gce["hostname"])
+    gce_ssh_cmd = '%s "%s"' % (gcloud_ssh_cmd, tcpdump_cmd)
+    utils.exe_cmd(gce_ssh_cmd)
+
+    # verify tcpdump is stopped
+    ps_cmd = '%s "ps aux | grep tcpdump"' % gcloud_ssh_cmd
+    res = utils.exe_cmd(ps_cmd).decode("utf-8", "ignore")
+    if tcpdump_pid in res.split():
+        raise signals.TestFailure("Failed to stop tcpdump on gce server")
+    if not fname:
+        return None
+
+    # pull pcap file
+    gcloud_scp_cmd = "%s --project=%s --zone=%s %s@%s:" % \
+        (GCE_SCP, gce["project"], gce["zone"], gce["username"], gce["hostname"])
+    pull_file = '%s%s.pcap %s/' % (gcloud_scp_cmd, fname, ad.device_log_path)
+    utils.exe_cmd(pull_file)
+    if not os.path.exists(
+        "%s/%s.pcap" % (ad.device_log_path, fname.split('/')[-1])):
+        raise signals.TestFailure("Failed to pull tcpdump from gce server")
+
+    # delete pcaps
+    utils.exe_cmd('%s "sudo rm %s.*"' % (gcloud_ssh_cmd, fname))
+
+    # return pcap file
+    pcap_file = "%s/%s.pcap" % (ad.device_log_path, fname.split('/')[-1])
+    return pcap_file
+
 def is_ipaddress_ipv6(ip_address):
     """Verify if the given string is a valid IPv6 address.
 
diff --git a/acts/framework/acts/test_utils/net/socket_test_utils.py b/acts/framework/acts/test_utils/net/socket_test_utils.py
index a8a05fc..42e4537 100644
--- a/acts/framework/acts/test_utils/net/socket_test_utils.py
+++ b/acts/framework/acts/test_utils/net/socket_test_utils.py
@@ -258,6 +258,18 @@
     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
diff --git a/acts/framework/acts/test_utils/wifi/WifiBaseTest.py b/acts/framework/acts/test_utils/wifi/WifiBaseTest.py
index 82d655f..122709f 100644
--- a/acts/framework/acts/test_utils/wifi/WifiBaseTest.py
+++ b/acts/framework/acts/test_utils/wifi/WifiBaseTest.py
@@ -316,11 +316,6 @@
             ent_network_pwd=False,
             radius_conf_pwd=None,
             ap_count=1):
-        asserts.assert_true(
-            len(self.user_params["AccessPoint"]) == 2,
-            "Exactly two access points must be specified. \
-             Each access point has 2 radios, one each for 2.4GHZ \
-             and 5GHz. A test can choose to use one or both APs.")
 
         config_count = 1
         count = 0
diff --git a/acts/framework/acts/test_utils/wifi/aware/aware_test_utils.py b/acts/framework/acts/test_utils/wifi/aware/aware_test_utils.py
index 2623f9b..e297799 100644
--- a/acts/framework/acts/test_utils/wifi/aware/aware_test_utils.py
+++ b/acts/framework/acts/test_utils/wifi/aware/aware_test_utils.py
@@ -468,6 +468,28 @@
     return True
 
 
+def run_ping6(dut, target_ip, duration=60):
+    """Run ping test and return the latency result
+
+    Args:
+        dut: the dut which run the ping cmd
+        target_ip: target IP Address for ping
+        duration: the duration time of the ping
+
+    return: dict contains "min/avg/max/mdev" result
+    """
+    cmd = "ping6 -w %d %s" % (duration, target_ip)
+    ping_result = dut.adb.shell(cmd, timeout=duration + 1)
+    res = re.match(".*mdev = (\S+) .*", ping_result, re.S)
+    asserts.assert_true(res, "Cannot reach the IP address %s", target_ip)
+    title = ["min", "avg", "max", "mdev"]
+    result = res.group(1).split("/")
+    latency_result = {}
+    for i in range(len(title)):
+        latency_result[title[i]] = result[i]
+    return latency_result
+
+
 #########################################################
 # Aware primitives
 #########################################################
diff --git a/acts/framework/acts/test_utils/wifi/p2p/WifiP2pBaseTest.py b/acts/framework/acts/test_utils/wifi/p2p/WifiP2pBaseTest.py
index 7aca3c6..448647e 100644
--- a/acts/framework/acts/test_utils/wifi/p2p/WifiP2pBaseTest.py
+++ b/acts/framework/acts/test_utils/wifi/p2p/WifiP2pBaseTest.py
@@ -24,6 +24,8 @@
 from acts.test_utils.wifi import wifi_test_utils as wutils
 from acts.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'):
@@ -105,5 +107,7 @@
 
     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/framework/acts/test_utils/wifi/wifi_constants.py b/acts/framework/acts/test_utils/wifi/wifi_constants.py
index 21f13d2..49f9725 100644
--- a/acts/framework/acts/test_utils/wifi/wifi_constants.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_constants.py
@@ -41,6 +41,9 @@
 # WifiManagerSoftApCallback-[callbackId]-OnNumClientsChanged
 SOFTAP_NUMBER_CLIENTS_CHANGED = "-OnNumClientsChanged"
 SOFTAP_NUMBER_CLIENTS_CALLBACK_KEY = "NumClients"
+SOFTAP_INFO_CHANGED = "-OnInfoChanged"
+SOFTAP_INFO_FREQUENCY_CALLBACK_KEY = "frequency"
+SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY = "bandwidth"
 SOFTAP_STATE_CHANGE_CALLBACK_KEY = "State"
 WIFI_AP_DISABLING_STATE = 10
 WIFI_AP_DISABLED_STATE = 11
diff --git a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
index 8f356ed..cb3153a 100755
--- a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
@@ -49,16 +49,16 @@
 
 DEFAULT_PING_ADDR = "https://www.google.com/robots.txt"
 
-roaming_attn = {
+ROAMING_ATTN = {
         "AP1_on_AP2_off": [
             0,
             0,
-            95,
-            95
+            60,
+            60
         ],
         "AP1_off_AP2_on": [
-            95,
-            95,
+            60,
+            60,
             0,
             0
         ],
@@ -638,6 +638,7 @@
     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:
@@ -1025,7 +1026,7 @@
         num_of_tries=num_of_tries)
 
 
-def _toggle_wifi_and_wait_for_reconnection(ad, network, num_of_tries=1):
+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.
 
@@ -1948,12 +1949,13 @@
     return [attn0, attn1]
 
 
-def set_attns(attenuator, attn_val_name):
+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:
@@ -1966,7 +1968,11 @@
                        attn_val_name)
         raise
 
-def set_attns_steps(attenuators, atten_val_name, steps=10, wait_time=12):
+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.
@@ -1975,6 +1981,7 @@
         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.
     """
@@ -1990,7 +1997,11 @@
         time.sleep(wait_time)
 
 
-def trigger_roaming_and_validate(dut, attenuator, attn_val_name, expected_con):
+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.
 
@@ -1998,14 +2009,13 @@
         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(attenuator, attn_val_name)
-    logging.info("Wait %ss for roaming to finish.", ROAMING_TIMEOUT)
-    time.sleep(ROAMING_TIMEOUT)
+    set_attns_steps(attenuator, attn_val_name, roaming_attn)
 
     verify_wifi_connection_info(dut, expected_con)
     expected_bssid = expected_con[WifiEnums.BSSID_KEY]
@@ -2034,6 +2044,13 @@
     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],
@@ -2042,6 +2059,15 @@
                          "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,
@@ -2092,6 +2118,41 @@
         return None
     return num_of_clients
 
+def get_current_softap_info(ad, callbackId, least_one):
+    """pop up all of softap info changed event from queue.
+    Args:
+        callbackId: Id of the callback associated with registering.
+        least_one: Wait for the info callback event before pop all.
+    Returns:
+        Returns last updated information of softap.
+    """
+    eventStr = wifi_constants.SOFTAP_CALLBACK_EVENT + str(
+            callbackId) + wifi_constants.SOFTAP_INFO_CHANGED
+    ad.log.info("softap info dump from eventStr %s",
+                eventStr)
+    frequency = 0
+    bandwidth = 0
+    if (least_one):
+        event = ad.ed.pop_event(eventStr, SHORT_TIMEOUT)
+        frequency = event['data'][wifi_constants.
+                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+        bandwidth = event['data'][wifi_constants.
+                SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
+        ad.log.info("softap info updated, frequency is %s, bandwidth is %s",
+            frequency, bandwidth)
+
+    events = ad.ed.pop_all(eventStr)
+    for event in events:
+        frequency = event['data'][wifi_constants.
+                SOFTAP_INFO_FREQUENCY_CALLBACK_KEY]
+        bandwidth = event['data'][wifi_constants.
+                SOFTAP_INFO_BANDWIDTH_CALLBACK_KEY]
+    ad.log.info("softap info, frequency is %s, bandwidth is %s",
+            frequency, bandwidth)
+    return frequency, bandwidth
+
+
+
 def get_ssrdumps(ad, test_name=""):
     """Pulls dumps in the ssrdump dir
     Args:
@@ -2151,30 +2212,33 @@
     if test_status:
         shutil.rmtree(os.path.dirname(fname))
 
-def verify_mac_not_found_in_pcap(mac, packets):
+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("Caught Factory MAC: %s in packet sniffer."
-                         "Packet = %s" % (mac, pkt.show()))
+            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(mac, packets):
+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." % mac)
+    asserts.fail("Did not find MAC = %s in packet sniffer."
+                 "for device %s" % (mac, ad.serial))
 
 def start_cnss_diags(ads):
     for ad in ads:
diff --git a/acts/framework/acts/tracelogger.py b/acts/framework/acts/tracelogger.py
index 9652fd0..ed9ee12 100644
--- a/acts/framework/acts/tracelogger.py
+++ b/acts/framework/acts/tracelogger.py
@@ -14,11 +14,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import datetime
 import inspect
-import logging
 import os
-import xml.etree.cElementTree as et
 
 
 class TraceLogger(object):
@@ -65,41 +62,3 @@
 
     def __getattr__(self, name):
         return getattr(self._logger, name)
-
-
-class TakoTraceLogger(TraceLogger):
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        self.d = self.debug
-        self.e = self.error
-        self.i = self.info
-        self.t = self.step
-        self.w = self.warning
-
-    def _logger_level(self, level_name):
-        level = logging.getLevelName(level_name)
-        return lambda *args, **kwargs: self._logger.log(level, *args, **kwargs)
-
-    def step(self, msg, *args, **kwargs):
-        """Delegate a step call to the underlying logger."""
-        self._log_with(self._logger_level('STEP'), 1, msg, *args, **kwargs)
-
-    def device(self, msg, *args, **kwargs):
-        """Delegate a device call to the underlying logger."""
-        self._log_with(self._logger_level('DEVICE'), 1, msg, *args, **kwargs)
-
-    def suite(self, msg, *args, **kwargs):
-        """Delegate a device call to the underlying logger."""
-        self._log_with(self._logger_level('SUITE'), 1, msg, *args, **kwargs)
-
-    def case(self, msg, *args, **kwargs):
-        """Delegate a case call to the underlying logger."""
-        self._log_with(self._logger_level('CASE'), 1, msg, *args, **kwargs)
-
-    def flush_log(self):
-        """This function exists for compatibility with Tako's logserial module.
-
-        Note that flushing the log is handled automatically by python's logging
-        module.
-        """
-        pass
diff --git a/acts/framework/acts/utils.py b/acts/framework/acts/utils.py
index 091d640..209e028 100755
--- a/acts/framework/acts/utils.py
+++ b/acts/framework/acts/utils.py
@@ -19,7 +19,7 @@
 import copy
 import datetime
 import functools
-import IPy
+import ipaddress
 import json
 import logging
 import os
@@ -37,9 +37,6 @@
 
 from acts import signals
 from acts.controllers import adb
-from acts.controllers.android_device import AndroidDevice
-from acts.controllers.fuchsia_device import FuchsiaDevice
-from acts.controllers.utils_lib.ssh.connection import SshConnection
 from acts.libs.proc import job
 
 # File name length is limited to 255 chars on some OS, so we need to make sure
@@ -962,10 +959,7 @@
                  default www.google.com
         timeout: timeout for icmp pings to complete.
     """
-    if is_valid_ipv6_address(dest_ip):
-        ping_cmd = "ping6 -W 1"
-    else:
-        ping_cmd = "ping -W 1"
+    ping_cmd = "ping -W 1"
     if count:
         ping_cmd += " -c %d" % count
     if dest_ip:
@@ -1401,7 +1395,7 @@
 
     Returns:
         A list of dictionaries of the the various IP addresses:
-            ipv4_private_local_addresses: Any 192.168, 172.16, 10, or 169.254
+            ipv4_private_local_addresses: Any 192.168, 172.16, or 10
                 addresses
             ipv4_public_addresses: Any IPv4 public addresses
             ipv6_link_local_addresses: Any fe80:: addresses
@@ -1413,68 +1407,26 @@
     ipv6_link_local_addresses = []
     ipv6_private_local_addresses = []
     ipv6_public_addresses = []
-    is_local = comm_channel == job
-    if type(comm_channel) is AndroidDevice:
-        all_interfaces_and_addresses = comm_channel.adb.shell(
-            'ip -o addr | awk \'!/^[0-9]*: ?lo|link\/ether/ {gsub("/", " "); '
-            'print $2" "$4}\'')
-        ifconfig_output = comm_channel.adb.shell('ifconfig %s' % interface)
-    elif (type(comm_channel) is SshConnection or is_local):
-        all_interfaces_and_addresses = comm_channel.run(
-            'ip -o addr | awk \'!/^[0-9]*: ?lo|link\/ether/ {gsub("/", " "); '
-            'print $2" "$4}\'').stdout
-        ifconfig_output = comm_channel.run('ifconfig %s' % interface).stdout
-    elif type(comm_channel) is FuchsiaDevice:
-        all_interfaces_and_addresses = []
-        comm_channel.netstack_lib.init()
-        interfaces = comm_channel.netstack_lib.netstackListInterfaces()
-        if interfaces.get('error') is not None:
-            raise ActsUtilsError('Failed with {}'.format(
-                interfaces.get('error')))
-        for item in interfaces.get('result'):
-            for ipv4_address in item['ipv4_addresses']:
-                ipv4_address = '.'.join(map(str, ipv4_address))
-                all_interfaces_and_addresses.append(
-                    '%s %s' % (item['name'], ipv4_address))
-            for ipv6_address in item['ipv6_addresses']:
-                converted_ipv6_address = []
-                for octet in ipv6_address:
-                    converted_ipv6_address.append(format(octet, 'x').zfill(2))
-                ipv6_address = ''.join(converted_ipv6_address)
-                ipv6_address = (':'.join(
-                    ipv6_address[i:i + 4]
-                    for i in range(0, len(ipv6_address), 4)))
-                all_interfaces_and_addresses.append(
-                    '%s %s' % (item['name'], str(IPy.IP(ipv6_address))))
-        all_interfaces_and_addresses = '\n'.join(all_interfaces_and_addresses)
-        ifconfig_output = all_interfaces_and_addresses
-    else:
-        raise ValueError('Unsupported method to send command to device.')
-
+    all_interfaces_and_addresses = comm_channel.run(
+        'ip -o addr | awk \'!/^[0-9]*: ?lo|link\/ether/ {gsub("/", " "); '
+        'print $2" "$4}\'').stdout
+    ifconfig_output = comm_channel.run('ifconfig %s' % interface).stdout
     for interface_line in all_interfaces_and_addresses.split('\n'):
         if interface != interface_line.split()[0]:
             continue
-        on_device_ip = IPy.IP(interface_line.split()[1])
-        if on_device_ip.version() is 4:
-            if on_device_ip.iptype() == 'PRIVATE':
-                if str(on_device_ip) in ifconfig_output:
-                    ipv4_private_local_addresses.append(
-                        on_device_ip.strNormal())
-            elif (on_device_ip.iptype() == 'PUBLIC'
-                  or on_device_ip.iptype() == 'CARRIER_GRADE_NAT'):
-                if str(on_device_ip) in ifconfig_output:
-                    ipv4_public_addresses.append(on_device_ip.strNormal())
-        elif on_device_ip.version() is 6:
-            if on_device_ip.iptype() == 'LINKLOCAL':
-                if str(on_device_ip) in ifconfig_output:
-                    ipv6_link_local_addresses.append(on_device_ip.strNormal())
-            elif on_device_ip.iptype() == 'ULA':
-                if str(on_device_ip) in ifconfig_output:
-                    ipv6_private_local_addresses.append(
-                        on_device_ip.strNormal())
-            elif 'ALLOCATED' in on_device_ip.iptype():
-                if str(on_device_ip) in ifconfig_output:
-                    ipv6_public_addresses.append(on_device_ip.strNormal())
+        on_device_ip = ipaddress.ip_address(interface_line.split()[1])
+        if on_device_ip.version() == 4:
+            if on_device_ip.is_private():
+                ipv4_private_local_addresses.append(str(on_device_ip))
+            elif on_device_ip.is_global():
+                ipv4_public_addresses.append(str(on_device_ip))
+        elif on_device_ip.version() == 6:
+            if on_device_ip.is_link_local():
+                ipv6_link_local_addresses.append(str(on_device_ip))
+            elif on_device_ip.is_private():
+                ipv6_private_local_addresses.append(str(on_device_ip))
+            elif on_device_ip.is_global():
+                ipv6_public_addresses.append(str(on_device_ip))
     return {
         'ipv4_private': ipv4_private_local_addresses,
         'ipv4_public': ipv4_public_addresses,
@@ -1500,6 +1452,14 @@
     all_ips_and_interfaces = comm_channel.run(
         '(ip -o -4 addr show; ip -o -6 addr show) | '
         'awk \'{print $2" "$4}\'').stdout
+    #ipv4_addresses = comm_channel.run(
+    #    'ip -o -4 addr show| awk \'{print $2": "$4}\'').stdout
+    #ipv6_addresses = comm_channel._ssh_session.run(
+    #    'ip -o -6 addr show| awk \'{print $2": "$4}\'').stdout
+    #if desired_ip_address in ipv4_addresses:
+    #    ip_addresses_to_search = ipv4_addresses
+    #elif desired_ip_address in ipv6_addresses:
+    #    ip_addresses_to_search = ipv6_addresses
     for ip_address_and_interface in all_ips_and_interfaces.split('\n'):
         if desired_ip_address in ip_address_and_interface:
             return ip_address_and_interface.split()[1][:-1]
@@ -1509,18 +1469,5 @@
 def renew_linux_ip_address(comm_channel, interface):
     comm_channel.run('sudo ifconfig %s down' % interface)
     comm_channel.run('sudo ifconfig %s up' % interface)
-    comm_channel.run('sudo killall dhcpcd 2>/dev/null; echo""')
-    is_dhcpcd_dead = False
-    dhcpcd_counter = 0
-    dhcpcd_checker_max_times = 3
-    while not is_dhcpcd_dead:
-        if dhcpcd_counter == dhcpcd_checker_max_times:
-            raise TimeoutError('Unable to stop dhcpcd')
-        time.sleep(1)
-        if 'dhcpcd' in comm_channel.run('ps axu').stdout:
-            dhcpcd_counter += 1
-        else:
-            is_dhcpcd_dead = True
-    comm_channel.run('sudo dhcpcd -q -b')
     comm_channel.run('sudo dhclient -r %s' % interface)
     comm_channel.run('sudo dhclient %s' % interface)
diff --git a/acts/framework/setup.py b/acts/framework/setup.py
index e08824c..6eb41e1 100755
--- a/acts/framework/setup.py
+++ b/acts/framework/setup.py
@@ -32,6 +32,7 @@
     'numpy',
     'pyserial',
     'pyyaml>=5.1',
+    'tzlocal',
     'shellescape>=3.4.1',
     'protobuf',
     'retry',
@@ -41,7 +42,6 @@
     'xlsxwriter',
     'mobly>=1.10.0',
     'grpcio',
-    'IPy',
     'Monsoon',
     # paramiko-ng is needed vs paramiko as currently paramiko does not support
     # ed25519 ssh keys, which is what Fuchsia uses.
diff --git a/acts/framework/tests/acts_utils_test.py b/acts/framework/tests/acts_utils_test.py
index 686b4b1..2ede709 100755
--- a/acts/framework/tests/acts_utils_test.py
+++ b/acts/framework/tests/acts_utils_test.py
@@ -23,156 +23,13 @@
 from acts import utils
 from acts import signals
 from acts.controllers.adb import AdbError
-from acts.controllers.android_device import AndroidDevice
-from acts.controllers.fuchsia_device import FuchsiaDevice
-from acts.controllers.utils_lib.ssh.connection import SshConnection
-from acts.libs.proc import job
 
 PROVISIONED_STATE_GOOD = 1
 
-MOCK_IP_ADDRESSES = """eno1 100.127.110.79
-eno1 2401:fa00:480:7a00:8d4f:85ff:cc5c:787e
-eno1 2401:fa00:480:7a00:459:b993:fcbf:1419
-eno1 fe80::c66d:3c75:2cec:1d72
-enx00e04c000d06 192.168.42.220
-enx00e04c000d06 fe80::2c68:f1b7:eaaa:52e7"""
-
-MOCK_IFCONFIG_OUTPUT = """eno1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
-        inet 100.127.110.79  netmask 255.255.255.0  broadcast 100.127.110.255
-        inet6 2401:fa00:480:7a00:8d4f:85ff:cc5c:787e  prefixlen 64  scopeid 0x0<global>
-        inet6 fe80::c66d:3c75:2cec:1d72  prefixlen 64  scopeid 0x20<link>
-        inet6 2401:fa00:480:7a00:459:b993:fcbf:1419  prefixlen 64  scopeid 0x0<global>
-        ether 54:b2:03:13:36:05  txqueuelen 1000  (Ethernet)
-        RX packets 32943262  bytes 13324306863 (13.3 GB)
-        RX errors 669  dropped 0  overruns 0  frame 669
-        TX packets 4778580  bytes 3012041798 (3.0 GB)
-        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
-        device interrupt 16  memory 0xdf200000-df220000
-
-enx00e04c000d06: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
-        inet 192.168.42.220  netmask 255.255.255.0  broadcast 192.168.42.255
-        inet6 fe80::2c68:f1b7:eaaa:52e7  prefixlen 64  scopeid 0x20<link>
-        ether 00:e0:4c:00:0d:06  txqueuelen 1000  (Ethernet)
-        RX packets 10212416  bytes 3204008175 (3.2 GB)
-        RX errors 0  dropped 0  overruns 0  frame 0
-        TX packets 9868425  bytes 5641667955 (5.6 GB)
-        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
-
-lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
-        inet 127.0.0.1  netmask 255.0.0.0
-        inet6 ::1  prefixlen 128  scopeid 0x10<host>
-        loop  txqueuelen 1000  (Local Loopback)
-        RX packets 42779835  bytes 6144028882 (6.1 GB)
-        RX errors 0  dropped 0  overruns 0  frame 0
-        TX packets 42779835  bytes 6144028882 (6.1 GB)
-        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
-
-"""
-
-FUCHSIA_INTERFACES = {
-    'id':
-    '1',
-    'result': [{
-        'features':
-        4,
-        'filepath':
-        '[none]',
-        'id':
-        1,
-        'ipv4_addresses': [[127, 0, 0, 1]],
-        'ipv6_addresses': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]],
-        'is_administrative_status_enabled':
-        True,
-        'is_physical_status_up':
-        True,
-        'mac': [0, 0, 0, 0, 0, 0],
-        'mtu':
-        65536,
-        'name':
-        'lo',
-        'topopath':
-        'loopback'
-    }, {
-        'features':
-        0,
-        'filepath':
-        '/dev/class/ethernet/000',
-        'id':
-        2,
-        'ipv4_addresses': [[100, 127, 110, 79]],
-        'ipv6_addresses':
-        [[254, 128, 0, 0, 0, 0, 0, 0, 198, 109, 60, 117, 44, 236, 29, 114],
-         [36, 1, 250, 0, 4, 128, 122, 0, 141, 79, 133, 255, 204, 92, 120, 126],
-         [36, 1, 250, 0, 4, 128, 122, 0, 4, 89, 185, 147, 252, 191, 20, 25]],
-        'is_administrative_status_enabled':
-        True,
-        'is_physical_status_up':
-        True,
-        'mac': [0, 224, 76, 5, 76, 229],
-        'mtu':
-        1514,
-        'name':
-        'eno1',
-        'topopath':
-        '@/dev/xhci/xhci/usb-bus/001/001/ifc-000/usb-cdc-ecm/ethernet'
-    }, {
-        'features':
-        1,
-        'filepath':
-        '/dev/class/ethernet/001',
-        'id':
-        3,
-        'ipv4_addresses': [],
-        'ipv6_addresses':
-        [[254, 128, 0, 0, 0, 0, 0, 0, 96, 255, 93, 96, 52, 253, 253, 243],
-         [254, 128, 0, 0, 0, 0, 0, 0, 70, 7, 11, 255, 254, 118, 126, 192]],
-        'is_administrative_status_enabled':
-        False,
-        'is_physical_status_up':
-        False,
-        'mac': [68, 7, 11, 118, 126, 192],
-        'mtu':
-        1500,
-        'name':
-        'wlanxc0',
-        'topopath':
-        '@/dev/wifi/wlanphy/wlanif-client/wlan-ethernet/ethernet'
-    }],
-    'error':
-    None
-}
-
-CORRECT_FULL_IP_LIST = {
-    'ipv4_private': [],
-    'ipv4_public': ['100.127.110.79'],
-    'ipv6_link_local': ['fe80:0:0:0:c66d:3c75:2cec:1d72'],
-    'ipv6_private_local': [],
-    'ipv6_public': [
-        '2401:fa00:480:7a00:8d4f:85ff:cc5c:787e',
-        '2401:fa00:480:7a00:459:b993:fcbf:1419'
-    ]
-}
-
-CORRECT_EMPTY_IP_LIST = {
-    'ipv4_private': [],
-    'ipv4_public': [],
-    'ipv6_link_local': [],
-    'ipv6_private_local': [],
-    'ipv6_public': []
-}
-
-FUCHSIA_INIT_SERVER = ('acts.controllers.fuchsia_device.FuchsiaDevice.'
-                       'init_server_connection')
-FUCHSIA_NETSTACK_LIST_INTERFACES = (
-    'acts.controllers.'
-    'fuchsia_lib.netstack.netstack_lib.'
-    'FuchsiaNetstackLib.netstackListInterfaces')
-FUCHSIA_INIT_NETSTACK = ('acts.controllers.fuchsia_lib.netstack.'
-                         'netstack_lib.FuchsiaNetstackLib.init')
-
 
 class ByPassSetupWizardTests(unittest.TestCase):
     """This test class for unit testing acts.utils.bypass_setup_wizard."""
+
     def test_start_standing_subproc(self):
         with self.assertRaisesRegex(utils.ActsUtilsError,
                                     'Process .* has terminated'):
@@ -269,32 +126,33 @@
 
 class BypassSetupWizardReturn:
     # No complications. Bypass works the first time without issues.
-    NO_COMPLICATIONS = (
-        'Starting: Intent { cmp=com.google.android.setupwizard/'
-        '.SetupWizardExitActivity }')
+    NO_COMPLICATIONS = ('Starting: Intent { cmp=com.google.android.setupwizard/'
+                        '.SetupWizardExitActivity }')
 
     # Fail with doesn't need to be skipped/was skipped already.
     ALREADY_BYPASSED = AdbError('', 'ADB_CMD_OUTPUT:0', 'Error type 3\n'
-                                'Error: Activity class', 1)
+                                                        'Error: Activity class',
+                                1)
     # Fail with different error.
     UNRECOGNIZED_ERR = AdbError('', 'ADB_CMD_OUTPUT:0', 'Error type 4\n'
-                                'Error: Activity class', 0)
+                                                        'Error: Activity class',
+                                0)
     # Fail, get root access, then no complications arise.
-    ROOT_ADB_NO_COMP = AdbError(
-        '', 'ADB_CMD_OUTPUT:255', 'Security exception: Permission Denial: '
-        'starting Intent { flg=0x10000000 '
-        'cmp=com.google.android.setupwizard/'
-        '.SetupWizardExitActivity } from null '
-        '(pid=5045, uid=2000) not exported from uid '
-        '10000', 0)
+    ROOT_ADB_NO_COMP = AdbError('', 'ADB_CMD_OUTPUT:255',
+                                'Security exception: Permission Denial: '
+                                'starting Intent { flg=0x10000000 '
+                                'cmp=com.google.android.setupwizard/'
+                                '.SetupWizardExitActivity } from null '
+                                '(pid=5045, uid=2000) not exported from uid '
+                                '10000', 0)
     # Even with root access, the bypass setup wizard doesn't need to be skipped.
-    ROOT_ADB_SKIPPED = AdbError(
-        '', 'ADB_CMD_OUTPUT:255', 'Security exception: Permission Denial: '
-        'starting Intent { flg=0x10000000 '
-        'cmp=com.google.android.setupwizard/'
-        '.SetupWizardExitActivity } from null '
-        '(pid=5045, uid=2000) not exported from '
-        'uid 10000', 0)
+    ROOT_ADB_SKIPPED = AdbError('', 'ADB_CMD_OUTPUT:255',
+                                'Security exception: Permission Denial: '
+                                'starting Intent { flg=0x10000000 '
+                                'cmp=com.google.android.setupwizard/'
+                                '.SetupWizardExitActivity } from null '
+                                '(pid=5045, uid=2000) not exported from '
+                                'uid 10000', 0)
     # Even with root access, the bypass setup wizard fails
     ROOT_ADB_FAILS = AdbError(
         '', 'ADB_CMD_OUTPUT:255',
@@ -306,6 +164,7 @@
 
 class ConcurrentActionsTest(unittest.TestCase):
     """Tests acts.utils.run_concurrent_actions and related functions."""
+
     @staticmethod
     def function_returns_passed_in_arg(arg):
         return arg
@@ -314,8 +173,7 @@
     def function_raises_passed_in_exception_type(exception_type):
         raise exception_type
 
-    def test_run_concurrent_actions_no_raise_returns_proper_return_values(
-        self):
+    def test_run_concurrent_actions_no_raise_returns_proper_return_values(self):
         """Tests run_concurrent_actions_no_raise returns in the correct order.
 
         Each function passed into run_concurrent_actions_no_raise returns the
@@ -324,7 +182,8 @@
         ret_values = utils.run_concurrent_actions_no_raise(
             lambda: self.function_returns_passed_in_arg('ARG1'),
             lambda: self.function_returns_passed_in_arg('ARG2'),
-            lambda: self.function_returns_passed_in_arg('ARG3'))
+            lambda: self.function_returns_passed_in_arg('ARG3')
+        )
 
         self.assertEqual(len(ret_values), 3)
         self.assertEqual(ret_values[0], 'ARG1')
@@ -340,7 +199,8 @@
         """
         ret_values = utils.run_concurrent_actions_no_raise(
             lambda: self.function_raises_passed_in_exception_type(IndexError),
-            lambda: self.function_raises_passed_in_exception_type(KeyError))
+            lambda: self.function_raises_passed_in_exception_type(KeyError)
+        )
 
         self.assertEqual(len(ret_values), 2)
         self.assertEqual(ret_values[0].__class__, IndexError)
@@ -356,7 +216,8 @@
         ret_values = utils.run_concurrent_actions(
             lambda: self.function_returns_passed_in_arg('ARG1'),
             lambda: self.function_returns_passed_in_arg('ARG2'),
-            lambda: self.function_returns_passed_in_arg('ARG3'))
+            lambda: self.function_returns_passed_in_arg('ARG3')
+        )
 
         self.assertEqual(len(ret_values), 3)
         self.assertEqual(ret_values[0], 'ARG1')
@@ -367,43 +228,42 @@
         """Tests run_concurrent_actions raises exceptions from given actions."""
         with self.assertRaises(KeyError):
             utils.run_concurrent_actions(
-                lambda: self.function_returns_passed_in_arg('ARG1'), lambda:
-                self.function_raises_passed_in_exception_type(KeyError))
+                lambda: self.function_returns_passed_in_arg('ARG1'),
+                lambda: self.function_raises_passed_in_exception_type(KeyError)
+            )
 
     def test_test_concurrent_actions_raises_non_test_failure(self):
         """Tests test_concurrent_actions raises the given exception."""
         with self.assertRaises(KeyError):
             utils.test_concurrent_actions(
-                lambda: self.function_raises_passed_in_exception_type(KeyError
-                                                                      ),
-                failure_exceptions=signals.TestFailure)
+                lambda: self.function_raises_passed_in_exception_type(KeyError),
+                failure_exceptions=signals.TestFailure
+            )
 
     def test_test_concurrent_actions_raises_test_failure(self):
         """Tests test_concurrent_actions raises the given exception."""
         with self.assertRaises(signals.TestFailure):
             utils.test_concurrent_actions(
-                lambda: self.function_raises_passed_in_exception_type(KeyError
-                                                                      ),
-                failure_exceptions=KeyError)
+                lambda: self.function_raises_passed_in_exception_type(KeyError),
+                failure_exceptions=KeyError
+            )
 
 
 class SuppressLogOutputTest(unittest.TestCase):
     """Tests SuppressLogOutput"""
+
     def test_suppress_log_output(self):
         """Tests that the SuppressLogOutput context manager removes handlers
         of the specified levels upon entry and re-adds handlers upon exit.
         """
-        handlers = [
-            logging.NullHandler(level=lvl)
-            for lvl in (logging.DEBUG, logging.INFO, logging.ERROR)
-        ]
+        handlers = [logging.NullHandler(level=lvl) for lvl in
+                    (logging.DEBUG, logging.INFO, logging.ERROR)]
         log = logging.getLogger('test_log')
         for handler in handlers:
             log.addHandler(handler)
         with utils.SuppressLogOutput(log, [logging.INFO, logging.ERROR]):
             self.assertTrue(
-                any(handler.level == logging.DEBUG
-                    for handler in log.handlers))
+                any(handler.level == logging.DEBUG for handler in log.handlers))
             self.assertFalse(
                 any(handler.level in (logging.INFO, logging.ERROR)
                     for handler in log.handlers))
@@ -411,6 +271,7 @@
 
 
 class IpAddressUtilTest(unittest.TestCase):
+
     def test_positive_ipv4_normal_address(self):
         ip_address = "192.168.1.123"
         self.assertTrue(utils.is_valid_ipv4_address(ip_address))
@@ -455,96 +316,6 @@
         ip_address = 'fdsafdsafdsafdsf'
         self.assertFalse(utils.is_valid_ipv6_address(ip_address))
 
-    @mock.patch('acts.libs.proc.job.run')
-    def test_local_get_interface_ip_addresses_full(self, job_mock):
-        job_mock.side_effect = [
-            job.Result(stdout=bytes(MOCK_IP_ADDRESSES, 'utf-8'),
-                       encoding='utf-8'),
-            job.Result(stdout=bytes(MOCK_IFCONFIG_OUTPUT, 'utf-8'),
-                       encoding='utf-8')
-        ]
-        self.assertTrue(
-            utils.get_interface_ip_addresses(job, 'eno1') ==
-            CORRECT_FULL_IP_LIST)
-
-    @mock.patch('acts.libs.proc.job.run')
-    def test_local_get_interface_ip_addresses_empty(self, job_mock):
-        job_mock.side_effect = [
-            job.Result(stdout=bytes(MOCK_IP_ADDRESSES, 'utf-8'),
-                       encoding='utf-8'),
-            job.Result(stdout=bytes(MOCK_IFCONFIG_OUTPUT, 'utf-8'),
-                       encoding='utf-8')
-        ]
-        self.assertTrue(
-            utils.get_interface_ip_addresses(job, 'wlan1') ==
-            CORRECT_EMPTY_IP_LIST)
-
-    @mock.patch('acts.controllers.utils_lib.ssh.connection.SshConnection.run')
-    def test_ssh_get_interface_ip_addresses_full(self, ssh_mock):
-        ssh_mock.side_effect = [
-            job.Result(stdout=bytes(MOCK_IP_ADDRESSES, 'utf-8'),
-                       encoding='utf-8'),
-            job.Result(stdout=bytes(MOCK_IFCONFIG_OUTPUT, 'utf-8'),
-                       encoding='utf-8')
-        ]
-        self.assertTrue(
-            utils.get_interface_ip_addresses(SshConnection('mock_settings'),
-                                             'eno1') == CORRECT_FULL_IP_LIST)
-
-    @mock.patch('acts.controllers.utils_lib.ssh.connection.SshConnection.run')
-    def test_ssh_get_interface_ip_addresses_empty(self, ssh_mock):
-        ssh_mock.side_effect = [
-            job.Result(stdout=bytes(MOCK_IP_ADDRESSES, 'utf-8'),
-                       encoding='utf-8'),
-            job.Result(stdout=bytes(MOCK_IFCONFIG_OUTPUT, 'utf-8'),
-                       encoding='utf-8')
-        ]
-        self.assertTrue(
-            utils.get_interface_ip_addresses(SshConnection('mock_settings'),
-                                             'wlan1') == CORRECT_EMPTY_IP_LIST)
-
-    @mock.patch('acts.controllers.adb.AdbProxy.shell')
-    def test_android_get_interface_ip_addresses_full(self, adb_mock):
-        adb_mock.side_effect = [MOCK_IP_ADDRESSES, MOCK_IFCONFIG_OUTPUT]
-        self.assertTrue(
-            utils.get_interface_ip_addresses(AndroidDevice(), 'eno1') ==
-            CORRECT_FULL_IP_LIST)
-
-    @mock.patch('acts.controllers.adb.AdbProxy.shell')
-    def test_android_get_interface_ip_addresses_empty(self, adb_mock):
-        adb_mock.side_effect = [MOCK_IP_ADDRESSES, MOCK_IFCONFIG_OUTPUT]
-        self.assertTrue(
-            utils.get_interface_ip_addresses(AndroidDevice(), 'wlan1') ==
-            CORRECT_EMPTY_IP_LIST)
-
-    @mock.patch(FUCHSIA_INIT_SERVER)
-    @mock.patch(FUCHSIA_NETSTACK_LIST_INTERFACES)
-    @mock.patch(FUCHSIA_INIT_NETSTACK)
-    def test_fuchsia_get_interface_ip_addresses_full(self, init_mock,
-                                                     list_interfaces_mock,
-                                                     fuchsia_device_mock):
-        init_mock.return_value = None
-        list_interfaces_mock.return_value = FUCHSIA_INTERFACES
-        fuchsia_device_mock.return_value = None
-        self.assertTrue(
-            utils.get_interface_ip_addresses(
-                FuchsiaDevice({'ip': '192.168.1.1'}), 'eno1') ==
-            CORRECT_FULL_IP_LIST)
-
-    @mock.patch(FUCHSIA_INIT_SERVER)
-    @mock.patch(FUCHSIA_NETSTACK_LIST_INTERFACES)
-    @mock.patch(FUCHSIA_INIT_NETSTACK)
-    def test_fuchsia_get_interface_ip_addresses_empty(self, init_mock,
-                                                      list_interfaces_mock,
-                                                      fuchsia_device_mock):
-        init_mock.return_value = None
-        list_interfaces_mock.return_value = FUCHSIA_INTERFACES
-        fuchsia_device_mock.return_value = None
-        self.assertTrue(
-            utils.get_interface_ip_addresses(
-                FuchsiaDevice({'ip': '192.168.1.1'}), 'wlan1') ==
-            CORRECT_EMPTY_IP_LIST)
-
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/acts/framework/tests/test_utils/instrumentation/data/sample_monsoon_data b/acts/framework/tests/test_utils/instrumentation/data/sample_monsoon_data
new file mode 100644
index 0000000..2a70273
--- /dev/null
+++ b/acts/framework/tests/test_utils/instrumentation/data/sample_monsoon_data
@@ -0,0 +1,10 @@
+0s    3.67
+1s    3.69
+2s    0.95
+3s    3.06
+4s    2.17
+5s    1.62
+6s    3.95
+7s    2.47
+8s    1.11
+9s    0.47
diff --git a/acts/framework/tests/test_utils/instrumentation/device/apps/dismiss_dialogs_test.py b/acts/framework/tests/test_utils/instrumentation/device/apps/dismiss_dialogs_test.py
new file mode 100644
index 0000000..3b36274
--- /dev/null
+++ b/acts/framework/tests/test_utils/instrumentation/device/apps/dismiss_dialogs_test.py
@@ -0,0 +1,72 @@
+#!/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.test_utils.instrumentation.device.apps.dismiss_dialogs import \
+    DialogDismissalUtil
+
+
+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 -f -e apps sample.app.one '
+            'dismiss.dialogs/.DismissDialogsInstrumentation '
+            '-e screenshots true -e quitOnError true'
+        )
+        self._dismiss_dialogs_util._dut.adb.shell.assert_called_with(
+            expected_cmd)
+
+    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 -f -e apps sample.app.one,sample.app.two '
+            'dismiss.dialogs/.DismissDialogsInstrumentation '
+            '-e screenshots true -e quitOnError true'
+        )
+        self._dismiss_dialogs_util._dut.adb.shell.assert_called_with(
+            expected_cmd)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/test_utils/instrumentation/device/apps/hotword_model_extractor_test.py b/acts/framework/tests/test_utils/instrumentation/device/apps/hotword_model_extractor_test.py
new file mode 100644
index 0000000..053da31
--- /dev/null
+++ b/acts/framework/tests/test_utils/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.test_utils.instrumentation.device.apps.hotword_model_extractor import \
+    HotwordModelExtractor
+from acts.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.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/framework/tests/test_utils/instrumentation/device/command/adb_command_types_test.py b/acts/framework/tests/test_utils/instrumentation/device/command/adb_command_types_test.py
index efe9bf0..127575b 100755
--- a/acts/framework/tests/test_utils/instrumentation/device/command/adb_command_types_test.py
+++ b/acts/framework/tests/test_utils/instrumentation/device/command/adb_command_types_test.py
@@ -19,6 +19,8 @@
 from acts.test_utils.instrumentation.device.command.adb_command_types import \
     DeviceBinaryCommandSeries
 from acts.test_utils.instrumentation.device.command.adb_command_types import \
+    DeviceGServices
+from acts.test_utils.instrumentation.device.command.adb_command_types import \
     DeviceSetprop
 from acts.test_utils.instrumentation.device.command.adb_command_types import \
     DeviceSetting
@@ -108,6 +110,19 @@
             device_binary_setting.toggle(False),
             '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),
+            '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.
diff --git a/acts/framework/tests/test_utils/instrumentation/power/__init__.py b/acts/framework/tests/test_utils/instrumentation/power/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/acts/framework/tests/test_utils/instrumentation/power/__init__.py
diff --git a/acts/framework/tests/test_utils/instrumentation/power/instrumentation_power_test_test.py b/acts/framework/tests/test_utils/instrumentation/power/instrumentation_power_test_test.py
new file mode 100644
index 0000000..3a617e2
--- /dev/null
+++ b/acts/framework/tests/test_utils/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
+
+import mock
+from acts.test_utils.instrumentation.config_wrapper import ConfigWrapper
+from acts.test_utils.instrumentation.power.instrumentation_power_test \
+    import ACCEPTANCE_THRESHOLD
+from acts.test_utils.instrumentation.power.instrumentation_power_test \
+    import InstrumentationPowerTest
+from acts.test_utils.instrumentation.power.power_metrics import PowerMetrics
+
+from acts import signals
+
+
+class MockInstrumentationPowerTest(InstrumentationPowerTest):
+    """Mock test class to initialize required attributes."""
+
+    # avg: 2.214, stdev: 1.358, max: 4.78, min: 0.61
+    SAMPLE_DATA = [1.64, 2.98, 1.72, 3.45, 1.31, 4.78, 3.43, 0.61, 1.19, 1.03]
+
+    def __init__(self):
+        self.log = mock.Mock()
+        self.metric_logger = mock.Mock()
+        self.current_test_name = 'test_case'
+        self._power_metrics = PowerMetrics(4.2)
+        self._power_metrics.test_metrics = {
+            'instrTest1': PowerMetrics(4.2),
+            'instrTest2': PowerMetrics(4.2)
+        }
+        self._power_metrics.test_metrics['instrTest1'].generate_test_metrics(
+            list(zip(range(10), self.SAMPLE_DATA))
+        )
+        self._power_metrics.test_metrics['instrTest2'].generate_test_metrics(
+            list(zip(range(10), self.SAMPLE_DATA))
+        )
+        self._instrumentation_config = ConfigWrapper()
+        self._class_config = ConfigWrapper(
+            {
+                self.current_test_name: {
+                    ACCEPTANCE_THRESHOLD: {}
+                }
+            }
+        )
+
+    def set_criteria(self, criteria):
+        """Set the acceptance criteria for metrics validation."""
+        test_config = self._class_config[self.current_test_name]
+        test_config[ACCEPTANCE_THRESHOLD] = ConfigWrapper(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_power_results('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_power_results('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_power_results('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_power_results('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_power_results('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_power_results('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_power_results('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_power_results(
+                '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_power_results(
+                'instrTest1', 'instrTest2')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/framework/tests/test_utils/instrumentation/power/power_metrics_test.py b/acts/framework/tests/test_utils/instrumentation/power/power_metrics_test.py
new file mode 100644
index 0000000..38441d1
--- /dev/null
+++ b/acts/framework/tests/test_utils/instrumentation/power/power_metrics_test.py
@@ -0,0 +1,159 @@
+#!/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 statistics
+import unittest
+
+from acts.test_utils.instrumentation import instrumentation_proto_parser \
+    as parser
+from acts.test_utils.instrumentation.power.power_metrics import CURRENT
+from acts.test_utils.instrumentation.power.power_metrics import HOUR
+from acts.test_utils.instrumentation.power.power_metrics import MILLIAMP
+from acts.test_utils.instrumentation.power.power_metrics import MINUTE
+from acts.test_utils.instrumentation.power.power_metrics import Measurement
+from acts.test_utils.instrumentation.power.power_metrics import PowerMetrics
+from acts.test_utils.instrumentation.power.power_metrics import TIME
+from acts.test_utils.instrumentation.power.power_metrics import WATT
+
+FAKE_UNIT_TYPE = 'fake_unit'
+FAKE_UNIT = 'F'
+
+
+class MeasurementTest(unittest.TestCase):
+    """Unit tests for the Measurement class."""
+
+    def test_init_with_valid_unit_type(self):
+        """Test that a Measurement is properly initialized given a valid unit
+        type.
+        """
+        measurement = Measurement(2, CURRENT, MILLIAMP)
+        self.assertEqual(measurement.value, 2)
+        self.assertEqual(measurement._unit, MILLIAMP)
+
+    def test_init_with_invalid_unit_type(self):
+        """Test that __init__ raises an error if given an invalid unit type."""
+        with self.assertRaisesRegex(TypeError, 'valid unit type'):
+            measurement = Measurement(2, FAKE_UNIT_TYPE, FAKE_UNIT)
+
+    def test_unit_conversion(self):
+        """Test that to_unit correctly converts value and unit."""
+        ratio = 1000
+        current_amps = Measurement.amps(15)
+        current_milliamps = current_amps.to_unit(MILLIAMP)
+        self.assertEqual(current_milliamps.value / current_amps.value, ratio)
+
+    def test_unit_conversion_with_wrong_type(self):
+        """Test that to_unit raises and error if incompatible unit type is
+        specified.
+        """
+        current_amps = Measurement.amps(3.4)
+        with self.assertRaisesRegex(TypeError, 'Incompatible units'):
+            power_watts = current_amps.to_unit(WATT)
+
+    def test_comparison_operators(self):
+        """Test that the comparison operators work as intended."""
+        # time_a == time_b < time_c
+        time_a = Measurement.seconds(120)
+        time_b = Measurement(2, TIME, MINUTE)
+        time_c = Measurement(0.1, TIME, HOUR)
+
+        self.assertEqual(time_a, time_b)
+        self.assertEqual(time_b, time_a)
+        self.assertLessEqual(time_a, time_b)
+        self.assertGreaterEqual(time_a, time_b)
+
+        self.assertNotEqual(time_a, time_c)
+        self.assertNotEqual(time_c, time_a)
+        self.assertLess(time_a, time_c)
+        self.assertLessEqual(time_a, time_c)
+        self.assertGreater(time_c, time_a)
+        self.assertGreaterEqual(time_c, time_a)
+
+    def test_arithmetic_operators(self):
+        """Test that the addition and subtraction operators work as intended"""
+        time_a = Measurement(3, TIME, HOUR)
+        time_b = Measurement(90, TIME, MINUTE)
+
+        sum_ = time_a + time_b
+        self.assertEqual(sum_.value, 4.5)
+        self.assertEqual(sum_._unit, HOUR)
+
+        sum_reversed = time_b + time_a
+        self.assertEqual(sum_reversed.value, 270)
+        self.assertEqual(sum_reversed._unit, MINUTE)
+
+        diff = time_a - time_b
+        self.assertEqual(diff.value, 1.5)
+        self.assertEqual(diff._unit, HOUR)
+
+        diff_reversed = time_b - time_a
+        self.assertEqual(diff_reversed.value, -90)
+        self.assertEqual(diff_reversed._unit, MINUTE)
+
+
+class PowerMetricsTest(unittest.TestCase):
+    """Unit tests for the PowerMetrics class."""
+
+    SAMPLES = [0.13, 0.95, 0.32, 4.84, 2.48, 4.11, 4.85, 4.88, 4.22, 2.2]
+    RAW_DATA = list(zip(range(10), SAMPLES))
+    VOLTAGE = 4.2
+    START_TIME = 5
+
+    def setUp(self):
+        self.power_metrics = PowerMetrics(self.VOLTAGE, self.START_TIME)
+
+    def test_import_raw_data(self):
+        """Test that power metrics can be loaded from file. Simply ensure that
+        the number of samples is correct."""
+
+        imported_data = PowerMetrics.import_raw_data(
+            os.path.join(os.path.dirname(__file__),
+                         '../data/sample_monsoon_data')
+        )
+        self.power_metrics.generate_test_metrics(imported_data)
+        self.assertEqual(self.power_metrics._num_samples, 10)
+
+    def test_split_by_test_with_timestamps(self):
+        """Test that given test timestamps, a power metric is generated from
+        a subset of samples corresponding to the test."""
+        sample_test = 'sample_test'
+        test_start = 8500
+        test_end = 13500
+        test_timestamps = {sample_test: {parser.START_TIMESTAMP: test_start,
+                                         parser.END_TIMESTAMP: test_end}}
+        self.power_metrics.generate_test_metrics(self.RAW_DATA, test_timestamps)
+        test_metrics = self.power_metrics.test_metrics[sample_test]
+        self.assertEqual(test_metrics._num_samples, 5)
+
+    def test_numeric_metrics(self):
+        """Test that the numeric metrics have correct values."""
+        self.power_metrics.generate_test_metrics(self.RAW_DATA)
+        self.assertAlmostEqual(self.power_metrics.avg_current.value,
+                               statistics.mean(self.SAMPLES) * 1000)
+        self.assertAlmostEqual(self.power_metrics.max_current.value,
+                               max(self.SAMPLES) * 1000)
+        self.assertAlmostEqual(self.power_metrics.min_current.value,
+                               min(self.SAMPLES) * 1000)
+        self.assertAlmostEqual(self.power_metrics.stdev_current.value,
+                               statistics.stdev(self.SAMPLES) * 1000)
+        self.assertAlmostEqual(
+            self.power_metrics.avg_power.value,
+            self.power_metrics.avg_current.value * self.VOLTAGE)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/acts/tests/google/ble/concurrency/ConcurrentBleAdvertisementDiscoveryTest.py b/acts/tests/google/ble/concurrency/ConcurrentBleAdvertisementDiscoveryTest.py
index 2af4c05..c38fc93 100644
--- a/acts/tests/google/ble/concurrency/ConcurrentBleAdvertisementDiscoveryTest.py
+++ b/acts/tests/google/ble/concurrency/ConcurrentBleAdvertisementDiscoveryTest.py
@@ -43,22 +43,21 @@
 
 class ConcurrentBleAdvertisementDiscoveryTest(BluetoothBaseTest):
     default_timeout = 10
+    droid_list = []
     max_advertisements = -1
     advertise_callback_list = []
 
     def setup_class(self):
         super().setup_class()
-        self.droid_list = get_advanced_droid_list(self.android_devices)
         self.scn_ad = self.android_devices[0]
         self.adv_ad = self.android_devices[1]
+        self.droid_list = get_advanced_droid_list(self.android_devices)
         self.max_advertisements = self.droid_list[1]['max_advertisements']
 
     def setup_test(self):
-        return reset_bluetooth(self.android_devices)
-
-    def setup_test(self):
-        super(BluetoothBaseTest, self).setup_test()
+        super().setup_test()
         self.log.info("Setting up advertisements")
+        reset_bluetooth(self.android_devices)
         try:
             self.advertise_callback_list = setup_n_advertisements(
                 self.adv_ad, self.max_advertisements)
diff --git a/acts/tests/google/ble/concurrency/ConcurrentBleAdvertisingTest.py b/acts/tests/google/ble/concurrency/ConcurrentBleAdvertisingTest.py
index 94ee0f7..09c6cd3 100644
--- a/acts/tests/google/ble/concurrency/ConcurrentBleAdvertisingTest.py
+++ b/acts/tests/google/ble/concurrency/ConcurrentBleAdvertisingTest.py
@@ -43,17 +43,18 @@
 
 class ConcurrentBleAdvertisingTest(BluetoothBaseTest):
     default_timeout = 10
+    droid_list = []
     max_advertisements = -1
 
     def setup_class(self):
         super().setup_class()
-        self.droid_list = get_advanced_droid_list(self.android_devices)
         self.scn_ad = self.android_devices[0]
         self.adv_ad = self.android_devices[1]
+        self.droid_list = get_advanced_droid_list(self.android_devices)
         self.max_advertisements = self.droid_list[1]['max_advertisements']
 
     def setup_test(self):
-        super(BluetoothBaseTest, self).setup_test()
+        super().setup_test()
         return reset_bluetooth(self.android_devices)
 
     def _verify_n_advertisements(self, num_advertisements):
diff --git a/acts/tests/google/ble/conn_oriented_chan/BleCocTest.py b/acts/tests/google/ble/conn_oriented_chan/BleCocTest.py
index 97ace9b..166b848 100644
--- a/acts/tests/google/ble/conn_oriented_chan/BleCocTest.py
+++ b/acts/tests/google/ble/conn_oriented_chan/BleCocTest.py
@@ -49,14 +49,14 @@
     def setup_class(self):
         super().setup_class()
         self.client_ad = self.android_devices[0]
-        # The client which is scanning will need location to be enabled in order to
-        # start scan and get scan results.
-        utils.set_location_service(self.client_ad, True)
         self.server_ad = self.android_devices[1]
         # Note that some tests required a third device.
         if len(self.android_devices) > 2:
             self.server2_ad = self.android_devices[2]
 
+        # The client which is scanning will need location to be enabled in order to
+        # start scan and get scan results.
+        utils.set_location_service(self.client_ad, True)
         return setup_multiple_devices_for_bt_test(self.android_devices)
 
     def teardown_test(self):
diff --git a/acts/tests/google/ble/system_tests/BleStressTest.py b/acts/tests/google/ble/system_tests/BleStressTest.py
index f97907f..afbb67a 100644
--- a/acts/tests/google/ble/system_tests/BleStressTest.py
+++ b/acts/tests/google/ble/system_tests/BleStressTest.py
@@ -37,6 +37,7 @@
 class BleStressTest(BluetoothBaseTest):
     default_timeout = 10
     PAIRING_TIMEOUT = 20
+    droid_list = []
 
     def setup_class(self):
         super().setup_class()
@@ -45,7 +46,7 @@
         self.adv_ad = self.android_devices[1]
 
     def teardown_test(self):
-        super(BluetoothBaseTest, self).teardown_test()
+        super().teardown_test()
         self.log_stats()
 
     def bleadvertise_verify_onsuccess_handler(self, event):
diff --git a/acts/tests/google/experimental/BluetoothLatencyTest.py b/acts/tests/google/experimental/BluetoothLatencyTest.py
index 811a41c..ea6ed4a 100644
--- a/acts/tests/google/experimental/BluetoothLatencyTest.py
+++ b/acts/tests/google/experimental/BluetoothLatencyTest.py
@@ -21,6 +21,7 @@
 from acts import asserts
 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
@@ -112,6 +113,7 @@
         return (end_time - start_time) * 1000
 
     @BluetoothBaseTest.bt_test_wrap
+    @test_tracker_info(uuid='7748295d-204e-4ad0-adf5-7591380b940a')
     def test_bluetooth_latency(self):
         """Tests the latency for a data transfer over RFCOMM"""
 
diff --git a/acts/tests/google/experimental/BluetoothPairAndConnectTest.py b/acts/tests/google/experimental/BluetoothPairAndConnectTest.py
index e54e4e7..2402fb5 100644
--- a/acts/tests/google/experimental/BluetoothPairAndConnectTest.py
+++ b/acts/tests/google/experimental/BluetoothPairAndConnectTest.py
@@ -27,8 +27,11 @@
 from acts.controllers.buds_lib.test_actions.apollo_acts import ApolloTestActions
 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.utils import set_location_service
 
@@ -74,10 +77,10 @@
         self.bt_logger = BluetoothMetricLogger.for_test_case()
 
     def setup_test(self):
+        setup_multiple_devices_for_bt_test(self.android_devices)
         # Make sure Bluetooth is on
         enable_bluetooth(self.phone.droid, self.phone.ed)
         set_location_service(self.phone, True)
-        factory_reset_bluetooth([self.phone])
         self.apollo_act.factory_reset()
         self.log.info('===== START BLUETOOTH PAIR AND CONNECT TEST  =====')
 
@@ -116,6 +119,8 @@
 
         return pair_time, connection_time
 
+    @BluetoothBaseTest.bt_test_wrap
+    @test_tracker_info(uuid='c914fd08-350d-465a-96cf-970d40e71060')
     def test_bluetooth_connect(self):
         # Store metrics
         metrics = {}
diff --git a/acts/tests/google/experimental/BluetoothReconnectTest.py b/acts/tests/google/experimental/BluetoothReconnectTest.py
index a03ec7b..5339e51 100644
--- a/acts/tests/google/experimental/BluetoothReconnectTest.py
+++ b/acts/tests/google/experimental/BluetoothReconnectTest.py
@@ -22,9 +22,12 @@
 from acts import asserts
 from acts.base_test import BaseTestClass
 from acts.controllers.buds_lib.test_actions.apollo_acts import ApolloTestActions
-from acts.signals import TestPass, TestFailure
+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 factory_reset_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.utils import set_location_service
 
@@ -67,10 +70,10 @@
         self.bt_logger = BluetoothMetricLogger.for_test_case()
 
     def setup_test(self):
+        setup_multiple_devices_for_bt_test(self.android_devices)
         # Make sure Bluetooth is on
         enable_bluetooth(self.phone.droid, self.phone.ed)
         set_location_service(self.phone, True)
-        factory_reset_bluetooth([self.phone])
         self.apollo_act.factory_reset()
 
         # Initial pairing and connection of devices
@@ -119,6 +122,8 @@
         end_time = time.perf_counter()
         return (end_time - start_time) * 1000
 
+    @BluetoothBaseTest.bt_test_wrap
+    @test_tracker_info(uuid='da921903-92d0-471d-ae01-456058cc1297')
     def test_bluetooth_reconnect(self):
         """Reconnects Bluetooth between a phone and Apollo device a specified
         number of times and reports connection time statistics."""
diff --git a/acts/tests/google/experimental/BluetoothThroughputTest.py b/acts/tests/google/experimental/BluetoothThroughputTest.py
index 3403ded..1f53172 100644
--- a/acts/tests/google/experimental/BluetoothThroughputTest.py
+++ b/acts/tests/google/experimental/BluetoothThroughputTest.py
@@ -18,6 +18,7 @@
 from acts import asserts
 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
@@ -109,6 +110,7 @@
         return throughput
 
     @BluetoothBaseTest.bt_test_wrap
+    @test_tracker_info(uuid='23afba5b-5801-42c2-8d7a-41510e91a605')
     def test_bluetooth_throughput_large_buffer(self):
         """Tests the throughput over a series of data transfers with large
         buffer size.
@@ -143,6 +145,7 @@
                        extras=proto)
 
     @BluetoothBaseTest.bt_test_wrap
+    @test_tracker_info(uuid='5472fe33-891e-4fa1-ba84-78fc6f6a2327')
     def test_bluetooth_throughput_medium_buffer(self):
         """Tests the throughput over a series of data transfers with medium
         buffer size.
@@ -177,6 +180,7 @@
                        extras=proto)
 
     @BluetoothBaseTest.bt_test_wrap
+    @test_tracker_info(uuid='97589280-cefa-4ae4-b3fd-94ec9c1f4104')
     def test_bluetooth_throughput_small_buffer(self):
         """Tests the throughput over a series of data transfers with small
         buffer size.
diff --git a/acts/tests/google/instrumentation/power/camera/CapturePhotosTest.py b/acts/tests/google/instrumentation/power/camera/CapturePhotosTest.py
new file mode 100644
index 0000000..e6f2626
--- /dev/null
+++ b/acts/tests/google/instrumentation/power/camera/CapturePhotosTest.py
@@ -0,0 +1,46 @@
+#!/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.test_utils.instrumentation.power import instrumentation_power_test
+from acts.test_utils.instrumentation.device.apps.dismiss_dialogs import \
+    DialogDismissalUtil
+
+
+class ImageCaptureTest(instrumentation_power_test.InstrumentationPowerTest):
+    """
+    Test class for running instrumentation test CameraTests#testImageCapture.
+    """
+
+    def _prepare_device(self):
+        super()._prepare_device()
+        self.mode_airplane()
+        self.base_device_configuration()
+        self._dialog_util = DialogDismissalUtil(
+            self.ad_dut,
+            self._instrumentation_config.get_file('dismiss_dialogs_apk')
+        )
+        self._dialog_util.dismiss_dialogs('GoogleCamera')
+
+    def _cleanup_device(self):
+        self._dialog_util.close()
+        super()._cleanup_device()
+
+    def test_capture_photos(self):
+        """Measures power during photo capture."""
+        self.run_and_measure(
+            'com.google.android.platform.powertests.CameraTests',
+            'testImageCapture', req_params=['hdr_mode'])
+        self.validate_power_results()
diff --git a/acts/tests/google/instrumentation/power/idle/DisplayAlwaysOnTest.py b/acts/tests/google/instrumentation/power/idle/DisplayAlwaysOnTest.py
new file mode 100644
index 0000000..b891b0c
--- /dev/null
+++ b/acts/tests/google/instrumentation/power/idle/DisplayAlwaysOnTest.py
@@ -0,0 +1,39 @@
+#!/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.test_utils.instrumentation.power import instrumentation_power_test
+from acts.test_utils.instrumentation.device.command.adb_commands import common
+
+
+class DisplayAlwaysOnTest(instrumentation_power_test.InstrumentationPowerTest):
+    """Test class for running instrumentation test DisplayAlwaysOn."""
+
+    def _prepare_device(self):
+        super()._prepare_device()
+        self.base_device_configuration()
+        self.adb_run(common.doze_mode.toggle(True))
+        self.adb_run(common.doze_always_on.toggle(True))
+        self.adb_run(common.disable_sensors)
+
+    def test_display_always_on(self):
+        """Measures power when the device is rock bottom state plus display
+        always on (known as doze mode)."""
+
+        self.run_and_measure(
+            'com.google.android.platform.powertests.IdleTestCase',
+            'testIdleScreenOff')
+
+        self.validate_power_results()
diff --git a/acts/tests/google/instrumentation/power/idle/PartialWakeLockTest.py b/acts/tests/google/instrumentation/power/idle/PartialWakeLockTest.py
new file mode 100644
index 0000000..973c0f9
--- /dev/null
+++ b/acts/tests/google/instrumentation/power/idle/PartialWakeLockTest.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.
+
+from acts.test_utils.instrumentation.power import instrumentation_power_test
+
+
+class PartialWakeLockTest(instrumentation_power_test.InstrumentationPowerTest):
+    """Test class for running instrumentation test PartialWakeLock."""
+
+    def _prepare_device(self):
+        super()._prepare_device()
+        self.base_device_configuration()
+
+    def test_partial_wake_lock(self):
+        """Measures power when the device is idle with a partial wake lock."""
+        self.run_and_measure(
+            'com.google.android.platform.powertests.IdleTestCase',
+            'testPartialWakelock')
+        self.validate_power_results()
diff --git a/acts/tests/google/instrumentation/power/idle/RockBottomTest.py b/acts/tests/google/instrumentation/power/idle/RockBottomTest.py
new file mode 100644
index 0000000..43dd9f1
--- /dev/null
+++ b/acts/tests/google/instrumentation/power/idle/RockBottomTest.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.
+
+from acts.test_utils.instrumentation.power import instrumentation_power_test
+
+
+class RockBottomTest(instrumentation_power_test.InstrumentationPowerTest):
+    """Test class for running instrumentation test RockBottom."""
+
+    def _prepare_device(self):
+        super()._prepare_device()
+        self.base_device_configuration()
+
+    def test_rock_bottom(self):
+        """Measures power when the device is in a rock bottom state."""
+        self.run_and_measure(
+            'com.google.android.platform.powertests.IdleTestCase',
+            'testIdleScreenOff')
+        self.validate_power_results()
diff --git a/acts/tests/google/instrumentation/power/media/VideoPlaybackTest.py b/acts/tests/google/instrumentation/power/media/VideoPlaybackTest.py
new file mode 100644
index 0000000..f4364f2
--- /dev/null
+++ b/acts/tests/google/instrumentation/power/media/VideoPlaybackTest.py
@@ -0,0 +1,43 @@
+#!/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.test_utils.instrumentation.power import instrumentation_power_test
+
+BIG_FILE_PUSH_TIMEOUT = 600
+
+
+class VideoPlaybackTest(
+    instrumentation_power_test.InstrumentationPowerTest):
+    """Test class for running instrumentation tests
+    VideoPlaybackHighBitRateTest."""
+
+    def _prepare_device(self):
+        super()._prepare_device()
+        self.base_device_configuration()
+
+    def test_playback_high_bit_rate(self):
+        """Measures power when the device is in a rock bottom state."""
+        video_location = self.push_to_external_storage(
+            self._instrumentation_config.get_file('high_bit_rate_video'),
+            timeout=BIG_FILE_PUSH_TIMEOUT)
+        self.trigger_scan_on_external_storage()
+
+        self.run_and_measure(
+            'com.google.android.platform.powertests.PhotosTests',
+            'testVideoPlaybackThroughIntent',
+            extra_params=[('video_file_path', video_location)])
+
+        self.validate_power_results()
diff --git a/acts/tests/google/wifi/WifiAutoUpdateTest.py b/acts/tests/google/wifi/WifiAutoUpdateTest.py
index 33369a2..400ffe8 100755
--- a/acts/tests/google/wifi/WifiAutoUpdateTest.py
+++ b/acts/tests/google/wifi/WifiAutoUpdateTest.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python3.4
+#   !/usr/bin/env python3.4
 #
 #   Copyright 2017 - The Android Open Source Project
 #
@@ -14,22 +14,21 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import itertools
-import pprint
-import queue
-import time
-
-import acts.base_test
-import acts.signals as signals
-import acts.test_utils.wifi.wifi_test_utils as wutils
-import acts.utils
-
+import re
 from acts import asserts
+from acts.controllers.android_device import SL4A_APK_NAME
 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
+import acts.utils as utils
 
 WifiEnums = wutils.WifiEnums
+SSID = WifiEnums.SSID_KEY
+PWD = WifiEnums.PWD_KEY
+NETID = WifiEnums.NETID_KEY
 # Default timeout used for reboot, toggle WiFi and Airplane mode,
 # for the system to settle down after the operation.
 DEFAULT_TIMEOUT = 10
@@ -51,50 +50,52 @@
         self.tests = (
             "test_check_wifi_state_after_au",
             "test_verify_networks_after_au",
+            "test_configstore_after_au",
+            "test_mac_randomization_after_au",
+            "test_wifi_hotspot_5g_psk_after_au",
             "test_all_networks_connectable_after_au",
-            "test_connection_to_new_networks",
+            "test_connect_to_network_suggestion_after_au",
             "test_check_wifi_toggling_after_au",
+            "test_connection_to_new_networks",
             "test_reset_wifi_after_au")
 
     def setup_class(self):
         super(WifiAutoUpdateTest, self).setup_class()
         ota_updater.initialize(self.user_params, self.android_devices)
         self.dut = self.android_devices[0]
+        self.dut_client = self.android_devices[1]
         wutils.wifi_test_device_init(self.dut)
-        req_params = []
-        opt_param = [
-            "open_network", "reference_networks", "iperf_server_address"
-        ]
-        self.unpack_userparams(
-            req_param_names=req_params, opt_param_names=opt_param)
-
-        if "AccessPoint" in self.user_params:
-            self.legacy_configure_ap_and_start()
-
-        asserts.assert_true(
-            len(self.reference_networks) > 0,
-            "Need at least two reference network with psk.")
-        asserts.assert_true(
-            len(self.open_network) > 0,
-            "Need at least two open network with psk.")
         wutils.wifi_toggle_state(self.dut, True)
 
+        # configure APs
+        self.legacy_configure_ap_and_start(wpa_network=True)
+        self.wpapsk_2g = self.reference_networks[0]["2g"]
+        self.wpapsk_5g = self.reference_networks[0]["5g"]
+        self.open_2g = self.open_network[0]["2g"]
+        self.open_5g = self.open_network[0]["5g"]
+
+        # saved & connected networks, network suggestions
+        # and new networks
+        self.saved_networks = [self.open_2g]
+        self.network_suggestions = [self.wpapsk_5g]
+        self.connected_networks = [self.wpapsk_2g, self.open_5g]
+        self.new_networks = [self.reference_networks[1]["2g"],
+                             self.open_network[1]["5g"]]
+
+        # add pre ota upgrade configuration
         self.wifi_config_list = []
-
-        # Disabling WiFi setup before OTA for debugging.
-        # Setup WiFi and add few open and wpa networks before OTA.
-        # self.add_network_and_enable(self.open_network[0]['2g'])
-        # self.add_network_and_enable(self.reference_networks[0]['5g'])
-
-        # Add few dummy networks to the list.
-        # self.add_and_enable_dummy_networks()
+        self.pre_default_mac = {}
+        self.pre_random_mac = {}
+        self.pst_default_mac = {}
+        self.pst_random_mac = {}
+        self.add_pre_update_configuration()
 
         # Run OTA below, if ota fails then abort all tests.
         try:
             ota_updater.update(self.dut)
-        except Exception as err:
+        except Exception as e:
             raise signals.TestAbortClass(
-                "Failed up apply OTA update. Aborting tests")
+                "Failed up apply OTA update. Aborting tests: %s" % e)
 
     def setup_test(self):
         self.dut.droid.wakeLockAcquireBright()
@@ -113,45 +114,111 @@
             del self.user_params["reference_networks"]
             del self.user_params["open_network"]
 
-    """Helper Functions"""
+    ### Helper Methods
+
+    def add_pre_update_configuration(self):
+        self.add_network_suggestions(self.network_suggestions)
+        self.add_network_and_enable(self.saved_networks[0])
+        self.add_wifi_hotspot()
+        self.connect_to_multiple_networks(self.connected_networks)
+
+    def add_wifi_hotspot(self):
+        self.wifi_hotspot = {"SSID": "hotspot_%s" % utils.rand_ascii_str(6),
+                             "password": "pass_%s" % utils.rand_ascii_str(6)}
+        wutils.save_wifi_soft_ap_config(self.dut,
+                                        self.wifi_hotspot,
+                                        WIFI_CONFIG_APBAND_5G)
+
+    def verify_wifi_hotspot(self):
+        """Verify wifi tethering."""
+        wutils.start_wifi_tethering_saved_config(self.dut)
+        wutils.connect_to_wifi_network(self.dut_client,
+                                       self.wifi_hotspot,
+                                       check_connectivity=False)
+        wutils.stop_wifi_tethering(self.dut)
+
+    def connect_to_multiple_networks(self, networks):
+        """Connect to a list of wifi networks.
+
+        Args:
+            networks : list of wifi networks.
+        """
+        self.log.info("Connect to multiple wifi networks")
+        for network in networks:
+            ssid = network[SSID]
+            wutils.start_wifi_connection_scan_and_ensure_network_found(
+                self.dut, ssid)
+            wutils.wifi_connect(self.dut, network, num_of_tries=6)
+            self.wifi_config_list.append(network)
+            self.pre_default_mac[network[SSID]] = self.get_sta_mac_address()
+            self.pre_random_mac[network[SSID]] = \
+                self.dut.droid.wifigetRandomizedMacAddress(network)
+
+    def get_sta_mac_address(self):
+        """Gets the current MAC address being used for client mode."""
+        out = self.dut.adb.shell("ifconfig wlan0")
+        res = re.match(".* HWaddr (\S+).*", out, re.S)
+        return res.group(1)
+
+    def add_network_suggestions(self, network_suggestions):
+        """Add wifi network suggestions to DUT.
+
+        Args:
+            network_suggestions : suggestions to add.
+        """
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions(network_suggestions),
+            "Failed to add suggestions")
+
+        # Enable suggestions by the app.
+        self.dut.log.debug("Enabling suggestions from test")
+        self.dut.adb.shell(
+            "cmd wifi network-suggestions-set-user-approved %s yes" % \
+                SL4A_APK_NAME)
+
+    def remove_suggestions_and_ensure_no_connection(self,
+                                                    network_suggestions,
+                                                    expected_ssid):
+        """Remove network suggestions.
+
+        Args:
+            network_suggestions : suggestions to remove.
+            expected_ssid : SSID to verify that DUT is not connected.
+        """
+        # remove network suggestion and verify disconnect
+        self.dut.log.info("Removing network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiRemoveNetworkSuggestions(network_suggestions),
+            "Failed to remove suggestions")
+
+        wutils.wait_for_disconnect(self.dut)
+        self.dut.ed.clear_all_events()
+
+        # Now ensure that we didn't connect back.
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut,
+                                    expected_ssid,
+                                    assert_on_fail=False),
+            "Device should not connect back")
 
     def add_network_and_enable(self, network):
         """Add a network and enable it.
 
         Args:
             network : Network details for the network to be added.
-
         """
+        self.log.info("Add a wifi network and enable it")
         ret = self.dut.droid.wifiAddNetwork(network)
         asserts.assert_true(ret != -1, "Add network %r failed" % network)
-        self.wifi_config_list.append({
-                WifiEnums.SSID_KEY: network[WifiEnums.SSID_KEY],
-                WifiEnums.NETID_KEY: ret})
+        self.wifi_config_list.append({SSID: network[SSID], NETID: ret})
         self.dut.droid.wifiEnableNetwork(ret, 0)
 
-    def add_and_enable_dummy_networks(self, num_networks=5):
-        """Add some dummy networks to the device and enable them.
-
-        Args:
-            num_networks: Number of networks to add.
-        """
-        ssid_name_base = "dummy_network_"
-        for i in range(0, num_networks):
-            network = {}
-            network[WifiEnums.SSID_KEY] = ssid_name_base + str(i)
-            network[WifiEnums.PWD_KEY] = "dummynet_password"
-            self.add_network_and_enable(network)
-
     def check_networks_after_autoupdate(self, networks):
-        """Verify that all previously configured networks are presistent after
-           reboot.
+        """Verify that all previously configured networks are persistent.
 
         Args:
             networks: List of network dicts.
-
-        Return:
-            None. Raises TestFailure.
-
         """
         network_info = self.dut.droid.wifiGetConfiguredNetworks()
         if len(network_info) != len(networks):
@@ -160,21 +227,35 @@
                 "don't match. \nBefore reboot = %s \n After reboot = %s" %
                 (networks, network_info))
             raise signals.TestFailure(msg)
-        current_count = 0
+
         # For each network, check if it exists in configured list after Auto-
         # update.
         for network in networks:
-            exists = wutils.match_networks({
-                WifiEnums.SSID_KEY: network[WifiEnums.SSID_KEY]
-            }, network_info)
-            if not len(exists):
+            exists = wutils.match_networks({SSID: network[SSID]}, network_info)
+            if not exists:
                 raise signals.TestFailure("%s network is not present in the"
                                           " configured list after Auto-update" %
-                                          network[WifiEnums.SSID_KEY])
+                                          network[SSID])
             # Get the new network id for each network after reboot.
-            network[WifiEnums.NETID_KEY] = exists[0]['networkId']
+            network[NETID] = exists[0]["networkId"]
 
-    """Tests"""
+    def get_enabled_network(self, network1, network2):
+        """Check network status and return currently unconnected network.
+
+        Args:
+            network1: dict representing a network.
+            network2: dict representing a network.
+
+        Returns:
+            Network dict of the unconnected network.
+        """
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        enabled = network1
+        if wifi_info[SSID] == network1[SSID]:
+            enabled = network2
+        return enabled
+
+    ### Tests
 
     @test_tracker_info(uuid="9ff1f01e-e5ff-408b-9a95-29e87a2df2d8")
     def test_check_wifi_state_after_au(self):
@@ -192,6 +273,78 @@
         """
         self.check_networks_after_autoupdate(self.wifi_config_list)
 
+    @test_tracker_info(uuid="799e83c2-305d-4510-921e-dac3c0dbb6c5")
+    def test_configstore_after_au(self):
+        """Verify DUT automatically connects to wifi networks after ota.
+
+           Steps:
+               1. Connect to two wifi networks pre ota.
+               2. Verify DUT automatically connects to 1 after ota.
+               3. Re-connect to the other wifi network.
+        """
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        self.pst_default_mac[wifi_info[SSID]] = self.get_sta_mac_address()
+        self.pst_random_mac[wifi_info[SSID]] = \
+            self.dut.droid.wifigetRandomizedMacAddress(wifi_info)
+        reconnect_to = self.get_enabled_network(self.wifi_config_list[1],
+                                                self.wifi_config_list[2])
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, reconnect_to[SSID])
+        wutils.wifi_connect_by_id(self.dut, reconnect_to[NETID])
+        connect_data = self.dut.droid.wifiGetConnectionInfo()
+        connect_ssid = connect_data[SSID]
+        self.log.info("Expected SSID = %s" % reconnect_to[SSID])
+        self.log.info("Connected SSID = %s" % connect_ssid)
+        if connect_ssid != reconnect_to[SSID]:
+            raise signals.TestFailure(
+                "Device failed to reconnect to the correct"
+                " network after reboot.")
+        self.pst_default_mac[wifi_info[SSID]] = self.get_sta_mac_address()
+        self.pst_random_mac[wifi_info[SSID]] = \
+            self.dut.droid.wifigetRandomizedMacAddress(wifi_info)
+
+        for network in self.connected_networks:
+            wutils.wifi_forget_network(self.dut, network[SSID])
+
+    @test_tracker_info(uuid="e26d0ed9-9457-4a95-a962-4d43b0032bac")
+    def test_mac_randomization_after_au(self):
+        """Verify randomized MAC addrs are persistent after ota.
+
+           Steps:
+               1. Reconnect to the wifi networks configured pre ota.
+               2. Get the randomized MAC addrs.
+        """
+        for ssid, mac in self.pst_random_mac.items():
+            asserts.assert_true(
+                self.pre_random_mac[ssid] == mac,
+                "MAC addr of %s is %s after ota. Expected %s" %
+                (ssid, mac, self.pre_random_mac[ssid]))
+
+    @test_tracker_info(uuid="f68a65e6-97b7-4746-bad8-4c206551d87e")
+    def test_wifi_hotspot_5g_psk_after_au(self):
+        """Verify hotspot after ota upgrade.
+
+           Steps:
+               1. Start wifi hotspot on the saved config.
+               2. Verify DUT client connects to it.
+        """
+        self.verify_wifi_hotspot()
+
+    @test_tracker_info(uuid="21f91372-88a6-44b9-a4e8-d4664823dffb")
+    def test_connect_to_network_suggestion_after_au(self):
+        """Verify connection to network suggestion after ota.
+
+           Steps:
+               1. DUT has network suggestion added before OTA.
+               2. Wait for the device to connect to it.
+               3. Remove the suggestions and ensure the device does not
+                  connect back.
+        """
+        wutils.start_wifi_connection_scan_and_return_status(self.dut)
+        wutils.wait_for_connect(self.dut, self.network_suggestions[0][SSID])
+        self.remove_suggestions_and_ensure_no_connection(
+            self.network_suggestions, self.network_suggestions[0][SSID])
+
     @test_tracker_info(uuid="b8e47a4f-62fe-4a0e-b999-27ae1ebf4d19")
     def test_connection_to_new_networks(self):
         """Check if we can connect to new networks after Auto-update.
@@ -201,14 +354,11 @@
                2. Connect to an open network.
                3. Forget ntworks added in 1 & 2.
                TODO: (@bmahadev) Add WEP network once it's ready.
-
         """
-        wutils.connect_to_wifi_network(self.dut, self.open_network[0]['5g'])
-        wutils.connect_to_wifi_network(self.dut, self.reference_networks[0]['2g'])
-        wutils.wifi_forget_network(self.dut,
-                self.reference_networks[0]['2g'][WifiEnums.SSID_KEY])
-        wutils.wifi_forget_network(self.dut,
-                self.open_network[0]['5g'][WifiEnums.SSID_KEY])
+        for network in self.new_networks:
+            wutils.connect_to_wifi_network(self.dut, network)
+        for network in self.new_networks:
+            wutils.wifi_forget_network(self.dut, network[SSID])
 
     @test_tracker_info(uuid="1d8309e4-d5a2-4f48-ba3b-895a58c9bf3a")
     def test_all_networks_connectable_after_au(self):
@@ -218,15 +368,14 @@
                1. Connect to previously added PSK network using network id.
                2. Connect to previously added open network using network id.
                TODO: (@bmahadev) Add WEP network once it's ready.
-
         """
-        for network in self.wifi_config_list:
-            if 'dummy' not in network[WifiEnums.SSID_KEY]:
-                if not wutils.connect_to_wifi_network_with_id(self.dut,
-                        network[WifiEnums.NETID_KEY],
-                        network[WifiEnums.SSID_KEY]):
-                    raise signals.TestFailure("Failed to connect to %s after \
-                            Auto-update" % network[WifiEnums.SSID_KEY])
+        network = self.wifi_config_list[0]
+        if not wutils.connect_to_wifi_network_with_id(self.dut,
+                                                      network[NETID],
+                                                      network[SSID]):
+            raise signals.TestFailure("Failed to connect to %s after OTA" %
+                                      network[SSID])
+        wutils.wifi_forget_network(self.dut, network[SSID])
 
     @test_tracker_info(uuid="05671859-38b1-4dbf-930c-18048971d075")
     def test_check_wifi_toggling_after_au(self):
diff --git a/acts/tests/google/wifi/WifiDppTest.py b/acts/tests/google/wifi/WifiDppTest.py
index 42591b0..77471a8 100644
--- a/acts/tests/google/wifi/WifiDppTest.py
+++ b/acts/tests/google/wifi/WifiDppTest.py
@@ -49,19 +49,33 @@
   DPP_TEST_MESSAGE_TYPE = "Type"
   DPP_TEST_MESSAGE_STATUS = "Status"
   DPP_TEST_MESSAGE_NETWORK_ID = "NetworkId"
+  DPP_TEST_MESSAGE_FAILURE_SSID = "onFailureSsid"
+  DPP_TEST_MESSAGE_FAILURE_CHANNEL_LIST = "onFailureChannelList"
+  DPP_TEST_MESSAGE_FAILURE_BAND_LIST = "onFailureBandList"
 
   DPP_TEST_NETWORK_ROLE_STA = "sta"
   DPP_TEST_NETWORK_ROLE_AP = "ap"
 
+  DPP_TEST_PARAM_SSID = "SSID"
+  DPP_TEST_PARAM_PASSWORD = "Password"
+
   WPA_SUPPLICANT_SECURITY_SAE = "sae"
   WPA_SUPPLICANT_SECURITY_PSK = "psk"
 
+  DPP_EVENT_PROGRESS_AUTHENTICATION_SUCCESS = 0
+  DPP_EVENT_PROGRESS_RESPONSE_PENDING = 1
+  DPP_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2
+  DPP_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3
+
+  DPP_EVENT_SUCCESS_CONFIGURATION_SENT = 0
+  DPP_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1
+
   def setup_class(self):
     """ Sets up the required dependencies from the config file and configures the device for
         WifiService API tests.
 
         Returns:
-          True is successfully configured the requirements for testig.
+          True is successfully configured the requirements for testing.
     """
 
     # Device 0 is under test. Device 1 performs the responder role
@@ -75,6 +89,41 @@
     utils.require_sl4a((self.dut,))
     utils.sync_device_time(self.dut)
 
+    req_params = ["dpp_r1_test_only"]
+    opt_param = ["wifi_psk_network", "wifi_sae_network"]
+    self.unpack_userparams(
+      req_param_names=req_params, opt_param_names=opt_param)
+
+    self.dut.log.info(
+      "Parsed configs: %s %s" % (self.wifi_psk_network, self.wifi_sae_network))
+
+    # Set up the networks. This is optional. In case these networks are not initialized,
+    # the script will create random ones. However, a real AP is required to pass DPP R2 test.
+    # Most efficient setup would be to use an AP in WPA2/WPA3 transition mode.
+    if self.DPP_TEST_PARAM_SSID in self.wifi_psk_network:
+      self.psk_network_ssid = self.wifi_psk_network[self.DPP_TEST_PARAM_SSID]
+    else:
+      self.psk_network_ssid = None
+
+    if self.DPP_TEST_PARAM_PASSWORD in self.wifi_psk_network:
+      self.psk_network_password = self.wifi_psk_network[self.DPP_TEST_PARAM_PASSWORD]
+    else:
+      self.psk_network_ssid = None
+
+    if self.DPP_TEST_PARAM_SSID in self.wifi_sae_network:
+      self.sae_network_ssid = self.wifi_sae_network[self.DPP_TEST_PARAM_SSID]
+    else:
+      self.sae_network_ssid = None
+
+    if self.DPP_TEST_PARAM_PASSWORD in self.wifi_sae_network:
+      self.sae_network_password = self.wifi_sae_network[self.DPP_TEST_PARAM_PASSWORD]
+    else:
+      self.sae_network_ssid = None
+
+    if self.dpp_r1_test_only == "False":
+      if not self.wifi_psk_network or not self.wifi_sae_network:
+        asserts.fail("Must specify wifi_psk_network and wifi_sae_network for DPP R2 tests")
+
     # Enable verbose logging on the dut
     self.dut.droid.wifiEnableVerboseLogging(1)
     asserts.assert_true(self.dut.droid.wifiGetVerboseLoggingLevel() == 1,
@@ -87,18 +136,42 @@
     self.dut.take_bug_report(test_name, begin_time)
     self.dut.cat_adb_log(test_name, begin_time)
 
-  def create_and_save_wifi_network_config(self, security):
+  def create_and_save_wifi_network_config(self, security, random_network=False,
+                                          r2_auth_error=False):
     """ Create a config with random SSID and password.
 
             Args:
                security: Security type: PSK or SAE
+               random_network: A boolean that indicates if to create a random network
+               r2_auth_error: A boolean that indicates if to create a network with a bad password
 
             Returns:
                A tuple with the config and networkId for the newly created and
                saved network.
     """
-    config_ssid = self.DPP_TEST_SSID_PREFIX + utils.rand_ascii_str(8)
-    config_password = utils.rand_ascii_str(8)
+    if security == self.DPP_TEST_SECURITY_PSK:
+      if self.psk_network_ssid is None or self.psk_network_password is None or \
+              random_network is True:
+        config_ssid = self.DPP_TEST_SSID_PREFIX + utils.rand_ascii_str(8)
+        config_password = utils.rand_ascii_str(8)
+      else:
+        config_ssid = self.psk_network_ssid
+        if r2_auth_error:
+          config_password = utils.rand_ascii_str(8)
+        else:
+          config_password = self.psk_network_password
+    else:
+      if self.sae_network_ssid is None or self.sae_network_password is None or \
+              random_network is True:
+        config_ssid = self.DPP_TEST_SSID_PREFIX + utils.rand_ascii_str(8)
+        config_password = utils.rand_ascii_str(8)
+      else:
+        config_ssid = self.sae_network_ssid
+        if r2_auth_error:
+          config_password = utils.rand_ascii_str(8)
+        else:
+          config_password = self.sae_network_password
+
     self.dut.log.info(
         "creating config: %s %s %s" % (config_ssid, config_password, security))
     config = {
@@ -231,9 +304,9 @@
     cmd = "wpa_cli DPP_BOOTSTRAP_REMOVE %s" % uri_id
     result = device.adb.shell(cmd)
 
-    if "FAIL" in result:
-      asserts.fail("del_uri: Failed to delete URI. Command used: %s" % cmd)
-    device.log.info("Deleted URI, id = %s" % uri_id)
+    # If URI was already flushed, ignore a failure here
+    if "FAIL" not in result:
+      device.log.info("Deleted URI, id = %s" % uri_id)
 
   def start_responder_configurator(self,
                                    device,
@@ -269,15 +342,23 @@
         self.log.warning("SAE not supported on device! reverting to PSK")
         security = self.DPP_TEST_SECURITY_PSK_PASSPHRASE
 
+    ssid = self.DPP_TEST_SSID_PREFIX + utils.rand_ascii_str(8)
+    password = utils.rand_ascii_str(8)
+
     if security == self.DPP_TEST_SECURITY_SAE:
       conf += self.WPA_SUPPLICANT_SECURITY_SAE
+      if not self.sae_network_ssid is None:
+        ssid = self.sae_network_ssid
+        password = self.sae_network_password
     elif security == self.DPP_TEST_SECURITY_PSK_PASSPHRASE:
       conf += self.WPA_SUPPLICANT_SECURITY_PSK
+      if not self.psk_network_ssid is None:
+        ssid = self.psk_network_ssid
+        password = self.psk_network_password
     else:
       conf += self.WPA_SUPPLICANT_SECURITY_PSK
       use_psk = True
 
-    ssid = self.DPP_TEST_SSID_PREFIX + utils.rand_ascii_str(8)
     self.log.debug("SSID = %s" % ssid)
 
     ssid_encoded = binascii.hexlify(ssid.encode()).decode()
@@ -291,7 +372,6 @@
         psk_encoded = psk
       self.log.debug("PSK = %s" % psk)
     else:
-      password = utils.rand_ascii_str(8)
       if not invalid_config:
         password_encoded = binascii.b2a_hex(password.encode()).decode()
       else:
@@ -356,9 +436,11 @@
     if "FAIL" in result:
       asserts.fail("start_responder_enrollee: Failure. Command used: %s" % cmd)
 
+    device.adb.shell("wpa_cli set dpp_config_processing 2")
+
     device.log.info("Started responder in enrollee mode")
 
-  def stop_responder(self, device):
+  def stop_responder(self, device, flush=False):
     """Stop responder on helper device
 
        Args:
@@ -368,7 +450,9 @@
     if "FAIL" in result:
       asserts.fail("stop_responder: Failed to stop responder")
     device.adb.shell("wpa_cli set dpp_configurator_params")
-
+    device.adb.shell("wpa_cli set dpp_config_processing 0")
+    if flush:
+      device.adb.shell("wpa_cli flush")
     device.log.info("Stopped responder")
 
   def start_dpp_as_initiator_configurator(self,
@@ -379,7 +463,9 @@
                                           net_role=DPP_TEST_NETWORK_ROLE_STA,
                                           cause_timeout=False,
                                           fail_authentication=False,
-                                          invalid_uri=False):
+                                          invalid_uri=False,
+                                          r2_no_ap=False,
+                                          r2_auth_error=False):
     """ Test Easy Connect (DPP) as initiator configurator.
 
                 1. Enable wifi, if needed
@@ -406,13 +492,16 @@
             fail_authentication: Fail authentication by corrupting the
               responder's key
             invalid_uri: Use garbage string instead of a URI
+            r2_no_ap: Indicates if to test DPP R2 no AP failure event
+            r2_auth_error: Indicates if to test DPP R2 authentication failure
     """
     if not self.dut.droid.wifiIsEasyConnectSupported():
       self.log.warning("Easy Connect is not supported on device!")
       return
 
     wutils.wifi_toggle_state(self.dut, True)
-    test_network_id = self.create_and_save_wifi_network_config(security)
+    test_network_id = self.create_and_save_wifi_network_config(security, random_network=r2_no_ap,
+                                                               r2_auth_error=r2_auth_error)
 
     if use_mac:
       mac = autils.get_mac_addr(self.helper_dev, "wlan0")
@@ -454,34 +543,57 @@
               == self.DPP_TEST_EVENT_ENROLLEE_SUCCESS:
         asserts.fail("DPP failure, unexpected result!")
         break
-      if dut_event[self.DPP_TEST_EVENT_DATA][
-          self
-          .DPP_TEST_MESSAGE_TYPE] == self.DPP_TEST_EVENT_CONFIGURATOR_SUCCESS:
-        if cause_timeout or fail_authentication or invalid_uri:
+      if dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_TYPE] \
+              == self.DPP_TEST_EVENT_CONFIGURATOR_SUCCESS:
+        if cause_timeout or fail_authentication or invalid_uri or r2_no_ap or r2_auth_error:
           asserts.fail(
               "Unexpected DPP success, status code: %s" %
               dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS])
         else:
           val = dut_event[self.DPP_TEST_EVENT_DATA][
               self.DPP_TEST_MESSAGE_STATUS]
-          if val == 0:
+          if val == self.DPP_EVENT_SUCCESS_CONFIGURATION_SENT:
             self.dut.log.info("DPP Configuration sent success")
+          if val == self.DPP_EVENT_SUCCESS_CONFIGURATION_APPLIED:
+            self.dut.log.info("DPP Configuration applied by enrollee")
         break
-      if dut_event[self.DPP_TEST_EVENT_DATA][
-          self.DPP_TEST_MESSAGE_TYPE] == self.DPP_TEST_EVENT_PROGRESS:
+      if dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_TYPE] \
+              == self.DPP_TEST_EVENT_PROGRESS:
         self.dut.log.info("DPP progress event")
         val = dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS]
-        if val == 0:
+        if val == self.DPP_EVENT_PROGRESS_AUTHENTICATION_SUCCESS:
           self.dut.log.info("DPP Authentication success")
-        elif val == 1:
+        elif val == self.DPP_EVENT_PROGRESS_RESPONSE_PENDING:
           self.dut.log.info("DPP Response pending")
+        elif val == self.DPP_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE:
+          self.dut.log.info("DPP Configuration sent, waiting response")
+        elif val == self.DPP_EVENT_PROGRESS_CONFIGURATION_ACCEPTED:
+          self.dut.log.info("Configuration accepted")
         continue
       if dut_event[self.DPP_TEST_EVENT_DATA][
           self.DPP_TEST_MESSAGE_TYPE] == self.DPP_TEST_EVENT_FAILURE:
-        if cause_timeout or fail_authentication or invalid_uri:
+        if cause_timeout or fail_authentication or invalid_uri or r2_no_ap or r2_auth_error:
           self.dut.log.info(
               "Error %s occurred, as expected" %
               dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_STATUS])
+          if r2_no_ap or r2_auth_error:
+            if not dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_SSID]:
+              asserts.fail("Expected SSID value in DPP R2 onFailure event")
+            self.dut.log.info(
+              "Enrollee searched for SSID %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_SSID])
+          if r2_no_ap:
+            if not dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_CHANNEL_LIST]:
+              asserts.fail("Expected Channel list value in DPP R2 onFailure event")
+            self.dut.log.info(
+              "Enrollee scanned the following channels: %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_CHANNEL_LIST])
+          if r2_no_ap or r2_auth_error:
+            if not dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_BAND_LIST]:
+              asserts.fail("Expected Band Support list value in DPP R2 onFailure event")
+            self.dut.log.info(
+              "Enrollee supports the following bands: %s" %
+              dut_event[self.DPP_TEST_EVENT_DATA][self.DPP_TEST_MESSAGE_FAILURE_BAND_LIST])
         else:
           asserts.fail(
               "DPP failure, status code: %s" %
@@ -492,7 +604,7 @@
     self.dut.ed.clear_all_events()
 
     # Stop responder
-    self.stop_responder(self.helper_dev)
+    self.stop_responder(self.helper_dev, flush=True)
 
     if not invalid_uri:
       # Delete URI
@@ -607,7 +719,7 @@
     self.dut.ed.clear_all_events()
 
     # Stop responder
-    self.stop_responder(self.helper_dev)
+    self.stop_responder(self.helper_dev, flush=True)
 
     # Delete URI
     self.del_uri(self.helper_dev, uri_id)
@@ -792,4 +904,16 @@
         use_mac=True,
         cause_timeout=True)
 
-  """ Tests End """
+  def test_dpp_as_initiator_configurator_fail_r2_no_ap(self):
+    asserts.skip_if(self.dpp_r1_test_only == "True",
+                    "DPP R1 test, skipping this test for DPP R2 only")
+    self.start_dpp_as_initiator_configurator(
+      security=self.DPP_TEST_SECURITY_PSK, use_mac=True, r2_no_ap=True)
+
+  def test_dpp_as_initiator_configurator_fail_r2_auth_error(self):
+    asserts.skip_if(self.dpp_r1_test_only == "True",
+                    "DPP R1 test, skipping this test for DPP R2 only")
+    self.start_dpp_as_initiator_configurator(
+      security=self.DPP_TEST_SECURITY_PSK, use_mac=True, r2_auth_error=True)
+
+""" Tests End """
diff --git a/acts/tests/google/wifi/WifiIOTTest.py b/acts/tests/google/wifi/WifiIOTTest.py
deleted file mode 100644
index 1daf346..0000000
--- a/acts/tests/google/wifi/WifiIOTTest.py
+++ /dev/null
@@ -1,376 +0,0 @@
-#!/usr/bin/env python3.4
-#
-#   Copyright 2017 - The Android Open Source Project
-#
-#   Licensed under the Apache License, Version 2.0 (the "License");
-#   you may not use this file except in compliance with the License.
-#   You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-#   Unless required by applicable law or agreed to in writing, software
-#   distributed under the License is distributed on an "AS IS" BASIS,
-#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#   See the License for the specific language governing permissions and
-#   limitations under the License.
-
-import itertools
-import pprint
-import time
-
-import acts.signals
-import acts.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
-
-WifiEnums = wutils.WifiEnums
-
-
-class WifiIOTTest(WifiBaseTest):
-    """ Tests for wifi IOT
-
-        Test Bed Requirement:
-          * One Android device
-          * Wi-Fi IOT networks visible to the device
-    """
-
-    def setup_class(self):
-        super().setup_class()
-
-        self.dut = self.android_devices[0]
-        wutils.wifi_test_device_init(self.dut)
-
-        req_params = [ "iot_networks", ]
-        opt_params = [ "open_network", "iperf_server_address" ]
-        self.unpack_userparams(req_param_names=req_params,
-                               opt_param_names=opt_params)
-
-        asserts.assert_true(
-            len(self.iot_networks) > 0,
-            "Need at least one iot network with psk.")
-
-        if getattr(self, 'open_network', False):
-            self.iot_networks.append(self.open_network)
-
-        wutils.wifi_toggle_state(self.dut, True)
-        if "iperf_server_address" in self.user_params:
-            self.iperf_server = self.iperf_servers[0]
-            self.iperf_server.start()
-
-        # create hashmap for testcase name and SSIDs
-        self.iot_test_prefix = "test_iot_connection_to_"
-        self.ssid_map = {}
-        for network in self.iot_networks:
-            SSID = network['SSID'].replace('-','_')
-            self.ssid_map[SSID] = network
-
-    def setup_test(self):
-        self.dut.droid.wakeLockAcquireBright()
-        self.dut.droid.wakeUpNow()
-
-    def teardown_test(self):
-        self.dut.droid.wakeLockRelease()
-        self.dut.droid.goToSleepNow()
-        wutils.reset_wifi(self.dut)
-
-    def teardown_class(self):
-        if "iperf_server_address" in self.user_params:
-            self.iperf_server.stop()
-
-    def on_fail(self, test_name, begin_time):
-        self.dut.take_bug_report(test_name, begin_time)
-        self.dut.cat_adb_log(test_name, begin_time)
-
-    """Helper Functions"""
-
-    def connect_to_wifi_network(self, network):
-        """Connection logic for open and psk wifi networks.
-
-        Args:
-            params: Dictionary with network info.
-        """
-        SSID = network[WifiEnums.SSID_KEY]
-        self.dut.ed.clear_all_events()
-        wutils.start_wifi_connection_scan(self.dut)
-        scan_results = self.dut.droid.wifiGetScanResults()
-        wutils.assert_network_in_list({WifiEnums.SSID_KEY: SSID}, scan_results)
-        wutils.wifi_connect(self.dut, network, num_of_tries=3)
-
-    def run_iperf_client(self, network):
-        """Run iperf traffic after connection.
-
-        Args:
-            params: Dictionary with network info.
-        """
-        if "iperf_server_address" in self.user_params:
-            wait_time = 5
-            SSID = network[WifiEnums.SSID_KEY]
-            self.log.info("Starting iperf traffic through {}".format(SSID))
-            time.sleep(wait_time)
-            port_arg = "-p {}".format(self.iperf_server.port)
-            success, data = self.dut.run_iperf_client(self.iperf_server_address,
-                                                      port_arg)
-            self.log.debug(pprint.pformat(data))
-            asserts.assert_true(success, "Error occurred in iPerf traffic.")
-
-    def connect_to_wifi_network_and_run_iperf(self, network):
-        """Connection logic for open and psk wifi networks.
-
-        Logic steps are
-        1. Connect to the network.
-        2. Run iperf traffic.
-
-        Args:
-            params: A dictionary with network info.
-        """
-        self.connect_to_wifi_network(network)
-        self.run_iperf_client(network)
-
-    """Tests"""
-
-    @test_tracker_info(uuid="a57cc861-b6c2-47e4-9db6-7a3ab32c6e20")
-    def test_iot_connection_to_ubiquity_ap1_2g(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="2065c2f7-2b89-4da7-a15d-e5dc17b88d52")
-    def test_iot_connection_to_ubiquity_ap1_5g(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="6870e35b-f7a7-45bf-b021-fea049ae53de")
-    def test_iot_connection_to_AirportExpress_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="95f4b405-79d7-4873-a152-4384acc88f41")
-    def test_iot_connection_to_AirportExpress_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="02a8cc75-6781-4153-8d90-bed7568a1e78")
-    def test_iot_connection_to_AirportExtreme_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="83a42c97-1358-4ba7-bdb2-238fdb1c945e")
-    def test_iot_connection_to_AirportExtreme_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="d56cc46a-f772-4c96-b84e-4e05c82f5f9d")
-    def test_iot_connection_to_AirportExtremeOld_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="4b57277d-ea96-4379-bd71-8b4f03253ec8")
-    def test_iot_connection_to_AirportExtremeOld_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="2503d9ed-35df-4be0-b838-590324cecaee")
-    def iot_connection_to_Dlink_AC1200_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="0a44e148-a4bf-43f4-88eb-e4c1ffa850ce")
-    def iot_connection_to_Dlink_AC1200_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="6bd77417-089f-4fb1-b4c2-2cd673c64bcb")
-    def test_iot_connection_to_Dlink_AC3200_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="9fbff6e7-36c8-4342-9c29-bce6a8ef04ec")
-    def test_iot_connection_to_Dlink_AC3200_5G_1(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="bfccdaa9-8e01-488c-9768-8c71ab5ec157")
-    def test_iot_connection_to_Dlink_AC3200_5G_2(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="0e4978de-0435-4856-ae5a-c39cc64e375b")
-    def test_iot_connection_to_Dlink_N750_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="cdb82797-9981-4ba6-8958-025f59c60e83")
-    def test_iot_connection_to_Dlink_N750_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="0bf8f129-eb96-4b1e-94bd-8dd93e8731e3")
-    def iot_connection_to_Linksys_E800_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="f231216d-6ab6-46b7-a0a5-1ac15935e412")
-    def test_iot_connection_to_Linksys_AC1900_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="5acd4bec-b210-4b4c-8b2c-60f3f67798a9")
-    def test_iot_connection_to_Linksys_AC1900_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="f4fd9877-b13f-47b0-9523-1ce363200c2f")
-    def iot_connection_to_Linksys_AC2400_2g(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="438d679a-4f6c-476d-9eba-63b6f1f2bef4")
-    def iot_connection_to_Linksys_AC2400_5g(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="b9bc00d8-46c5-4c5e-bd58-93ab1ca8d53b")
-    def iot_connection_to_NETGEAR_AC1900_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="fb4c7d80-4c12-4b08-a40a-2745e2bd167b")
-    def iot_connection_to_NETGEAR_AC1900_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="054d2ffc-97fd-4613-bf47-acedd0fa4701")
-    def test_iot_connection_to_NETGEAR_AC3200_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="d15a789a-def5-4c6a-b59e-1a75f73cc6a9")
-    def test_iot_connection_to_NETGEAR_AC3200_5G_1(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="1de6369e-97da-479f-b17c-9144bb814f51")
-    def test_iot_connection_to_NETGEAR_AC3200_5G_2(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="008ec18e-fd48-4359-8a0d-223c921a1faa")
-    def iot_connection_to_NETGEAR_N300_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="c61eeaf0-af02-46bf-bcec-871e2f9dee71")
-    def iot_connection_to_WNDR4500v2_AES_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="dcad3474-4022-48bc-8529-07321611b616")
-    def iot_connection_to_WNDR4500v2_WEP_SHARED128_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="3573a880-4542-4dea-9909-aa2f9865a059")
-    def iot_connection_to_ARCHER_WEP_OPEN_64_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="9c15c52e-945a-4b9b-bf0e-5bd6293dad1c")
-    def iot_connection_to_ARCHER_WEP_OPEN_128_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="e5517b82-c225-449d-83ac-055a561a764f")
-    def test_iot_connection_to_TP_LINK_AC1700_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="9531d3cc-129d-4501-a5e3-d7502120cd8b")
-    def test_iot_connection_to_TP_LINK_AC1700_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="eab810d4-8e07-49c9-86c1-cb8d1a0285d0")
-    def iot_connection_to_TP_LINK_N300_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="05d4cb25-a58d-46b4-a5ff-6e3fe28f2b16")
-    def iot_connection_to_fritz_7490_5g(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="8333e5e6-72fd-4957-bab0-fa45ce1d765a")
-    def iot_connection_to_NETGEAR_R8500_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="c88053fb-730f-4447-a802-1fb9721f69df")
-    def iot_connection_to_NETGEAR_R8500_5G1(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="f5d1e44b-396b-4976-bb0c-160bdce89a59")
-    def iot_connection_to_NETGEAR_R8500_5G2(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="7c12f943-d9e2-45b1-aa84-fcb43efbbb04")
-    def test_iot_connection_to_TP_LINK_5504_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="52be6b76-5e43-4289-83e1-4cd0d995d39b")
-    def test_iot_connection_to_TP_LINK_5504_5G_1(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="0b43d1da-e207-443d-b16c-c4ee3e924036")
-    def test_iot_connection_to_TP_LINK_5504_5G_2(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="4adcef5c-589a-4398-a28c-28a56d762f72")
-    def test_iot_connection_to_TP_LINK_C2600_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="3955a443-505b-4015-9daa-f52abbad8377")
-    def test_iot_connection_to_TP_LINK_C2600_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="3e9115dd-adb6-40a4-9831-dca8f1f32abe")
-    def test_iot_connection_to_Linksys06832_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="5dca028a-7384-444f-b231-973054afe215")
-    def test_iot_connection_to_Linksys06832_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="e639f6db-ad8e-4b4f-91f3-10acdf93142a")
-    def test_iot_connection_to_AmpedAthena_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="3dd90d80-952f-4f17-a48a-fe42e7d6e1ff")
-    def test_iot_connection_to_AmpedAthena_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="b9babe3a-ecba-4c5c-bc6b-0ba48c744e66")
-    def test_iot_connection_to_ASUS_AC3100_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="f8f06f92-821d-4e80-8f1e-efb6c6adc12a")
-    def test_iot_connection_to_ASUS_AC3100_5G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
-
-    @test_tracker_info(uuid="f4d227df-1151-469a-b01c-f4b1c1f7a84b")
-    def iot_connection_to_NETGEAR_WGR614_2G(self):
-        ssid_key = self.current_test_name.replace(self.iot_test_prefix, "")
-        self.connect_to_wifi_network_and_run_iperf(self.ssid_map[ssid_key])
diff --git a/acts/tests/google/wifi/WifiMacRandomizationTest.py b/acts/tests/google/wifi/WifiMacRandomizationTest.py
index 4485220..9461cbb 100644
--- a/acts/tests/google/wifi/WifiMacRandomizationTest.py
+++ b/acts/tests/google/wifi/WifiMacRandomizationTest.py
@@ -66,7 +66,7 @@
         self.dut_client = self.android_devices[1]
         wutils.wifi_test_device_init(self.dut)
         wutils.wifi_test_device_init(self.dut_client)
-        req_params = ["dbs_supported_models"]
+        req_params = ["dbs_supported_models", "roaming_attn"]
         opt_param = [
             "open_network", "reference_networks", "wep_networks"
         ]
@@ -79,8 +79,7 @@
         self.configure_packet_capture()
 
         if "AccessPoint" in self.user_params:
-            if "AccessPoint" in self.user_params:
-                self.legacy_configure_ap_and_start(wep_network=True, ap_count=2)
+            self.legacy_configure_ap_and_start(wep_network=True, ap_count=2)
 
         asserts.assert_true(
             len(self.reference_networks) > 0,
@@ -241,8 +240,9 @@
         for pkt in packets:
             self.log.debug("Packet Summary = %s" % pkt.summary())
             if mac in pkt.summary():
-                raise signals.TestFailure("Caught Factory MAC in packet sniffer."
-                                          "Packet = %s" % pkt.show())
+                raise signals.TestFailure("Caught Factory MAC in packet sniffer"
+                                          "Packet = %s Device = %s"
+                                           % (pkt.show(), self.dut))
 
     def verify_mac_is_found_in_pcap(self, mac, packets):
         for pkt in packets:
@@ -250,7 +250,7 @@
             if mac in pkt.summary():
                 return
         raise signals.TestFailure("Did not find MAC = %s in packet sniffer."
-                                  % mac)
+                                  "for device %s" % (mac, self.dut))
 
     def get_sta_mac_address(self):
         """Gets the current MAC address being used for client mode."""
@@ -489,18 +489,18 @@
         """
         AP1_network = self.reference_networks[0]["5g"]
         AP2_network = self.reference_networks[1]["5g"]
-        wutils.set_attns(self.attenuators, "AP1_on_AP2_off")
+        wutils.set_attns(self.attenuators, "AP1_on_AP2_off", self.roaming_attn)
         mac_before_roam = self.connect_to_network_and_verify_mac_randomization(
                 AP1_network)
         wutils.trigger_roaming_and_validate(self.dut, self.attenuators,
-                "AP1_off_AP2_on", AP2_network)
+                "AP1_off_AP2_on", AP2_network, self.roaming_attn)
         mac_after_roam = self.get_randomized_mac(AP2_network)
         if mac_after_roam != mac_before_roam:
             raise signals.TestFailure("Randomized MAC address changed after "
                    "roaming from AP1 to AP2.\nMAC before roam = %s\nMAC after "
                    "roam = %s" %(mac_before_roam, mac_after_roam))
         wutils.trigger_roaming_and_validate(self.dut, self.attenuators,
-                "AP1_on_AP2_off", AP1_network)
+                "AP1_on_AP2_off", AP1_network, self.roaming_attn)
         mac_after_roam = self.get_randomized_mac(AP1_network)
         if mac_after_roam != mac_before_roam:
             raise signals.TestFailure("Randomized MAC address changed after "
diff --git a/acts/tests/google/wifi/WifiManagerTest.py b/acts/tests/google/wifi/WifiManagerTest.py
index a4ef011..ce57f64 100644
--- a/acts/tests/google/wifi/WifiManagerTest.py
+++ b/acts/tests/google/wifi/WifiManagerTest.py
@@ -97,6 +97,8 @@
     def on_fail(self, test_name, begin_time):
         self.dut.take_bug_report(test_name, begin_time)
         self.dut.cat_adb_log(test_name, begin_time)
+        if self.dut_client:
+            self.dut_client.take_bug_report(test_name, begin_time)
 
     def teardown_class(self):
         if "AccessPoint" in self.user_params:
@@ -614,32 +616,6 @@
                 nw[WifiEnums.BSSID_KEY] != ssid,
                 "Found forgotten network %s in configured networks." % ssid)
 
-    @test_tracker_info(uuid="b306d65c-6df3-4eb5-a178-6278bdc76c3e")
-    def test_reconnect_to_connected_network(self):
-        """Connect to a network and immediately issue reconnect.
-
-        Steps:
-        1. Connect to a 2GHz network.
-        2. Reconnect to the network using its network id.
-        3. Connect to a 5GHz network.
-        4. Reconnect to the network using its network id.
-
-        """
-        connect_2g_data = self.get_connection_data(self.dut, self.wpapsk_2g)
-        reconnect_2g = self.connect_to_wifi_network_with_id(
-            connect_2g_data[WifiEnums.NETID_KEY],
-            connect_2g_data[WifiEnums.SSID_KEY])
-        if not reconnect_2g:
-            raise signals.TestFailure("Device did not connect to the correct"
-                                      " 2GHz network.")
-        connect_5g_data = self.get_connection_data(self.dut, self.wpapsk_5g)
-        reconnect_5g = self.connect_to_wifi_network_with_id(
-            connect_5g_data[WifiEnums.NETID_KEY],
-            connect_5g_data[WifiEnums.SSID_KEY])
-        if not reconnect_5g:
-            raise signals.TestFailure("Device did not connect to the correct"
-                                      " 5GHz network.")
-
     @test_tracker_info(uuid="3cff17f6-b684-4a95-a438-8272c2ad441d")
     def test_reconnect_to_previously_connected(self):
         """Connect to multiple networks and reconnect to the previous network.
@@ -976,3 +952,42 @@
                 "wifi state changed after reboot")
 
         disable_bluetooth(self.dut.droid)
+
+    @test_tracker_info(uuid="d0e14a2d-a28f-4551-8988-1e15d9d8bb1a")
+    def test_scan_result_api(self):
+        """Register scan result callback, start scan and wait for event"""
+        self.dut.ed.clear_all_events()
+        self.dut.droid.wifiStartScanWithListener()
+        try:
+            events = self.dut.ed.pop_events(
+                "WifiManagerScanResultsCallbackOnSuccess", 60)
+        except queue.Empty:
+            asserts.fail(
+                "Wi-Fi scan results did not become available within 60s.")
+
+    @test_tracker_info(uuid="03cfbc86-7fcc-48d8-ab0f-1f6f3523e596")
+    def test_enable_disable_auto_join_saved_network(self):
+        """
+        Add a saved network, simulate user change the auto join to false, ensure the device doesn't
+        auto connect to this network
+
+        Steps:
+        1. Create a saved network.
+        2. Add this saved network, and ensure we connect to this network
+        3. Simulate user change the auto join to false.
+        4. Toggle the Wifi off and on
+        4. Ensure device doesn't connect to his network
+        """
+        network = self.open_network_5g
+        wutils.connect_to_wifi_network(self.dut, network)
+        info = self.dut.droid.wifiGetConnectionInfo()
+        network_id = info[WifiEnums.NETID_KEY]
+        self.dut.log.info("Disable auto join on network")
+        self.dut.droid.wifiEnableAutojoin(network_id, False)
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.wifi_toggle_state(self.dut, True)
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut, network[WifiEnums.SSID_KEY],
+                                    assert_on_fail=False), "Device should not connect.")
+        self.dut.droid.wifiEnableAutojoin(network_id, True)
+        wutils.wait_for_connect(self.dut, network[WifiEnums.SSID_KEY], assert_on_fail=False)
diff --git a/acts/tests/google/wifi/WifiNetworkRequestTest.py b/acts/tests/google/wifi/WifiNetworkRequestTest.py
index b966de2..28d5d16 100644
--- a/acts/tests/google/wifi/WifiNetworkRequestTest.py
+++ b/acts/tests/google/wifi/WifiNetworkRequestTest.py
@@ -405,39 +405,17 @@
                     network_specifier)
         time.sleep(wifi_constants.NETWORK_REQUEST_CB_REGISTER_DELAY_SEC)
         self.dut.droid.wifiRegisterNetworkRequestMatchCallback()
-        # Wait for the request to timeout. In the meantime, platform will scan
-        # and return matching networks. Ensure the matching networks list is
-        # empty.
-        start_time = time.time()
-        has_request_timedout = False
+        # Wait for the request to timeout.
+        timeout_secs = \
+            NETWORK_REQUEST_TIMEOUT_MS / 1000 + NETWORK_REQUEST_INSTANT_FAILURE_TIMEOUT_SEC
         try:
-          while not has_request_timedout and time.time() - start_time <= \
-              NETWORK_REQUEST_TIMEOUT_MS / 1000:
-                # Pop all network request related events.
-                network_request_events = \
-                    self.dut.ed.pop_events("WifiManagerNetwork.*", 30)
-                asserts.assert_true(network_request_events, "invalid events")
-                for network_request_event in network_request_events:
-                    # Handle the network match callbacks.
-                    if network_request_event["name"] == \
-                        wifi_constants.WIFI_NETWORK_REQUEST_MATCH_CB_ON_MATCH:
-                        matched_scan_results = network_request_event["data"]
-                        self.dut.log.debug(
-                            "Network request on match results %s",
-                            matched_scan_results)
-                        asserts.assert_false(matched_scan_results,
-                                             "Empty network matches expected")
-                    # Handle the network request unavailable timeout.
-                    if network_request_event["name"] == \
-                        wifi_constants.WIFI_NETWORK_CB_ON_UNAVAILABLE:
-                        self.dut.log.info("Network request timed out")
-                        has_request_timedout = True
+            on_unavailable_event = self.dut.ed.pop_event(
+                wifi_constants.WIFI_NETWORK_CB_ON_UNAVAILABLE, timeout_secs)
+            asserts.assert_true(on_unavailable_event, "Network request did not timeout")
         except queue.Empty:
             asserts.fail("No events returned")
         finally:
             self.dut.droid.wifiStopTrackingStateChange()
-        asserts.assert_true(has_request_timedout,
-                            "Network request did not timeout")
 
     @test_tracker_info(uuid="760c3768-697d-442b-8d61-cfe02f10ceff")
     def test_connect_failure_user_rejected(self):
diff --git a/acts/tests/google/wifi/WifiNetworkSelectorTest.py b/acts/tests/google/wifi/WifiNetworkSelectorTest.py
index 5af4ad9..41f723c 100644
--- a/acts/tests/google/wifi/WifiNetworkSelectorTest.py
+++ b/acts/tests/google/wifi/WifiNetworkSelectorTest.py
@@ -35,9 +35,13 @@
 AP_1_5G_ATTENUATOR = 1
 AP_2_2G_ATTENUATOR = 2
 AP_2_5G_ATTENUATOR = 3
-ATTENUATOR_INITIAL_SETTING = 60
 # WifiNetworkSelector imposes a 10 seconds gap between two selections
 NETWORK_SELECTION_TIME_GAP = 12
+LVL1_ATTN = 15
+LVL2_ATTN = 30
+MIN_ATTN = 0
+MAX_ATTN = 95
+ATTN_SLEEP = 12
 
 
 class WifiNetworkSelectorTest(WifiBaseTest):
@@ -50,44 +54,31 @@
 
         self.dut = self.android_devices[0]
         wutils.wifi_test_device_init(self.dut)
-        req_params = []
-        opt_param = ["open_network", "reference_networks"]
-        self.unpack_userparams(
-            req_param_names=req_params, opt_param_names=opt_param)
-
-        if hasattr(self, 'access_points'):
-            self.legacy_configure_ap_and_start(ap_count=2)
-
-        if hasattr(self, 'packet_capture'):
-            self.configure_packet_capture()
+        self.legacy_configure_ap_and_start(ap_count=2, mirror_ap=False)
+        self.configure_packet_capture()
 
     def setup_test(self):
-        #reset and clear all saved networks on the DUT
-        wutils.reset_wifi(self.dut)
-        #move the APs out of range
-        for attenuator in self.attenuators:
-            attenuator.set_atten(ATTENUATOR_INITIAL_SETTING)
-        #turn on the screen
         self.dut.droid.wakeLockAcquireBright()
         self.dut.droid.wakeUpNow()
         self.dut.ed.clear_all_events()
-
-        if hasattr(self, 'packet_capture'):
-            self.pcap_procs = wutils.start_pcap(
-                self.packet_capture, 'dual', self.test_name)
+        self.pcap_procs = wutils.start_pcap(
+            self.packet_capture, 'dual', self.test_name)
+        for a in self.attenuators:
+            a.set_atten(MAX_ATTN)
+        time.sleep(ATTN_SLEEP)
 
     def teardown_test(self):
-        #turn off the screen
+        for a in self.attenuators:
+            a.set_atten(MIN_ATTN)
+        wutils.reset_wifi(self.dut)
         self.dut.droid.wakeLockRelease()
         self.dut.droid.goToSleepNow()
 
     def on_pass(self, test_name, begin_time):
-        if hasattr(self, 'packet_capture'):
-            wutils.stop_pcap(self.packet_capture, self.pcap_procs, True)
+        wutils.stop_pcap(self.packet_capture, self.pcap_procs, True)
 
     def on_fail(self, test_name, begin_time):
-        if hasattr(self, 'packet_capture'):
-            wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+        wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
         self.dut.take_bug_report(test_name, begin_time)
         self.dut.cat_adb_log(test_name, begin_time)
 
@@ -108,13 +99,14 @@
         """
         for network in networks:
             ret = ad.droid.wifiAddNetwork(network)
-            asserts.assert_true(ret != -1, "Failed to add network %s" %
-                                network)
+            asserts.assert_true(ret != -1,
+                                "Failed to add network %s" % network)
             ad.droid.wifiEnableNetwork(ret, 0)
-        configured_networks = ad.droid.wifiGetConfiguredNetworks()
-        logging.debug("Configured networks: %s", configured_networks)
 
-    def connect_and_verify_connected_bssid(self, expected_bssid):
+        configured_networks = ad.droid.wifiGetConfiguredNetworks()
+        self.log.info("Configured networks: %s", configured_networks)
+
+    def connect_and_verify_connected_bssid(self, network):
         """Start a scan to get the DUT connected to an AP and verify the DUT
         is connected to the correct BSSID.
 
@@ -124,22 +116,19 @@
         Returns:
             True if connection to given network happen, else return False.
         """
-        #wait for the attenuator to stablize
-        time.sleep(10)
-        #force start a single scan so we don't have to wait for the
-        #WCM scheduled scan.
-        wutils.start_wifi_connection_scan(self.dut)
-        #wait for connection
+        expected_ssid = network['SSID']
+        expected_bssid = network['bssid']
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, expected_ssid)
         time.sleep(20)
-        #verify connection
         actual_network = self.dut.droid.wifiGetConnectionInfo()
-        logging.info("Actual network: %s", actual_network)
-        try:
-            asserts.assert_equal(expected_bssid,
-                                 actual_network[WifiEnums.BSSID_KEY])
-        except:
-           msg = "Device did not connect to any network."
-           raise signals.TestFailure(msg)
+        self.log.info("Actual network: %s", actual_network)
+        asserts.assert_true(
+            actual_network and WifiEnums.BSSID_KEY in actual_network and \
+                expected_bssid == actual_network[WifiEnums.BSSID_KEY],
+            "Expected BSSID: %s, Actual BSSID: %s" %
+            (expected_bssid, actual_network[WifiEnums.BSSID_KEY]))
+        self.log.info("DUT connected to valid network: %s" % expected_bssid)
 
     """ Tests Begin """
 
@@ -150,14 +139,17 @@
             2. Move the DUT in range.
             3. Verify the DUT is connected to the network.
         """
-        #add a saved network to DUT
+        # add a saved network to DUT
         networks = [self.reference_networks[AP_1]['5g']]
         self.add_networks(self.dut, networks)
-        #move the DUT in range
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(0)
-        #verify
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
-            '5g']['bssid'])
+
+        # move the DUT in range
+        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(MIN_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT is connected to AP_1 5g network
+        self.connect_and_verify_connected_bssid(
+            self.reference_networks[AP_1]['5g'])
 
     @test_tracker_info(uuid="3ea818f2-10d7-4aad-bfab-7d8fb25aae78")
     def test_network_selector_basic_connection_prefer_5g(self):
@@ -166,18 +158,19 @@
             2. Move the DUT in range.
             3. Verify the DUT is connected to the 5G BSSID.
         """
-        #add a saved network with both 2G and 5G BSSIDs to DUT
-        # TODO: bmahadev Change this to a single SSID once we migrate tests to
-        # use dynamic AP.
+        # add a saved network with both 2G and 5G BSSIDs to DUT
         networks = [self.reference_networks[AP_1]['2g'],
                     self.reference_networks[AP_1]['5g']]
         self.add_networks(self.dut, networks)
-        #move the DUT in range
-        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(0)
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(0)
-        #verify
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
-            '5g']['bssid'])
+
+        # Move DUT in range
+        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(MIN_ATTN)
+        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(MIN_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT is connected to 5G network
+        self.connect_and_verify_connected_bssid(
+            self.reference_networks[AP_1]['5g'])
 
     @test_tracker_info(uuid="bebb29ca-4486-4cde-b390-c5f8f2e1580c")
     def test_network_selector_prefer_stronger_rssi(self):
@@ -187,18 +180,19 @@
             2. Move the DUT in range.
             3. Verify the DUT is connected to the SSID with stronger RSSI.
         """
-        #add a 2G and a 5G saved network to DUT
-        networks = [
-            self.reference_networks[AP_1]['2g'], self.reference_networks[AP_2][
-                '2g']
-        ]
+        # add a 2G and a 5G saved network to DUT
+        networks = [self.reference_networks[AP_1]['2g'],
+                    self.reference_networks[AP_2]['2g']]
         self.add_networks(self.dut, networks)
-        #move the DUT in range
-        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(20)
-        self.attenuators[AP_2_2G_ATTENUATOR].set_atten(40)
-        #verify
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
-            '2g']['bssid'])
+
+        # move the DUT in range
+        self.attenuators[AP_1_2G_ATTENUATOR].set_atten(LVL1_ATTN)
+        self.attenuators[AP_2_2G_ATTENUATOR].set_atten(LVL2_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT is connected AP_1
+        self.connect_and_verify_connected_bssid(
+            self.reference_networks[AP_1]['2g'])
 
     @test_tracker_info(uuid="f9f72dc5-034f-4fe2-a27d-df1b6cae76cd")
     def test_network_selector_prefer_secure_over_open_network(self):
@@ -208,17 +202,18 @@
             2. Move the DUT in range.
             3. Verify the DUT is connected to the secure network that uses WPA2.
         """
-        #add a open network and a secure saved network to DUT
-        networks = [
-            self.open_network[AP_1]['5g'], self.reference_networks[AP_1]['5g']
-        ]
+        # add a open network and a secure saved network to DUT
+        networks = [self.open_network[AP_1]['5g'],
+                    self.reference_networks[AP_1]['5g']]
         self.add_networks(self.dut, networks)
-        #move the DUT in range
-        #TODO: control open network attenuator
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(0)
-        #verify
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
-            '5g']['bssid'])
+
+        # Move DUT in range
+        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(MIN_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT connects to secure network
+        self.connect_and_verify_connected_bssid(
+            self.reference_networks[AP_1]['5g'])
 
     @test_tracker_info(uuid="ab2c527c-0f9c-4f09-a13f-e3f461b7da52")
     def test_network_selector_blacklist_by_connection_failure(self):
@@ -228,26 +223,30 @@
             2. Move the DUT in range.
             3. Verify the DUT is connected to network Y.
         """
-        #add two saved networks to DUT, and one of them is configured with incorrect password
+        # add two saved networks to DUT, and one of them is configured with
+        # incorrect password
         wrong_passwd_network = self.reference_networks[AP_1]['5g'].copy()
         wrong_passwd_network['password'] += 'haha'
         networks = [wrong_passwd_network, self.reference_networks[AP_2]['5g']]
         self.add_networks(self.dut, networks)
-        #make both AP_1 5G and AP_2 5G in range, and AP_1 5G has stronger RSSI than AP_2 5G
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(0)
-        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(10)
-        #start 3 scans to get AP_1 5G blacklisted because of the incorrect password
-        count = 0
-        while count < 3:
-            wutils.start_wifi_connection_scan(self.dut)
+
+        # make AP_1 5G has stronger RSSI than AP_2 5G
+        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(MIN_ATTN)
+        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(LVL1_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # start 3 scans to get AP_1 5G blacklisted because of the incorrect
+        # password
+        for _ in range(3):
+            wutils.start_wifi_connection_scan_and_return_status(self.dut)
             time.sleep(NETWORK_SELECTION_TIME_GAP)
-            count += 1
-        #verify
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_2][
-            '5g']['bssid'])
+
+        # verify DUT is connect AP_2 5G
+        self.connect_and_verify_connected_bssid(
+            self.reference_networks[AP_2]['5g'])
 
     @test_tracker_info(uuid="71d88fcf-c7b8-4fd2-a7cb-84ac4a130ecf")
-    def test_network_selector_2g_to_5g_prefer_same_SSID(self):
+    def network_selector_2g_to_5g_prefer_same_SSID(self):
         """
             1. Add SSID_A and SSID_B to DUT. Both SSIDs have both 2G and 5G
                BSSIDs.
@@ -277,7 +276,7 @@
             '5g']['bssid'])
 
     @test_tracker_info(uuid="c1243cf4-d96e-427e-869e-3d640bee3f28")
-    def test_network_selector_2g_to_5g_different_ssid(self):
+    def network_selector_2g_to_5g_different_ssid(self):
         """
             1. Add SSID_A and SSID_B to DUT. Both SSIDs have both 2G and 5G
                BSSIDs.
@@ -287,14 +286,13 @@
                2G RSSI.
             4. Verify the DUT switches to SSID_B's 5G.
         """
-        #add two saved networks to DUT
-        networks = [
-            self.reference_networks[AP_1]['2g'], self.reference_networks[AP_2][
-                '2g']
-        ]
+        # add two saved networks to DUT
+        networks = [self.reference_networks[AP_1]['2g'],
+                    self.reference_networks[AP_2]['2g']]
         self.add_networks(self.dut, networks)
-        #make both AP_1 2G and AP_2 5G in range, and AP_1 2G
-        #has much stronger RSSI than AP_2 5G
+
+        # make both AP_1 2G and AP_2 5G in range, and AP_1 2G
+        # has much stronger RSSI than AP_2 5G
         self.attenuators[AP_1_2G_ATTENUATOR].set_atten(0)
         self.attenuators[AP_2_5G_ATTENUATOR].set_atten(20)
         #verify
@@ -310,7 +308,7 @@
             '5g']['bssid'])
 
     @test_tracker_info(uuid="10da95df-83ed-4447-89f8-735b08dbe2eb")
-    def test_network_selector_5g_to_2g_same_ssid(self):
+    def network_selector_5g_to_2g_same_ssid(self):
         """
             1. Add one SSID that has both 2G and 5G to the DUT.
             2. Attenuate down the 2G RSSI.
@@ -346,26 +344,30 @@
             3. Change attenuation so that Y's RSSI goes above X's.
             4. Verify the DUT stays on X.
         """
-        #add two saved networks to DUT
-        networks = [
-            self.reference_networks[AP_1]['5g'], self.reference_networks[AP_2][
-                '5g']
-        ]
+        # add two saved networks to DUT
+        networks = [self.reference_networks[AP_1]['5g'],
+                    self.reference_networks[AP_2]['5g']]
         self.add_networks(self.dut, networks)
-        #make both AP_1 5G and AP_2 5G in range, and AP_1 5G
-        #has stronger RSSI than AP_2 5G
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(10)
-        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(20)
-        #verify
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
-            '5g']['bssid'])
-        #bump up AP_2 5G RSSI over AP_1 5G RSSI
-        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(0)
-        #ensure the time gap between two network selections
+
+        # make both AP_1 5G and AP_2 5G in range, and AP_1 5G
+        # has stronger RSSI than AP_2 5G
+        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(LVL1_ATTN)
+        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(LVL2_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT is connected to AP_1
+        self.connect_and_verify_connected_bssid(
+            self.reference_networks[AP_1]['5g'])
+
+        # bump up AP_2 5G RSSI over AP_1 5G RSSI
+        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(MIN_ATTN)
+
+        # ensure the time gap between two network selections
         time.sleep(NETWORK_SELECTION_TIME_GAP)
-        #verify
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
-            '5g']['bssid'])
+
+        # verify DUT is still connected to AP_1
+        self.connect_and_verify_connected_bssid(
+            self.reference_networks[AP_1]['5g'])
 
     @test_tracker_info(uuid="5470010f-8b62-4b1c-8b83-1f91422eced0")
     def test_network_selector_stay_on_user_selected_network(self):
@@ -375,23 +377,23 @@
             3. Start a scan and network selection.
             4. Verify DUT stays on SSID_A.
         """
-        #make AP_1 5G in range with a low RSSI
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(10)
-        #connect to AP_1 via user selection
-        wutils.wifi_connect(self.dut, self.reference_networks[AP_1]['5g'])
-        #verify
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
-            '5g']['bssid'])
-        #make AP_2 5G in range with a strong RSSI
-        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(0)
-        #add AP_2 as a saved network to DUT
+        # set max attenuation on AP_2 and make AP_1 5G in range with low RSSI
+        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(MIN_ATTN)
+        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(LVL1_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # connect to AP_1 via user selection and add, save AP_2
+        wutils.connect_to_wifi_network(
+            self.dut, self.reference_networks[AP_1]['5g'])
         networks = [self.reference_networks[AP_2]['5g']]
         self.add_networks(self.dut, networks)
-        #ensure the time gap between two network selections
+
+        # ensure the time gap between two network selections
         time.sleep(NETWORK_SELECTION_TIME_GAP)
-        #verify we are still connected to AP_1 5G
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
-            '5g']['bssid'])
+
+        # verify we are still connected to AP_1 5G
+        self.connect_and_verify_connected_bssid(
+            self.reference_networks[AP_1]['5g'])
 
     @test_tracker_info(uuid="f08d8f73-8c94-42af-bba9-4c49bbf16420")
     def test_network_selector_reselect_after_forget_network(self):
@@ -402,22 +404,25 @@
             3. Forget X.
             5. Verify the DUT reselect and connect to Y.
         """
-        #add two saved networks to DUT
-        networks = [
-            self.reference_networks[AP_1]['5g'], self.reference_networks[AP_2][
-                '5g']
-        ]
+        # add two networks to DUT
+        networks = [self.reference_networks[AP_1]['5g'],
+                    self.reference_networks[AP_2]['5g']]
         self.add_networks(self.dut, networks)
-        #make both AP_1 5G and AP_2 5G in range. AP_1 5G has stronger
-        #RSSI than AP_2 5G
-        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(0)
-        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(10)
-        #verify
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_1][
-            '5g']['bssid'])
-        #forget AP_1
-        wutils.wifi_forget_network(self.dut,
-                                   self.reference_networks[AP_1]['5g']['SSID'])
-        #verify
-        self.connect_and_verify_connected_bssid(self.reference_networks[AP_2][
-            '5g']['bssid'])
+
+        # make both AP_1 5G and AP_2 5G in range. AP_1 5G has stronger
+        # RSSI than AP_2 5G
+        self.attenuators[AP_1_5G_ATTENUATOR].set_atten(MIN_ATTN)
+        self.attenuators[AP_2_5G_ATTENUATOR].set_atten(LVL1_ATTN)
+        time.sleep(ATTN_SLEEP)
+
+        # verify DUT connected to AP1
+        self.connect_and_verify_connected_bssid(
+            self.reference_networks[AP_1]['5g'])
+
+        # forget AP_1
+        wutils.wifi_forget_network(
+            self.dut, self.reference_networks[AP_1]['5g']['SSID'])
+
+        # verify DUT connected to AP2
+        self.connect_and_verify_connected_bssid(
+            self.reference_networks[AP_2]['5g'])
diff --git a/acts/tests/google/wifi/WifiNetworkSuggestionTest.py b/acts/tests/google/wifi/WifiNetworkSuggestionTest.py
index 6d6da35..350364b 100644
--- a/acts/tests/google/wifi/WifiNetworkSuggestionTest.py
+++ b/acts/tests/google/wifi/WifiNetworkSuggestionTest.py
@@ -36,10 +36,12 @@
 EapPhase2 = WifiEnums.EapPhase2
 # Enterprise Config Macros
 Ent = WifiEnums.Enterprise
+ATT = 2
 
 # Default timeout used for reboot, toggle WiFi and Airplane mode,
 # for the system to settle down after the operation.
 DEFAULT_TIMEOUT = 10
+PASSPOINT_TIMEOUT = 30
 
 
 class WifiNetworkSuggestionTest(WifiBaseTest):
@@ -59,7 +61,7 @@
         req_params = []
         opt_param = [
             "open_network", "reference_networks", "radius_conf_2g", "radius_conf_5g", "ca_cert",
-            "eap_identity", "eap_password", "hidden_networks"
+            "eap_identity", "eap_password", "hidden_networks", "passpoint_networks"
         ]
         self.unpack_userparams(
             req_param_names=req_params, opt_param_names=opt_param)
@@ -70,21 +72,20 @@
                 radius_conf_2g=self.radius_conf_2g,
                 radius_conf_5g=self.radius_conf_5g,)
 
-        asserts.assert_true(
-            len(self.reference_networks) > 0,
-            "Need at least one reference network with psk.")
-        if hasattr(self, "reference_networks"):
-            self.wpa_psk_2g = self.reference_networks[0]["2g"]
-            self.wpa_psk_5g = self.reference_networks[0]["5g"]
-        if hasattr(self, "open_network"):
+        if hasattr(self, "reference_networks") and \
+            isinstance(self.reference_networks, list):
+              self.wpa_psk_2g = self.reference_networks[0]["2g"]
+              self.wpa_psk_5g = self.reference_networks[0]["5g"]
+        if hasattr(self, "open_network") and isinstance(self.open_network,list):
             self.open_2g = self.open_network[0]["2g"]
             self.open_5g = self.open_network[0]["5g"]
-        if hasattr(self, "ent_networks"):
+        if hasattr(self, "ent_networks") and isinstance(self.ent_networks,list):
             self.ent_network_2g = self.ent_networks[0]["2g"]
             self.ent_network_5g = self.ent_networks[0]["5g"]
             self.config_aka = {
                 Ent.EAP: int(EAP.AKA),
                 WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
+                "carrierId": str(self.dut.droid.telephonyGetSimCarrierId()),
             }
             self.config_ttls = {
                 Ent.EAP: int(EAP.TTLS),
@@ -94,8 +95,9 @@
                 Ent.PHASE2: int(EapPhase2.MSCHAPV2),
                 WifiEnums.SSID_KEY: self.ent_network_2g[WifiEnums.SSID_KEY],
             }
-        if hasattr(self, "hidden_networks"):
-            self.hidden_network = self.hidden_networks[0]
+        if hasattr(self, "hidden_networks") and \
+            isinstance(self.hidden_networks, list):
+              self.hidden_network = self.hidden_networks[0]
         self.dut.droid.wifiRemoveNetworkSuggestions([])
 
     def setup_test(self):
@@ -137,6 +139,24 @@
             + " " + SL4A_APK_NAME)
         return True if (is_approved_str == "yes") else False
 
+    def set_carrier_approved(self, carrier_id, approved):
+        self.dut.log.debug(("Setting IMSI protection exemption for carrier: " + carrier_id
+                                + "approved" if approved else "not approved"))
+        self.dut.adb.shell("cmd wifi imsi-protection-exemption-set-user-approved-for-carrier"
+                           + " " + carrier_id
+                           + " " + ("yes" if approved else "no"))
+
+    def is_carrier_approved(self, carrier_id):
+        is_approved_str = self.dut.adb.shell(
+            "cmd wifi imsi-protection-exemption-has-user-approved-for-carrier"
+            + " " + carrier_id)
+        return True if (is_approved_str == "yes") else False
+
+    def clear_carrier_approved(self, carrier_id):
+        self.dut.adb.shell(
+            "cmd wifi imsi-protection-exemption-clear-user-approved-for-carrier"
+            + " " + carrier_id)
+
     def clear_deleted_ephemeral_networks(self):
         self.dut.log.debug("Clearing deleted ephemeral networks")
         self.dut.adb.shell(
@@ -148,18 +168,21 @@
         if expect_post_connection_broadcast is not None:
             self.dut.droid.wifiStartTrackingNetworkSuggestionStateChange()
 
-        self.dut.log.info("Adding network suggestions");
+        self.dut.log.info("Adding network suggestions")
         asserts.assert_true(
             self.dut.droid.wifiAddNetworkSuggestions(network_suggestions),
             "Failed to add suggestions")
         # Enable suggestions by the app.
-        self.dut.log.debug("Enabling suggestions from test");
+        self.dut.log.debug("Enabling suggestions from test")
         self.set_approved(True)
         wutils.start_wifi_connection_scan_and_return_status(self.dut)
+        # if suggestion is passpoint wait longer for connection.
+        if "profile" in network_suggestions:
+            time.sleep(PASSPOINT_TIMEOUT)
         wutils.wait_for_connect(self.dut, expected_ssid)
 
         if expect_post_connection_broadcast is None:
-            return;
+            return
 
         # Check if we expected to get the broadcast.
         try:
@@ -180,15 +203,11 @@
     def remove_suggestions_disconnect_and_ensure_no_connection_back(self,
                                                                     network_suggestions,
                                                                     expected_ssid):
+        # Remove suggestion trigger disconnect and wait for the disconnect.
         self.dut.log.info("Removing network suggestions")
         asserts.assert_true(
             self.dut.droid.wifiRemoveNetworkSuggestions(network_suggestions),
             "Failed to remove suggestions")
-        # Ensure we did not disconnect
-        wutils.ensure_no_disconnect(self.dut)
-
-        # Trigger a disconnect and wait for the disconnect.
-        self.dut.droid.wifiDisconnect()
         wutils.wait_for_disconnect(self.dut)
         self.dut.ed.clear_all_events()
 
@@ -219,6 +238,11 @@
         self.remove_suggestions_disconnect_and_ensure_no_connection_back(
             network_suggestions, wifi_network[WifiEnums.SSID_KEY])
 
+        # Reboot with empty suggestion, verify user approval is kept.
+        self.dut.reboot()
+        time.sleep(DEFAULT_TIMEOUT)
+        asserts.assert_true(self.is_approved(), "User approval should be kept")
+
     @test_tracker_info(uuid="bda8ed20-4382-4380-831a-64cf77eca108")
     def test_connect_to_wpa_psk_2g(self):
         """ Adds a network suggestion and ensure that the device connected.
@@ -247,8 +271,11 @@
         1. Send 2 network suggestions to the device (with different priorities).
         2. Wait for the device to connect to the network with the highest
            priority.
-        3. Re-add the suggestions with the priorities reversed.
-        4. Again wait for the device to connect to the network with the highest
+        3. In-place modify network suggestions with priorities reversed
+        4. Restart wifi, wait for the device to connect to the network with the highest
+           priority.
+        5. Re-add the suggestions with the priorities reversed again.
+        6. Again wait for the device to connect to the network with the highest
            priority.
         """
         network_suggestion_2g = self.wpa_psk_2g
@@ -262,16 +289,33 @@
             self.wpa_psk_2g[WifiEnums.SSID_KEY],
             None)
 
+        # In-place modify Reverse the priority, should be no disconnect
+        network_suggestion_2g[WifiEnums.PRIORITY] = 2
+        network_suggestion_5g[WifiEnums.PRIORITY] = 5
+        self.dut.log.info("Modifying network suggestions");
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([network_suggestion_2g,
+                                                      network_suggestion_5g]),
+            "Failed to add suggestions")
+        wutils.ensure_no_disconnect(self.dut)
+
+        # Disable and re-enable wifi, should connect to higher priority
+        wutils.wifi_toggle_state(self.dut, False)
+        time.sleep(DEFAULT_TIMEOUT)
+        wutils.wifi_toggle_state(self.dut, True)
+        wutils.start_wifi_connection_scan_and_return_status(self.dut)
+        wutils.wait_for_connect(self.dut, self.wpa_psk_5g[WifiEnums.SSID_KEY])
+
         self.remove_suggestions_disconnect_and_ensure_no_connection_back(
-            [], self.wpa_psk_2g[WifiEnums.SSID_KEY])
+            [], self.wpa_psk_5g[WifiEnums.SSID_KEY])
 
         # Reverse the priority.
         # Add suggestions & wait for the connection event.
-        network_suggestion_2g[WifiEnums.PRIORITY] = 2
-        network_suggestion_5g[WifiEnums.PRIORITY] = 5
+        network_suggestion_2g[WifiEnums.PRIORITY] = 5
+        network_suggestion_5g[WifiEnums.PRIORITY] = 2
         self.add_suggestions_and_ensure_connection(
             [network_suggestion_2g, network_suggestion_5g],
-            self.wpa_psk_5g[WifiEnums.SSID_KEY],
+            self.wpa_psk_2g[WifiEnums.SSID_KEY],
             None)
 
     @test_tracker_info(uuid="b1d27eea-23c8-4c4f-b944-ef118e4cc35f")
@@ -308,6 +352,7 @@
         4. Reboot the device.
         5. Wait for the device to connect to back to it.
         6. Remove the suggestions and ensure the device does not connect back.
+        7. Reboot the device again, ensure user approval is kept
         """
         self._test_connect_to_wifi_network_reboot_config_store(
             [self.wpa_psk_5g], self.wpa_psk_5g)
@@ -325,9 +370,14 @@
         4. Reboot the device.
         5. Wait for the device to connect to the wifi network.
         6. Remove suggestions and ensure device doesn't connect back to it.
+        7. Reboot the device again, ensure user approval is kept
         """
+        if "carrierId" in self.config_aka:
+            self.set_carrier_approved(self.config_aka["carrierId"], True)
         self._test_connect_to_wifi_network_reboot_config_store(
             [self.config_aka], self.ent_network_2g)
+        if "carrierId" in self.config_aka:
+            self.clear_carrier_approved(self.config_aka["carrierId"])
 
     @test_tracker_info(uuid="98b2d40a-acb4-4a2f-aba1-b069e2a1d09d")
     def test_connect_to_wpa_ent_config_ttls_pap_reboot_config_store(self):
@@ -342,6 +392,7 @@
         4. Reboot the device.
         5. Wait for the device to connect to the wifi network.
         6. Remove suggestions and ensure device doesn't connect back to it.
+        7. Reboot the device again, ensure user approval is kept
         """
         config = dict(self.config_ttls)
         config[WifiEnums.Enterprise.PHASE2] = WifiEnums.EapPhase2.PAP.value
@@ -456,3 +507,222 @@
             [network_suggestion], network_suggestion[WifiEnums.SSID_KEY], False)
         self.remove_suggestions_disconnect_and_ensure_no_connection_back(
             [network_suggestion], network_suggestion[WifiEnums.SSID_KEY])
+
+    @test_tracker_info(uuid="806dff14-7543-482b-bd0a-598de59374b3")
+    def test_connect_to_passpoint_network_with_post_connection_broadcast(self):
+        """ Adds a passpoint network suggestion and ensure that the device connected.
+
+        Steps:
+        1. Send a network suggestion to the device.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did receive the post connection broadcast
+               (isAppInteractionRequired = true).
+        4. Remove the suggestions and ensure the device does not connect back.
+        """
+        asserts.skip_if(not hasattr(self, "passpoint_networks"),
+                        "No passpoint networks, skip this test")
+        passpoint_config = self.passpoint_networks[ATT]
+        passpoint_config[WifiEnums.IS_APP_INTERACTION_REQUIRED] = True
+        if "carrierId" in passpoint_config:
+            self.set_carrier_approved(passpoint_config["carrierId"], True)
+        self.add_suggestions_and_ensure_connection([passpoint_config],
+                                                   passpoint_config[WifiEnums.SSID_KEY], True)
+        self.remove_suggestions_disconnect_and_ensure_no_connection_back(
+            [passpoint_config], passpoint_config[WifiEnums.SSID_KEY])
+        if "carrierId" in passpoint_config:
+            self.clear_carrier_approved(passpoint_config["carrierId"])
+
+    @test_tracker_info(uuid="159b8b8c-fb00-4d4e-a29f-606881dcbf44")
+    def test_connect_to_passpoint_network_reboot_config_store(self):
+        """
+        Adds a passpoint network suggestion and ensure that the device connects to it
+        after reboot.
+
+        Steps:
+        1. Send a network suggestion to the device.
+        2. Wait for the device to connect to it.
+        3. Ensure that we did not receive the post connection broadcast
+           (isAppInteractionRequired = False).
+        4. Reboot the device.
+        5. Wait for the device to connect to back to it.
+        6. Remove the suggestions and ensure the device does not connect back.
+        7. Reboot the device again, ensure user approval is kept
+        """
+        asserts.skip_if(not hasattr(self, "passpoint_networks"),
+                        "No passpoint networks, skip this test")
+        passpoint_config = self.passpoint_networks[ATT]
+        if "carrierId" in passpoint_config:
+            self.set_carrier_approved(passpoint_config["carrierId"], True)
+        self._test_connect_to_wifi_network_reboot_config_store([passpoint_config],
+                                                               passpoint_config)
+        if "carrierId" in passpoint_config:
+            self.clear_carrier_approved(passpoint_config["carrierId"])
+
+    @test_tracker_info(uuid="34f3d28a-bedf-43fe-a12d-2cfadf6bc6eb")
+    def test_fail_to_connect_to_passpoint_network_when_not_approved(self):
+        """
+        Adds a passpoint network suggestion and ensure that the device does not
+        connect to it until we approve the app.
+
+        Steps:
+        1. Send a network suggestion to the device with the app not approved.
+        2. Ensure the network is present in scan results, but we don't connect
+           to it.
+        3. Now approve the app.
+        4. Wait for the device to connect to it.
+        """
+        asserts.skip_if(not hasattr(self, "passpoint_networks"),
+                        "No passpoint networks, skip this test")
+        passpoint_config = self.passpoint_networks[ATT]
+        if "carrierId" in passpoint_config:
+            self.set_carrier_approved(passpoint_config["carrierId"], True)
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([passpoint_config]),
+            "Failed to add suggestions")
+
+        # Disable suggestions by the app.
+        self.set_approved(False)
+
+        # Ensure the app is not approved.
+        asserts.assert_false(
+            self.is_approved(),
+            "Suggestions should be disabled")
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, passpoint_config[WifiEnums.SSID_KEY])
+
+        # Ensure we don't connect to the network.
+        asserts.assert_false(
+            wutils.wait_for_connect(
+                self.dut, passpoint_config[WifiEnums.SSID_KEY], assert_on_fail=False),
+            "Should not connect to network suggestions from unapproved app")
+
+        self.dut.log.info("Enabling suggestions from test");
+        # Now Enable suggestions by the app & ensure we connect to the network.
+        self.set_approved(True)
+
+        # Ensure the app is approved.
+        asserts.assert_true(
+            self.is_approved(),
+            "Suggestions should be enabled")
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, passpoint_config[WifiEnums.SSID_KEY])
+        time.sleep(PASSPOINT_TIMEOUT)
+        wutils.wait_for_connect(self.dut, passpoint_config[WifiEnums.SSID_KEY])
+        if "carrierId" in passpoint_config:
+            self.clear_carrier_approved(passpoint_config["carrierId"])
+
+    @test_tracker_info(uuid="cf624cda-4d25-42f1-80eb-6c717fb08338")
+    def test_fail_to_connect_to_passpoint_network_when_imsi_protection_exemption_not_approved(self):
+        """
+        Adds a passpoint network suggestion using SIM credential without IMSI privacy protection.
+        Before user approves the exemption, ensure that the device does noconnect to it until we
+        approve the carrier exemption.
+
+        Steps:
+        1. Send a network suggestion to the device with IMSI protection exemption not approved.
+        2. Ensure the network is present in scan results, but we don't connect
+           to it.
+        3. Now approve the carrier.
+        4. Wait for the device to connect to it.
+        """
+        asserts.skip_if(not hasattr(self, "passpoint_networks"),
+                        "No passpoint networks, skip this test")
+        passpoint_config = self.passpoint_networks[ATT]
+        asserts.skip_if("carrierId" not in passpoint_config,
+                        "Not a SIM based passpoint network, skip this test")
+
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([passpoint_config]),
+            "Failed to add suggestions")
+
+        # Ensure the carrier imsi protection exemption is not approved.
+        asserts.assert_false(
+            self.is_carrier_approved(passpoint_config["carrierId"]),
+            "Carrier should not be approved")
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, passpoint_config[WifiEnums.SSID_KEY])
+
+        # Ensure we don't connect to the network.
+        asserts.assert_false(
+            wutils.wait_for_connect(
+                self.dut, passpoint_config[WifiEnums.SSID_KEY], assert_on_fail=False),
+            "Should not connect to network suggestions from unapproved app")
+
+        self.dut.log.info("Enabling suggestions from test")
+        # Now approve IMSI protection exemption by carrier & ensure we connect to the network.
+        self.set_carrier_approved(passpoint_config["carrierId"], True)
+
+        # Ensure the carrier is approved.
+        asserts.assert_true(
+            self.is_carrier_approved(passpoint_config["carrierId"]),
+            "Carrier should be approved")
+
+        # Start a new scan to trigger auto-join.
+        wutils.start_wifi_connection_scan_and_ensure_network_found(
+            self.dut, passpoint_config[WifiEnums.SSID_KEY])
+        time.sleep(PASSPOINT_TIMEOUT)
+        wutils.wait_for_connect(self.dut, passpoint_config[WifiEnums.SSID_KEY])
+        self.clear_carrier_approved(passpoint_config["carrierId"])
+
+    @test_tracker_info(uuid="e35f99c8-78a4-4b96-9258-f9834b6ddd33")
+    def test_initial_auto_join_on_network_suggestion(self):
+        """
+        Add a network suggestion with enableAutojoin bit set to false, ensure the device doesn't
+        auto connect to this network
+
+        Steps:
+        1. Create a network suggestion.
+        2. Set EnableAutojoin to false.
+        3. Add this suggestion
+        4. Ensure device doesn't connect to his network
+        """
+        network_suggestion = self.wpa_psk_5g
+        # Set suggestion auto join initial to false.
+        network_suggestion["enableAutojoin"] = False
+        self.dut.log.info("Adding network suggestions")
+        asserts.assert_true(
+            self.dut.droid.wifiAddNetworkSuggestions([network_suggestion]),
+            "Failed to add suggestions")
+        # Enable suggestions by the app.
+        self.dut.log.debug("Enabling suggestions from test")
+        self.set_approved(True)
+        wutils.start_wifi_connection_scan_and_return_status(self.dut)
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut, network_suggestion[WifiEnums.SSID_KEY],
+                                    assert_on_fail=False), "Device should not connect.")
+
+    @test_tracker_info(uuid="ff4e451f-a380-4ff5-a5c2-dd9b1633d5e5")
+    def test_user_override_auto_join_on_network_suggestion(self):
+        """
+        Add a network suggestion, user change the auto join to false, ensure the device doesn't
+        auto connect to this network
+
+        Steps:
+        1. Create a network suggestion.
+        2. Add this suggestion, and ensure we connect to this network
+        3. Simulate user change the auto join to false.
+        4. Toggle the Wifi off and on
+        4. Ensure device doesn't connect to his network
+        """
+        network_suggestion = self.wpa_psk_5g
+        self.add_suggestions_and_ensure_connection([network_suggestion],
+                                                   network_suggestion[WifiEnums.SSID_KEY], False)
+        wifi_info = self.dut.droid.wifiGetConnectionInfo()
+        self.dut.log.info(wifi_info)
+        network_id = wifi_info[WifiEnums.NETID_KEY]
+        # Simulate user disable auto join through Settings.
+        self.dut.log.info("Disable auto join on suggestion")
+        self.dut.droid.wifiEnableAutojoin(network_id, False)
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.wifi_toggle_state(self.dut, True)
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut, network_suggestion[WifiEnums.SSID_KEY],
+                                    assert_on_fail=False), "Device should not connect.")
diff --git a/acts/tests/google/wifi/WifiPasspointTest.py b/acts/tests/google/wifi/WifiPasspointTest.py
index b867faa..afadb79 100755
--- a/acts/tests/google/wifi/WifiPasspointTest.py
+++ b/acts/tests/google/wifi/WifiPasspointTest.py
@@ -119,20 +119,18 @@
         """
         ad = self.dut
         ad.ed.clear_all_events()
-        wutils.start_wifi_connection_scan(ad)
-        scan_results = ad.droid.wifiGetScanResults()
-        # Wait for scan to complete.
-        time.sleep(5)
-        ssid = passpoint_network
-        wutils.assert_network_in_list({WifiEnums.SSID_KEY: ssid}, scan_results)
-        # Passpoint network takes longer time to connect than normal networks.
-        # Every try comes with a timeout of 30s. Setting total timeout to 120s.
-        wutils.wifi_passpoint_connect(self.dut, passpoint_network, num_of_tries=4)
+        try:
+            wutils.start_wifi_connection_scan_and_return_status(ad)
+            wutils.wait_for_connect(ad)
+        except:
+            pass
         # Re-verify we are connected to the correct network.
         network_info = self.dut.droid.wifiGetConnectionInfo()
-        if network_info[WifiEnums.SSID_KEY] != passpoint_network:
-            raise signals.TestFailure("Device did not connect to the passpoint"
-                                      " network.")
+        self.log.info("Network Info: %s" % network_info)
+        if not network_info or not network_info[WifiEnums.SSID_KEY] or \
+            network_info[WifiEnums.SSID_KEY] not in passpoint_network:
+              raise signals.TestFailure(
+                  "Device did not connect to passpoint network.")
 
 
     def get_configured_passpoint_and_delete(self):
@@ -395,3 +393,26 @@
     @test_tracker_info(uuid="f43ea759-673f-4567-aa11-da3bc2cabf08")
     def test_start_subscription_provisioning_and_toggle_wifi(self):
         self.start_subscription_provisioning(TOGGLE)
+
+    @test_tracker_info(uuid="ad6d5eb8-a3c5-4ce0-9e10-d0f201cd0f40")
+    def test_user_override_auto_join_on_passpoint_network(self):
+        """Add a Passpoint network, simulate user change the auto join to false, ensure the device
+        doesn't auto connect to this passponit network
+
+        Steps:
+            1. Install a Passpoint Profile.
+            2. Verify the device connects to the required Passpoint SSID.
+            3. Disable auto join Passpoint configuration using its FQDN.
+            4. disable and enable Wifi toggle, ensure we don't connect back
+        """
+        passpoint_config = self.passpoint_networks[BOINGO]
+        self.install_passpoint_profile(passpoint_config)
+        ssid = passpoint_config[WifiEnums.SSID_KEY]
+        self.check_passpoint_connection(ssid)
+        self.dut.log.info("Disable auto join on passpoint")
+        self.dut.droid.wifiEnableAutojoinPasspoint(passpoint_config['fqdn'], False)
+        wutils.wifi_toggle_state(self.dut, False)
+        wutils.wifi_toggle_state(self.dut, True)
+        asserts.assert_false(
+            wutils.wait_for_connect(self.dut, ssid, assert_on_fail=False),
+            "Device should not connect.")
diff --git a/acts/tests/google/wifi/WifiRoamingTest.py b/acts/tests/google/wifi/WifiRoamingTest.py
index 4a64ec1..d7bec90 100644
--- a/acts/tests/google/wifi/WifiRoamingTest.py
+++ b/acts/tests/google/wifi/WifiRoamingTest.py
@@ -16,6 +16,8 @@
 import pprint
 import random
 import time
+from acts import context
+from scapy.all import *
 
 from acts import asserts
 from acts import base_test
@@ -25,6 +27,11 @@
 from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
 
 WifiEnums = wutils.WifiEnums
+DEF_ATTN = 60
+MAX_ATTN = 95
+ROAM_DBM = -75
+WAIT_AFTER_ATTN = 12
+ATTN_STEP = 5
 
 class WifiRoamingTest(WifiBaseTest):
 
@@ -54,7 +61,8 @@
         asserts.assert_true(
             len(self.open_network) > 1,
             "Need at least two open networks for roaming")
-        wutils.wifi_toggle_state(self.dut, True)
+
+        self.configure_packet_capture()
 
     def teardown_class(self):
         self.dut.ed.clear_all_events()
@@ -71,6 +79,8 @@
         self.dut.droid.wakeLockRelease()
         self.dut.droid.goToSleepNow()
         wutils.reset_wifi(self.dut)
+        for a in self.attenuators:
+            a.set_atten(0)
 
     def on_fail(self, test_name, begin_time):
         self.dut.cat_adb_log(test_name, begin_time)
@@ -91,10 +101,87 @@
         5. Validate connection information and ping.
         """
         wutils.set_attns(self.attenuators, "AP1_on_AP2_off")
-        wutils.wifi_connect(self.dut, AP1_network)
+        wutils.connect_to_wifi_network(self.dut, AP1_network)
         self.log.info("Roaming from %s to %s", AP1_network, AP2_network)
-        wutils.trigger_roaming_and_validate(self.dut, self.attenuators,
-            "AP1_off_AP2_on", AP2_network)
+        self.trigger_roaming_and_verify_attenuation(AP1_network)
+        self.validate_roaming(AP2_network)
+
+    def get_rssi(self, pcap_file, expected_bssid):
+        """Get signal strength of the wifi network attenuated.
+
+        Args:
+            pcap_file: PCAP file path.
+            expected_bssid: BSSID of the wifi network attenuated.
+        """
+        packets = []
+        try:
+            packets = rdpcap(pcap_file)
+        except Scapy_Exception:
+            self.log.error("Failed to read pcap file")
+        if not packets:
+            return 0
+
+        dbm = -100
+        for pkt in packets:
+            if pkt and hasattr(pkt, 'type') and pkt.type == 0 and \
+                pkt.subtype == 8 and hasattr(pkt, 'info'):
+                  bssid = pkt.addr3
+                  if expected_bssid == bssid:
+                      dbm = int(pkt.dBm_AntSignal)
+        self.log.info("RSSI: %s" % dbm)
+        return dbm
+
+    def trigger_roaming_and_verify_attenuation(self, network):
+        """Trigger roaming and verify signal strength is below roaming limit.
+
+        Args:
+            network: Wifi network that is being attenuated.
+        """
+        wutils.set_attns_steps(self.attenuators, "AP1_off_AP2_on")
+        band = '5G' if network['SSID'].startswith('5g_') else '2G'
+        attn = DEF_ATTN + ATTN_STEP
+        while attn <= MAX_ATTN:
+            self.pcap_procs = wutils.start_pcap(
+                self.packet_capture, 'dual', self.test_name)
+            time.sleep(WAIT_AFTER_ATTN/3)
+            wutils.stop_pcap(self.packet_capture, self.pcap_procs, False)
+            pcap_file = os.path.join(
+                context.get_current_context().get_full_output_path(),
+                'PacketCapture',
+                '%s_%s.pcap' % (self.test_name, band))
+
+            rssi = self.get_rssi(pcap_file, network["bssid"])
+            if rssi == 0:
+                self.log.error("Failed to verify signal strength")
+                break
+            if self.get_rssi(pcap_file, network["bssid"]) < ROAM_DBM:
+                break
+
+            self.attenuators[0].set_atten(attn)
+            self.attenuators[1].set_atten(attn)
+            time.sleep(WAIT_AFTER_ATTN) # allow some time for attenuation
+            attn += 5
+
+    def validate_roaming(self, expected_con):
+        """Validate roaming.
+
+        Args:
+            expected_con: Expected wifi network after roaming.
+        """
+        expected_con = {
+            WifiEnums.SSID_KEY: expected_con[WifiEnums.SSID_KEY],
+            WifiEnums.BSSID_KEY: expected_con["bssid"],
+        }
+        curr_con = self.dut.droid.wifiGetConnectionInfo()
+        for key in expected_con:
+            if expected_con[key] != curr_con[key]:
+                asserts.fail("Expected '%s' to be %s, actual is %s." %
+                             (key, expected_con[key], curr_con[key]))
+        self.log.info("Roamed to %s successfully",
+                      expected_con[WifiEnums.BSSID_KEY])
+        if not wutils.validate_connection(self.dut):
+            raise signals.TestFailure("Fail to connect to internet on %s" %
+                                      expected_con[WifiEnums.BSSID_KEY])
 
     """ Tests Begin.
 
diff --git a/acts/tests/google/wifi/WifiScannerMultiScanTest.py b/acts/tests/google/wifi/WifiScannerMultiScanTest.py
index b0fc246..f8804bb 100755
--- a/acts/tests/google/wifi/WifiScannerMultiScanTest.py
+++ b/acts/tests/google/wifi/WifiScannerMultiScanTest.py
@@ -408,6 +408,7 @@
     """ Tests Begin """
 
     @test_tracker_info(uuid="d490b146-5fc3-4fc3-9958-78ba0ad63211")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_two_scans_at_same_interval(self):
         """Perform two WifiScanner background scans, one at 2.4GHz and the other
         at 5GHz, the same interval and number of BSSIDs per scan.
@@ -437,6 +438,7 @@
         self.scan_and_validate_results(scan_settings)
 
     @test_tracker_info(uuid="0ec9a554-f942-41a9-8096-6b0b400f60b0")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_two_scans_at_different_interval(self):
         """Perform two WifiScanner background scans, one at 2.4GHz and the other
         at 5GHz, different interval and number of BSSIDs per scan.
@@ -466,6 +468,7 @@
         self.scan_and_validate_results(scan_settings)
 
     @test_tracker_info(uuid="0d616591-0d32-4be6-8fd4-e4a5e9ccdce0")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_scans_24GHz_and_both(self):
         """Perform two WifiScanner background scans, one at 2.4GHz and
            the other at both 2.4GHz and 5GHz
@@ -495,6 +498,7 @@
         self.scan_and_validate_results(scan_settings)
 
     @test_tracker_info(uuid="ddcf959e-512a-4e86-b3d3-18cebd0b22a0")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_scans_5GHz_and_both(self):
         """Perform two WifiScanner scans, one at 5GHz and the other at both
            2.4GHz and 5GHz
@@ -524,6 +528,7 @@
         self.scan_and_validate_results(scan_settings)
 
     @test_tracker_info(uuid="060469f1-fc6b-4255-ab6e-b1d5b54db53d")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_scans_24GHz_5GHz_and_DFS(self):
         """Perform three WifiScanner scans, one at 5GHz, one at 2.4GHz and the
         other at just 5GHz DFS channels
@@ -557,6 +562,7 @@
         self.scan_and_validate_results(scan_settings)
 
     @test_tracker_info(uuid="14104e98-27a0-43d5-9525-b36b65ac3957")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_scans_batch_and_24GHz(self):
         """Perform two WifiScanner background scans, one in batch mode for both
         bands and the other in periodic mode at 2.4GHz
@@ -588,6 +594,7 @@
         self.scan_and_validate_results(scan_settings)
 
     @test_tracker_info(uuid="cd6064b5-840b-4334-8cd4-8320a6cda52f")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_scans_batch_and_5GHz(self):
         """Perform two WifiScanner background scans, one in batch mode for both
         bands and the other in periodic mode at 5GHz
@@ -619,6 +626,7 @@
         self.scan_and_validate_results(scan_settings)
 
     @test_tracker_info(uuid="9f48cb0c-de87-4cd2-9e50-857579d44079")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_scans_24GHz_5GHz_full_result(self):
         """Perform two WifiScanner background scans, one at 2.4GHz and
            the other at 5GHz. Report full scan results.
diff --git a/acts/tests/google/wifi/WifiScannerScanTest.py b/acts/tests/google/wifi/WifiScannerScanTest.py
index eaef028..05945dc 100755
--- a/acts/tests/google/wifi/WifiScannerScanTest.py
+++ b/acts/tests/google/wifi/WifiScannerScanTest.py
@@ -216,6 +216,11 @@
                 "timestamp"] + scan_time_mic
             self.log.debug("max_scan_interval: %s", max_scan_interval)
             for result in batch["ScanResults"]:
+                # Though the tests are run in shield box, there are leakes
+                # from outside environment. This would ignore any such SSIDs
+                ssid = result["SSID"]
+                if not ssid.startswith("2g_") or not ssid.startswith("5g_"):
+                    continue
                 if (result["frequency"] not in scan_channels or
                         result["timestamp"] > max_scan_interval or
                         result["timestamp"] < scan_rt * 1000 or
@@ -767,6 +772,7 @@
         self.wifi_scanner_batch_scan_full(scan_settings[0])
 
     @test_tracker_info(uuid="e9a7cfb5-21c4-4c40-8169-8d88b65a1dee")
+    @WifiBaseTest.wifi_test_wrap
     def test_single_scan_while_pno(self):
         """Test wifi scanner single scan parallel to PNO connection.
 
@@ -927,6 +933,7 @@
         self.wifi_scanner_single_scan(scan_setting)
 
     @test_tracker_info(uuid="7c8da0c4-dec7-4d04-abd4-f8ea467a5c6d")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_scanner_dual_radio_low_latency(self):
         """Test WiFi scanner single scan for mix channel with default setting
            parameters.
@@ -946,6 +953,7 @@
         self.wifi_scanner_single_scan_full(scan_setting)
 
     @test_tracker_info(uuid="58b49b01-851b-4e45-b218-9fd27c0be921")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_scanner_dual_radio_low_power(self):
         """Test WiFi scanner single scan for mix channel with default setting
            parameters.
@@ -965,6 +973,7 @@
         self.wifi_scanner_single_scan_full(scan_setting)
 
     @test_tracker_info(uuid="3e7288bc-45e4-497c-bf3a-977eec4e896e")
+    @WifiBaseTest.wifi_test_wrap
     def test_wifi_scanner_dual_radio_high_accuracy(self):
         """Test WiFi scanner single scan for mix channel with default setting
            parameters.
diff --git a/acts/tests/google/wifi/WifiSoftApAcsTest.py b/acts/tests/google/wifi/WifiSoftApAcsTest.py
index 3c08310..c130098 100644
--- a/acts/tests/google/wifi/WifiSoftApAcsTest.py
+++ b/acts/tests/google/wifi/WifiSoftApAcsTest.py
@@ -69,8 +69,17 @@
                      "iperf_server_port"]
         self.unpack_userparams(
             req_param_names=req_params, opt_param_names=opt_param)
+        self.chan_map = {v: k for k, v in hostapd_constants.CHANNEL_MAP.items()}
+        self.pcap_procs = None
 
     def setup_test(self):
+        if hasattr(self, 'packet_capture'):
+            chan = self.test_name.split('_')[-1]
+            if chan.isnumeric():
+                band = '2G' if self.chan_map[int(chan)] < 5000 else '5G'
+                self.packet_capture[0].configure_monitor_mode(band, int(chan))
+                self.pcap_procs = wutils.start_pcap(
+                    self.packet_capture[0], band, self.test_name)
         self.dut.droid.wakeLockAcquireBright()
         self.dut.droid.wakeUpNow()
 
@@ -80,6 +89,9 @@
         wutils.stop_wifi_tethering(self.dut)
         wutils.reset_wifi(self.dut)
         wutils.reset_wifi(self.dut_client)
+        if hasattr(self, 'packet_capture') and self.pcap_procs:
+            wutils.stop_pcap(self.packet_capture[0], self.pcap_procs, False)
+            self.pcap_procs = None
         try:
             if "AccessPoint" in self.user_params:
                 del self.user_params["reference_networks"]
@@ -155,12 +167,13 @@
         """
         if "AccessPoint" in self.user_params:
             if not channel_2g:
-                self.legacy_configure_ap_and_start(channel_5g=channel_5g)
-            elif not channel_5g:
-                self.legacy_configure_ap_and_start(channel_2g=channel_2g)
-            else:
-                self.legacy_configure_ap_and_start(channel_2g=channel_2g,
-                    channel_5g=chanel_5g)
+                channel_2g = hostapd_constants.AP_DEFAULT_CHANNEL_2G
+            if not channel_5g:
+                channel_5g = hostapd_constants.AP_DEFAULT_CHANNEL_5G
+            self.legacy_configure_ap_and_start(wpa_network=True,
+                                               wep_network=True,
+                                               channel_2g=channel_2g,
+                                               channel_5g=channel_5g)
 
     def start_traffic_and_softap(self, network, softap_band):
         """Start iPerf traffic on client dut, during softAP bring-up on dut.
diff --git a/acts/tests/google/wifi/WifiSoftApTest.py b/acts/tests/google/wifi/WifiSoftApTest.py
index ec1922d..de14eb3 100644
--- a/acts/tests/google/wifi/WifiSoftApTest.py
+++ b/acts/tests/google/wifi/WifiSoftApTest.py
@@ -90,9 +90,8 @@
             del self.user_params["open_network"]
 
     def setup_test(self):
-        # Set country code explicitly to "US".
-        wutils.set_wifi_country_code(self.dut, wutils.WifiEnums.CountryCode.US)
-        wutils.set_wifi_country_code(self.dut_client, wutils.WifiEnums.CountryCode.US)
+        for ad in self.android_devices:
+            wutils.wifi_toggle_state(ad, True)
 
     def teardown_test(self):
         self.dut.log.debug("Toggling Airplane mode OFF.")
@@ -400,6 +399,7 @@
         asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
                              "SoftAp is not reported as running")
         # local hotspot may not have internet connectivity
+        self.confirm_softap_in_scan_results(config[wutils.WifiEnums.SSID_KEY])
         wutils.wifi_connect(self.dut_client, config, check_connectivity=False)
         wutils.stop_wifi_tethering(self.dut)
         wutils.wait_for_disconnect(self.dut_client)
@@ -572,7 +572,7 @@
     def test_softap_auto_shut_off(self):
         """Test for softap auto shut off
 
-        1. Turn of hotspot
+        1. Turn off hotspot
         2. Register softap callback
         3. Let client connect to the hotspot
         4. Start wait [wifi_constants.DEFAULT_SOFTAP_TIMEOUT_S] seconds
diff --git a/acts/tests/google/wifi/WifiStaApConcurrencyTest.py b/acts/tests/google/wifi/WifiStaApConcurrencyTest.py
index e16e253..c5d7424 100644
--- a/acts/tests/google/wifi/WifiStaApConcurrencyTest.py
+++ b/acts/tests/google/wifi/WifiStaApConcurrencyTest.py
@@ -14,29 +14,28 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
-import itertools
 import pprint
-import queue
 import time
 
-import acts.base_test
+from acts import asserts
+from acts import base_test
+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
 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
-
 WifiEnums = wutils.WifiEnums
-
+WLAN = "wlan0"
 # Channels to configure the AP for various test scenarios.
 WIFI_NETWORK_AP_CHANNEL_2G = 1
 WIFI_NETWORK_AP_CHANNEL_5G = 36
 WIFI_NETWORK_AP_CHANNEL_5G_DFS = 132
 
+
 class WifiStaApConcurrencyTest(WifiBaseTest):
     """Tests for STA + AP concurrency scenarios.
 
@@ -50,53 +49,35 @@
 
         self.dut = self.android_devices[0]
         self.dut_client = self.android_devices[1]
-        wutils.wifi_test_device_init(self.dut)
-        wutils.wifi_test_device_init(self.dut_client)
+
         # Do a simple version of init - mainly just sync the time and enable
         # verbose logging.  This test will fail if the DUT has a sim and cell
         # data is disabled.  We would also like to test with phones in less
         # constrained states (or add variations where we specifically
         # constrain).
-        utils.require_sl4a((self.dut, self.dut_client))
-        utils.sync_device_time(self.dut)
-        utils.sync_device_time(self.dut_client)
-        # Set country code explicitly to "US".
-        wutils.set_wifi_country_code(self.dut, wutils.WifiEnums.CountryCode.US)
-        wutils.set_wifi_country_code(self.dut_client, wutils.WifiEnums.CountryCode.US)
-        # Enable verbose logging on the duts
-        self.dut.droid.wifiEnableVerboseLogging(1)
-        asserts.assert_equal(self.dut.droid.wifiGetVerboseLoggingLevel(), 1,
-            "Failed to enable WiFi verbose logging on the softap dut.")
-        self.dut_client.droid.wifiEnableVerboseLogging(1)
-        asserts.assert_equal(self.dut_client.droid.wifiGetVerboseLoggingLevel(), 1,
-            "Failed to enable WiFi verbose logging on the client dut.")
+        utils.require_sl4a(self.android_devices)
 
-        req_params = ["AccessPoint", "dbs_supported_models"]
-        opt_param = ["iperf_server_address", "iperf_server_port"]
-        self.unpack_userparams(
-            req_param_names=req_params, opt_param_names=opt_param)
+        for ad in self.android_devices:
+            wutils.wifi_test_device_init(ad)
+            utils.sync_device_time(ad)
+            # Set country code explicitly to "US".
+            wutils.set_wifi_country_code(ad, WifiEnums.CountryCode.US)
+            # Enable verbose logging on the duts.
+            ad.droid.wifiEnableVerboseLogging(1)
 
+        req_params = ["dbs_supported_models",
+                      "iperf_server_address",
+                      "iperf_server_port"]
+        self.unpack_userparams(req_param_names=req_params,)
         asserts.abort_class_if(
             self.dut.model not in self.dbs_supported_models,
             "Device %s does not support dual interfaces." % self.dut.model)
 
-        # Set the client wifi state to on before the test begins.
-        wutils.wifi_toggle_state(self.dut_client, True)
-
-        if len(self.android_devices) > 2:
-            wutils.wifi_test_device_init(self.android_devices[2])
-            utils.sync_device_time(self.android_devices[2])
-            wutils.set_wifi_country_code(self.android_devices[2], wutils.WifiEnums.CountryCode.US)
-            self.android_devices[2].droid.wifiEnableVerboseLogging(1)
-            asserts.assert_equal(self.android_devices[2].droid.wifiGetVerboseLoggingLevel(), 1,
-                "Failed to enable WiFi verbose logging on the client dut.")
-
     def setup_test(self):
         for ad in self.android_devices:
             ad.droid.wakeLockAcquireBright()
             ad.droid.wakeUpNow()
         self.turn_location_off_and_scan_toggle_off()
-        wutils.wifi_toggle_state(self.dut, False)
 
     def teardown_test(self):
         # Prevent the stop wifi tethering failure to block ap close
@@ -108,6 +89,8 @@
             ad.droid.wakeLockRelease()
             ad.droid.goToSleepNow()
             wutils.reset_wifi(ad)
+        self.turn_location_on_and_scan_toggle_on()
+        wutils.wifi_toggle_state(self.dut, True)
         self.access_points[0].close()
         del self.user_params["reference_networks"]
         del self.user_params["open_network"]
@@ -117,7 +100,8 @@
             ad.take_bug_report(test_name, begin_time)
             ad.cat_adb_log(test_name, begin_time)
 
-    """Helper Functions"""
+    ### Helper Functions ###
+
     def configure_ap(self, channel_2g=None, channel_5g=None):
         """Configure and bring up AP on required channel.
 
@@ -127,30 +111,27 @@
 
         """
         if not channel_2g:
-            self.legacy_configure_ap_and_start(channel_5g=channel_5g)
-        elif not channel_5g:
-            self.legacy_configure_ap_and_start(channel_2g=channel_2g)
-        else:
-            self.legacy_configure_ap_and_start(channel_2g=channel_2g,
-                channel_5g=channel_5g)
-        self.wpapsk_2g = self.reference_networks[0]["2g"]
-        self.wpapsk_5g = self.reference_networks[0]["5g"]
+            channel_2g = hostapd_constants.AP_DEFAULT_CHANNEL_2G
+        if not channel_5g:
+            channel_5g = hostapd_constants.AP_DEFAULT_CHANNEL_5G
+        self.legacy_configure_ap_and_start(channel_2g=channel_2g,
+                                           channel_5g=channel_5g)
+        self.open_2g = self.open_network[0]["2g"]
+        self.open_5g = self.open_network[0]["5g"]
 
     def turn_location_on_and_scan_toggle_on(self):
-        """ Turns on wifi location scans.
-        """
-        acts.utils.set_location_service(self.dut, True)
+        """Turns on wifi location scans."""
+        utils.set_location_service(self.dut, True)
         self.dut.droid.wifiScannerToggleAlwaysAvailable(True)
         msg = "Failed to turn on location service's scan."
         asserts.assert_true(self.dut.droid.wifiScannerIsAlwaysAvailable(), msg)
 
     def turn_location_off_and_scan_toggle_off(self):
-        """ Turns off wifi location scans.
-        """
-        acts.utils.set_location_service(self.dut, False)
+        """Turns off wifi location scans."""
+        utils.set_location_service(self.dut, False)
         self.dut.droid.wifiScannerToggleAlwaysAvailable(False)
         msg = "Failed to turn off location service's scan."
-        asserts.assert_true(not self.dut.droid.wifiScannerIsAlwaysAvailable(), msg)
+        asserts.assert_false(self.dut.droid.wifiScannerIsAlwaysAvailable(), msg)
 
     def run_iperf_client(self, params):
         """Run iperf traffic after connection.
@@ -161,8 +142,8 @@
         if "iperf_server_address" in self.user_params:
             wait_time = 5
             network, ad = params
-            SSID = network[WifiEnums.SSID_KEY]
-            self.log.info("Starting iperf traffic through {}".format(SSID))
+            ssid = network[WifiEnums.SSID_KEY]
+            self.log.info("Starting iperf traffic through {}".format(ssid))
             time.sleep(wait_time)
             port_arg = "-p {}".format(self.iperf_server_port)
             success, data = ad.run_iperf_client(self.iperf_server_address,
@@ -170,42 +151,6 @@
             self.log.debug(pprint.pformat(data))
             asserts.assert_true(success, "Error occurred in iPerf traffic.")
 
-    def connect_to_wifi_network_and_verify(self, params):
-        """Connection logic for open and psk wifi networks.
-
-        Args:
-            params: A tuple of network info and AndroidDevice object.
-        """
-        network, ad = params
-        SSID = network[WifiEnums.SSID_KEY]
-        wutils.reset_wifi(ad)
-        wutils.start_wifi_connection_scan_and_ensure_network_found(
-            ad, SSID)
-        wutils.wifi_connect(ad, network, num_of_tries=3)
-        if len(self.android_devices) > 2:
-            wutils.reset_wifi(self.android_devices[2])
-            wutils.start_wifi_connection_scan_and_ensure_network_found(
-                    self.android_devices[2], SSID)
-            wutils.wifi_connect(self.android_devices[2], network)
-
-    def confirm_softap_can_be_connected(self, network, check_connectivity=True):
-        """Confirm the ap started by wifi tethering is seen in scan results.
-
-        Args:
-            network: config of the ap we are looking for.
-        """
-        SSID = network[WifiEnums.SSID_KEY]
-        wutils.reset_wifi(self.dut_client)
-        wutils.start_wifi_connection_scan_and_ensure_network_found(
-            self.dut_client, SSID)
-        wutils.wifi_connect(self.dut_client, network, check_connectivity=check_connectivity)
-        if len(self.android_devices) > 2:
-            wutils.reset_wifi(self.android_devices[2])
-            wutils.start_wifi_connection_scan_and_ensure_network_found(
-                    self.android_devices[2], SSID)
-            wutils.wifi_connect(
-                    self.android_devices[2], network, check_connectivity=check_connectivity)
-
     def create_softap_config(self):
         """Create a softap config with ssid and password."""
         ap_ssid = "softap_" + utils.rand_ascii_str(8)
@@ -216,84 +161,95 @@
         return config
 
     def start_softap_and_verify(self, band, check_connectivity=True):
-        """Test startup of softap
+        """Test startup of softap.
 
         1. Bring up AP mode.
         2. Verify SoftAP active using the client device.
+
+        Args:
+            band: wifi band to start soft ap on
+            check_connectivity: If set, verify internet connectivity
+
+        Returns:
+            Softap config
         """
         config = self.create_softap_config()
         wutils.start_wifi_tethering(self.dut,
-                                    config[wutils.WifiEnums.SSID_KEY],
-                                    config[wutils.WifiEnums.PWD_KEY], band)
-        self.confirm_softap_can_be_connected(config, check_connectivity)
+                                    config[WifiEnums.SSID_KEY],
+                                    config[WifiEnums.PWD_KEY],
+                                    band)
+        for ad in self.android_devices[1:]:
+            wutils.connect_to_wifi_network(
+                ad, config, check_connectivity=check_connectivity)
         return config
 
     def connect_to_wifi_network_and_start_softap(self, nw_params, softap_band):
         """Test concurrent wifi connection and softap.
+
         This helper method first makes a wifi connection and then starts SoftAp.
-
-        Args:
-            nw_params: Params for network STA connection.
-            softap_band: Band for the AP.
-
         1. Bring up wifi.
         2. Establish connection to a network.
         3. Bring up softap and verify AP can be connected by a client device.
         4. Run iperf on the wifi/softap connection to the network.
-        """
-        wutils.wifi_toggle_state(self.dut, True)
-        self.connect_to_wifi_network_and_verify((nw_params, self.dut))
-        softap_config = self.start_softap_and_verify(softap_band)
-        self.run_iperf_client((nw_params, self.dut))
-        self.run_iperf_client((softap_config, self.dut_client))
-        if len(self.android_devices) > 2:
-            self.log.info("Testbed has extra android devices, do more validation")
-            self.verify_traffic_between_softap_clients(
-                    self.dut_client, self.android_devices[2])
-        # Verify that both softap & wifi is enabled concurrently.
-        self.verify_wifi_and_softap_enabled()
-
-    def start_softap_and_connect_to_wifi_network(self, nw_params, softap_band):
-        """Test concurrent wifi connection and softap.
-        This helper method first starts SoftAp and then makes a wifi connection.
 
         Args:
             nw_params: Params for network STA connection.
             softap_band: Band for the AP.
+        """
+        wutils.connect_to_wifi_network(self.dut, nw_params)
+        softap_config = self.start_softap_and_verify(softap_band)
+        self.run_iperf_client((nw_params, self.dut))
+        self.run_iperf_client((softap_config, self.dut_client))
 
+        if len(self.android_devices) > 2:
+            self.log.info("Testbed has extra devices, do more validation")
+            self.verify_traffic_between_dut_clients(
+                self.dut_client, self.android_devices[2])
+
+        asserts.assert_true(self.dut.droid.wifiCheckState(),
+                            "Wifi is not reported as running")
+        asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
+                            "SoftAp is not reported as running")
+
+    def start_softap_and_connect_to_wifi_network(self, nw_params, softap_band):
+        """Test concurrent wifi connection and softap.
+
+        This helper method first starts SoftAp and then makes a wifi connection.
         1. Bring up softap and verify AP can be connected by a client device.
         2. Bring up wifi.
         3. Establish connection to a network.
         4. Run iperf on the wifi/softap connection to the network.
+        5. Verify wifi state and softap state.
+
+        Args:
+            nw_params: Params for network STA connection.
+            softap_band: Band for the AP.
         """
-        softap_config = self.start_softap_and_verify(softap_band, check_connectivity=False)
-        wutils.wifi_toggle_state(self.dut, True)
-        self.connect_to_wifi_network_and_verify((nw_params, self.dut))
+        softap_config = self.start_softap_and_verify(softap_band, False)
+        wutils.connect_to_wifi_network(self.dut, nw_params)
         self.run_iperf_client((nw_params, self.dut))
         self.run_iperf_client((softap_config, self.dut_client))
-        if len(self.android_devices) > 2:
-            self.log.info("Testbed has extra android devices, do more validation")
-            self.verify_traffic_between_ap_clients(
-                    self.dut, self.android_devices[2])
-        # Verify that both softap & wifi is enabled concurrently.
-        self.verify_wifi_and_softap_enabled()
 
-    def verify_wifi_and_softap_enabled(self):
-        """Helper to verify both wifi and softap is enabled
-        """
+        if len(self.android_devices) > 2:
+            self.log.info("Testbed has extra devices, do more validation")
+            self.verify_traffic_between_dut_clients(
+                self.dut, self.android_devices[2])
+
         asserts.assert_true(self.dut.droid.wifiCheckState(),
                             "Wifi is not reported as running")
         asserts.assert_true(self.dut.droid.wifiIsApEnabled(),
-                             "SoftAp is not reported as running")
+                            "SoftAp is not reported as running")
 
-    def verify_traffic_between_softap_clients(self, ad1, ad2, num_of_tries=2):
+    def verify_traffic_between_dut_clients(self, ad1, ad2, num_of_tries=2):
         """Test the clients that connect to DUT's softap can ping each other.
 
         Args:
+            ad1: DUT 1
+            ad2: DUT 2
             num_of_tries: the retry times of ping test.
         """
-        ad1_ip = ad1.droid.connectivityGetIPv4Addresses('wlan0')[0]
-        ad2_ip = ad2.droid.connectivityGetIPv4Addresses('wlan0')[0]
+        ad1_ip = ad1.droid.connectivityGetIPv4Addresses(WLAN)[0]
+        ad2_ip = ad2.droid.connectivityGetIPv4Addresses(WLAN)[0]
         # Ping each other
         for _ in range(num_of_tries):
             if utils.adb_shell_ping(ad1, count=10, dest_ip=ad2_ip, timeout=20):
@@ -306,148 +262,112 @@
         else:
             asserts.fail("%s ping %s failed" % (ad2.serial, ad1_ip))
 
-    def verify_traffic_between_ap_clients(
-            self, ad1, ad2, num_of_tries=2):
-        """Test the clients that connect to access point can ping each other.
+    ### Tests ###
 
-        Args:
-            num_of_tries: the retry times of ping test.
-        """
-        ad1_ip = ad1.droid.connectivityGetIPv4Addresses('wlan0')[0]
-        ad2_ip = ad2.droid.connectivityGetIPv4Addresses('wlan0')[0]
-        # Ping each other
-        for _ in range(num_of_tries):
-            if utils.adb_shell_ping(ad1, count=10, dest_ip=ad2_ip, timeout=20):
-                break
-        else:
-            asserts.fail("%s ping %s failed" % (ad1.serial, ad2_ip))
-        for _ in range(num_of_tries):
-            if utils.adb_shell_ping(ad2, count=10, dest_ip=ad1_ip, timeout=20):
-                break
-        else:
-            asserts.fail("%s ping %s failed" % (ad2.serial, ad1_ip))
-
-    """Tests"""
     @test_tracker_info(uuid="c396e7ac-cf22-4736-a623-aa6d3c50193a")
     def test_wifi_connection_2G_softap_2G(self):
-        """Tests connection to 2G network followed by bringing up SoftAp on 2G.
-        """
+        """Test connection to 2G network followed by SoftAp on 2G."""
         self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
         self.connect_to_wifi_network_and_start_softap(
-            self.wpapsk_2g, WIFI_CONFIG_APBAND_2G)
+            self.open_2g, WIFI_CONFIG_APBAND_2G)
 
     @test_tracker_info(uuid="1cd6120d-3db4-4624-9bae-55c976533a48")
     def test_wifi_connection_5G_softap_5G(self):
-        """Tests connection to 5G network followed by bringing up SoftAp on 5G.
-        """
+        """Test connection to 5G network followed by SoftAp on 5G."""
         self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
         self.connect_to_wifi_network_and_start_softap(
-            self.wpapsk_5g, WIFI_CONFIG_APBAND_5G)
+            self.open_5g, WIFI_CONFIG_APBAND_5G)
 
     @test_tracker_info(uuid="5f980007-3490-413e-b94e-7700ffab8534")
     def test_wifi_connection_5G_DFS_softap_5G(self):
-        """Tests connection to 5G DFS network followed by bringing up SoftAp on 5G.
-        """
+        """Test connection to 5G DFS network followed by SoftAp on 5G."""
         self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
         self.connect_to_wifi_network_and_start_softap(
-            self.wpapsk_5g, WIFI_CONFIG_APBAND_5G)
+            self.open_5g, WIFI_CONFIG_APBAND_5G)
 
     @test_tracker_info(uuid="d05d5d44-c738-4372-9f01-ce2a640a2f25")
     def test_wifi_connection_5G_softap_2G(self):
-        """Tests connection to 5G network followed by bringing up SoftAp on 2G.
-        """
+        """Test connection to 5G network followed by SoftAp on 2G."""
         self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
         self.connect_to_wifi_network_and_start_softap(
-            self.wpapsk_5g, WIFI_CONFIG_APBAND_2G)
+            self.open_5g, WIFI_CONFIG_APBAND_2G)
 
     @test_tracker_info(uuid="909ac713-1ad3-4dad-9be3-ad60f00ed25e")
     def test_wifi_connection_5G_DFS_softap_2G(self):
-        """Tests connection to 5G DFS network followed by bringing up SoftAp on 2G.
-        """
+        """Test connection to 5G DFS network followed by SoftAp on 2G."""
         self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
         self.connect_to_wifi_network_and_start_softap(
-            self.wpapsk_5g, WIFI_CONFIG_APBAND_2G)
+            self.open_5g, WIFI_CONFIG_APBAND_2G)
 
     @test_tracker_info(uuid="e8de724a-25d3-4801-94cc-22e9e0ecc8d1")
     def test_wifi_connection_2G_softap_5G(self):
-        """Tests connection to 2G network followed by bringing up SoftAp on 5G.
-        """
+        """Test connection to 2G network followed by SoftAp on 5G."""
         self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
         self.connect_to_wifi_network_and_start_softap(
-            self.wpapsk_2g, WIFI_CONFIG_APBAND_5G)
+            self.open_2g, WIFI_CONFIG_APBAND_5G)
 
     @test_tracker_info(uuid="647f4e17-5c7a-4249-98af-f791d163a39f")
     def test_wifi_connection_5G_softap_2G_with_location_scan_on(self):
-        """Tests connection to 5G network followed by bringing up SoftAp on 2G
-        with location scans turned on.
-        """
+        """Test connection to 5G network, SoftAp on 2G with location scan on."""
         self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
         self.turn_location_on_and_scan_toggle_on()
         self.connect_to_wifi_network_and_start_softap(
-            self.wpapsk_5g, WIFI_CONFIG_APBAND_2G)
+            self.open_5g, WIFI_CONFIG_APBAND_2G)
         # Now toggle wifi off & ensure we can still scan.
         wutils.wifi_toggle_state(self.dut, False)
         wutils.start_wifi_connection_scan_and_ensure_network_found(
-            self.dut, self.wpapsk_5g[WifiEnums.SSID_KEY])
+            self.dut, self.open_5g[WifiEnums.SSID_KEY])
 
     @test_tracker_info(uuid="4aa56c11-e5bc-480b-bd61-4b4ee577a5da")
     def test_softap_2G_wifi_connection_2G(self):
-        """Tests bringing up SoftAp on 2G followed by connection to 2G network.
-        """
+        """Test SoftAp on 2G followed by connection to 2G network."""
         self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
         self.start_softap_and_connect_to_wifi_network(
-            self.wpapsk_2g, WIFI_CONFIG_APBAND_2G)
+            self.open_2g, WIFI_CONFIG_APBAND_2G)
 
     @test_tracker_info(uuid="5f954957-ad20-4de1-b20c-6c97d0463bdd")
     def test_softap_5G_wifi_connection_5G(self):
-        """Tests bringing up SoftAp on 5G followed by connection to 5G network.
-        """
+        """Test SoftAp on 5G followed by connection to 5G network."""
         self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
         self.start_softap_and_connect_to_wifi_network(
-            self.wpapsk_5g, WIFI_CONFIG_APBAND_5G)
+            self.open_5g, WIFI_CONFIG_APBAND_5G)
 
     @test_tracker_info(uuid="1306aafc-a07e-4654-ba78-674f90cf748e")
     def test_softap_5G_wifi_connection_5G_DFS(self):
-        """Tests bringing up SoftAp on 5G followed by connection to 5G DFS network.
-        """
+        """Test SoftAp on 5G followed by connection to 5G DFS network."""
         self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
         self.start_softap_and_connect_to_wifi_network(
-            self.wpapsk_5g, WIFI_CONFIG_APBAND_5G)
+            self.open_5g, WIFI_CONFIG_APBAND_5G)
 
     @test_tracker_info(uuid="5e28e8b5-3faa-4cff-a782-13a796d7f572")
     def test_softap_5G_wifi_connection_2G(self):
-        """Tests bringing up SoftAp on 5G followed by connection to 2G network.
-        """
+        """Test SoftAp on 5G followed by connection to 2G network."""
         self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
         self.start_softap_and_connect_to_wifi_network(
-            self.wpapsk_2g, WIFI_CONFIG_APBAND_5G)
+            self.open_2g, WIFI_CONFIG_APBAND_5G)
 
     @test_tracker_info(uuid="a2c62bc6-9ccd-4bc4-8a23-9a1b5d0b4b5c")
     def test_softap_2G_wifi_connection_5G(self):
-        """Tests bringing up SoftAp on 2G followed by connection to 5G network.
-        """
+        """Test SoftAp on 2G followed by connection to 5G network."""
         self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G)
         self.start_softap_and_connect_to_wifi_network(
-            self.wpapsk_5g, WIFI_CONFIG_APBAND_2G)
+            self.open_5g, WIFI_CONFIG_APBAND_2G)
 
     @test_tracker_info(uuid="75400685-a9d9-4091-8af3-97bd539c246a")
     def test_softap_2G_wifi_connection_5G_DFS(self):
-        """Tests bringing up SoftAp on 2G followed by connection to 5G DFS network.
-        """
+        """Test SoftAp on 2G followed by connection to 5G DFS network."""
         self.configure_ap(channel_5g=WIFI_NETWORK_AP_CHANNEL_5G_DFS)
         self.start_softap_and_connect_to_wifi_network(
-            self.wpapsk_5g, WIFI_CONFIG_APBAND_2G)
+            self.open_5g, WIFI_CONFIG_APBAND_2G)
 
     @test_tracker_info(uuid="aa23a3fc-31a1-4d5c-8cf5-2eb9fdf9e7ce")
     def test_softap_5G_wifi_connection_2G_with_location_scan_on(self):
-        """Tests bringing up SoftAp on 5G followed by connection to 2G network
-        with location scans turned on.
-        """
+        """Test SoftAp on 5G, connection to 2G network with location scan on."""
         self.configure_ap(channel_2g=WIFI_NETWORK_AP_CHANNEL_2G)
         self.turn_location_on_and_scan_toggle_on()
         self.start_softap_and_connect_to_wifi_network(
-            self.wpapsk_2g, WIFI_CONFIG_APBAND_5G)
+            self.open_2g, WIFI_CONFIG_APBAND_5G)
         # Now toggle wifi off & ensure we can still scan.
         wutils.wifi_toggle_state(self.dut, False)
         wutils.start_wifi_connection_scan_and_ensure_network_found(
-            self.dut, self.wpapsk_2g[WifiEnums.SSID_KEY])
+            self.dut, self.open_2g[WifiEnums.SSID_KEY])
diff --git a/acts/tests/google/wifi/WifiWakeTest.py b/acts/tests/google/wifi/WifiWakeTest.py
index 13c6b2d..580af86 100644
--- a/acts/tests/google/wifi/WifiWakeTest.py
+++ b/acts/tests/google/wifi/WifiWakeTest.py
@@ -192,7 +192,7 @@
             self.dut.droid.wifiCheckState(),
             "Expect Wifi Wake to enable Wifi, but Wifi is disabled.")
 
-    @test_tracker_info(uuid="")
+    @test_tracker_info(uuid="3cecd1c5-54bc-44a2-86f7-ad84625bf094")
     def test_reconnect_wifi_network_suggestion(self):
         """Tests that Wifi Wake re-enables Wifi for app provided suggestion."""
         self.dut.log.info("Adding network suggestions");
diff --git a/acts/tests/google/wifi/aware/functional/DataPathTest.py b/acts/tests/google/wifi/aware/functional/DataPathTest.py
index 3f68376..cf939bf 100644
--- a/acts/tests/google/wifi/aware/functional/DataPathTest.py
+++ b/acts/tests/google/wifi/aware/functional/DataPathTest.py
@@ -2159,8 +2159,8 @@
 
     The NDPs are all OPEN (no encryption).
     """
-        asserts.assert_true(
-            len(self.android_devices) >= 3,
+        asserts.skip_if(
+            len(self.android_devices) < 3,
             'A minimum of 3 devices is needed to run the test, have %d' % len(
                 self.android_devices))
 
diff --git a/acts/tests/google/wifi/aware/functional/DiscoveryTest.py b/acts/tests/google/wifi/aware/functional/DiscoveryTest.py
index 5f01c80..d867443 100644
--- a/acts/tests/google/wifi/aware/functional/DiscoveryTest.py
+++ b/acts/tests/google/wifi/aware/functional/DiscoveryTest.py
@@ -1068,3 +1068,192 @@
             s_config=autils.create_discovery_config(
                 sub_service_name, aconsts.SUBSCRIBE_TYPE_PASSIVE),
             device_startup_offset=self.device_startup_offset)
+
+    ##########################################################
+
+    def exchange_messages(self, p_dut, p_disc_id, s_dut, s_disc_id, peer_id_on_sub, session_name):
+        """
+        Exchange message between Publisher and Subscriber on target discovery session
+
+    Args:
+      p_dut: Publisher device
+      p_disc_id: Publish discovery session id
+      s_dut: Subscriber device
+      s_disc_id: Subscribe discovery session id
+      peer_id_on_sub: Peer ID of the Publisher as seen on the Subscriber
+      session_name: dictionary of discovery session name base on role("pub" or "sub")
+                    {role: {disc_id: name}}
+    """
+        msg_template = "Hello {} from {} !"
+
+        # Message send from Subscriber to Publisher
+        s_to_p_msg = msg_template.format(session_name["pub"][p_disc_id],
+                                         session_name["sub"][s_disc_id])
+        s_dut.droid.wifiAwareSendMessage(s_disc_id,
+                                         peer_id_on_sub,
+                                         self.get_next_msg_id(),
+                                         s_to_p_msg,
+                                         self.msg_retx_count)
+        autils.wait_for_event(s_dut,
+                              autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_SENT, s_disc_id))
+        event = autils.wait_for_event(p_dut,
+                                      autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+                                                            p_disc_id))
+        asserts.assert_equal(
+            event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], s_to_p_msg,
+            "Message on service %s from Subscriber to Publisher "
+            "not received correctly" % session_name["pub"][p_disc_id])
+        peer_id_on_pub = event["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+        # Message send from Publisher to Subscriber
+        p_to_s_msg = msg_template.format(session_name["sub"][s_disc_id],
+                                         session_name["pub"][p_disc_id])
+        p_dut.droid.wifiAwareSendMessage(p_disc_id,
+                                         peer_id_on_pub,
+                                         self.get_next_msg_id(), p_to_s_msg,
+                                         self.msg_retx_count)
+        autils.wait_for_event(
+            p_dut, autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_SENT, p_disc_id))
+        event = autils.wait_for_event(s_dut,
+                                      autils.decorate_event(aconsts.SESSION_CB_ON_MESSAGE_RECEIVED,
+                                                            s_disc_id))
+        asserts.assert_equal(
+            event["data"][aconsts.SESSION_CB_KEY_MESSAGE_AS_STRING], p_to_s_msg,
+            "Message on service %s from Publisher to Subscriber"
+            "not received correctly" % session_name["sub"][s_disc_id])
+
+    def run_multiple_concurrent_services_same_name_diff_ssi(self, type_x, type_y):
+        """Validate same service name with multiple service specific info on publisher
+        and subscriber can see all service
+
+    - p_dut running Publish X and Y
+    - s_dut running subscribe A and B
+    - subscribe A find X and Y
+    - subscribe B find X and Y
+
+    Message exchanges:
+    - A to X and X to A
+    - B to X and X to B
+    - A to Y and Y to A
+    - B to Y and Y to B
+
+    Note: test requires that publisher device support 2 publish sessions concurrently,
+    and subscriber device support 2 subscribe sessions concurrently.
+    The test will be skipped if the devices are not capable.
+
+    Args:
+      type_x, type_y: A list of [ptype, stype] of the publish and subscribe
+                      types for services X and Y respectively.
+    """
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        asserts.skip_if(
+            p_dut.aware_capabilities[aconsts.CAP_MAX_PUBLISHES] < 2
+            or s_dut.aware_capabilities[aconsts.CAP_MAX_SUBSCRIBES] < 2,
+            "Devices do not support 2 publish sessions or 2 subscribe sessions")
+
+        SERVICE_NAME = "ServiceName"
+        X_SERVICE_SSI = "ServiceSpecificInfoXXX"
+        Y_SERVICE_SSI = "ServiceSpecificInfoYYY"
+        use_id = True
+
+        # attach and wait for confirmation
+        p_id = p_dut.droid.wifiAwareAttach(False, None, use_id)
+        autils.wait_for_event(p_dut, autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED, p_id))
+        time.sleep(self.device_startup_offset)
+        s_id = s_dut.droid.wifiAwareAttach(False, None, use_id)
+        autils.wait_for_event(s_dut, autils.decorate_event(aconsts.EVENT_CB_ON_ATTACHED, s_id))
+
+        # Publisher: start publishing both X & Y services and wait for confirmations
+        p_disc_id_x = p_dut.droid.wifiAwarePublish(
+            p_id, autils.create_discovery_config(SERVICE_NAME, type_x[0], X_SERVICE_SSI), use_id)
+        event = autils.wait_for_event(p_dut,
+                                      autils.decorate_event(
+                                          aconsts.SESSION_CB_ON_PUBLISH_STARTED, p_disc_id_x))
+
+        p_disc_id_y = p_dut.droid.wifiAwarePublish(
+            p_id, autils.create_discovery_config(SERVICE_NAME, type_x[0], Y_SERVICE_SSI), use_id)
+        event = autils.wait_for_event(p_dut,
+                                      autils.decorate_event(
+                                          aconsts.SESSION_CB_ON_PUBLISH_STARTED, p_disc_id_y))
+
+        # Subscriber: start subscribe session A
+        s_disc_id_a = s_dut.droid.wifiAwareSubscribe(
+            s_id, autils.create_discovery_config(SERVICE_NAME, type_x[1]), use_id)
+        autils.wait_for_event(s_dut, autils.decorate_event(
+            aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED, s_disc_id_a))
+
+        # Subscriber: start subscribe session B
+        s_disc_id_b = s_dut.droid.wifiAwareSubscribe(
+            p_id, autils.create_discovery_config(SERVICE_NAME, type_y[1]), use_id)
+        autils.wait_for_event(s_dut, autils.decorate_event(
+            aconsts.SESSION_CB_ON_SUBSCRIBE_STARTED, s_disc_id_b))
+
+        session_name = {"pub": {p_disc_id_x: "X", p_disc_id_y: "Y"},
+                        "sub": {s_disc_id_a: "A", s_disc_id_b: "B"}}
+
+        # Subscriber: subscribe session A & B wait for service discovery
+        # Number of results on each session should be exactly 2
+        results_a = {}
+        for i in range(2):
+            event = autils.wait_for_event(s_dut, autils.decorate_event(
+                aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_a))
+            results_a[
+                bytes(event["data"][
+                          aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode('utf-8')] = event
+        autils.fail_on_event(s_dut, autils.decorate_event(
+            aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_a))
+
+        results_b = {}
+        for i in range(2):
+            event = autils.wait_for_event(s_dut, autils.decorate_event(
+                aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_b))
+            results_b[
+                bytes(event["data"][
+                          aconsts.SESSION_CB_KEY_SERVICE_SPECIFIC_INFO]).decode('utf-8')] = event
+        autils.fail_on_event(s_dut, autils.decorate_event(
+            aconsts.SESSION_CB_ON_SERVICE_DISCOVERED, s_disc_id_b))
+
+        s_a_peer_id_for_p_x = results_a[X_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+        s_a_peer_id_for_p_y = results_a[Y_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+        s_b_peer_id_for_p_x = results_b[X_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+        s_b_peer_id_for_p_y = results_b[Y_SERVICE_SSI]["data"][aconsts.SESSION_CB_KEY_PEER_ID]
+
+        # Message exchange between Publisher and Subscribe
+        self.exchange_messages(p_dut, p_disc_id_x,
+                               s_dut, s_disc_id_a, s_a_peer_id_for_p_x, session_name)
+
+        self.exchange_messages(p_dut, p_disc_id_x,
+                               s_dut, s_disc_id_b, s_b_peer_id_for_p_x, session_name)
+
+        self.exchange_messages(p_dut, p_disc_id_y,
+                               s_dut, s_disc_id_a, s_a_peer_id_for_p_y, session_name)
+
+        self.exchange_messages(p_dut, p_disc_id_y,
+                               s_dut, s_disc_id_b, s_b_peer_id_for_p_y, session_name)
+
+        # Check no more messages
+        time.sleep(autils.EVENT_TIMEOUT)
+        autils.verify_no_more_events(p_dut, timeout=0)
+        autils.verify_no_more_events(s_dut, timeout=0)
+
+        ##########################################################
+
+    def test_multiple_concurrent_services_diff_ssi_unsolicited_passive(self):
+        """Multi service test on same service name but different Service Specific Info
+    - Unsolicited publish
+    - Passive subscribe
+    """
+        self.run_multiple_concurrent_services_same_name_diff_ssi(
+            type_x=[aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE],
+            type_y=[aconsts.PUBLISH_TYPE_UNSOLICITED, aconsts.SUBSCRIBE_TYPE_PASSIVE])
+
+    def test_multiple_concurrent_services_diff_ssi_solicited_active(self):
+        """Multi service test on same service name but different Service Specific Info
+    - Solicited publish
+    - Active subscribe
+    """
+        self.run_multiple_concurrent_services_same_name_diff_ssi(
+            type_x=[aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE],
+            type_y=[aconsts.PUBLISH_TYPE_SOLICITED, aconsts.SUBSCRIBE_TYPE_ACTIVE])
diff --git a/acts/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py b/acts/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py
index 074ca48..3898832 100644
--- a/acts/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py
+++ b/acts/tests/google/wifi/aware/functional/MacRandomNoLeakageTest.py
@@ -70,12 +70,13 @@
         pcaps = pcap_5g + pcap_2g
 
         # Verify factory MAC is not leaked in both 2G and 5G pcaps
-        for mac in factory_mac_addresses:
-            wutils.verify_mac_not_found_in_pcap(mac, pcaps)
+        ads = [self.android_devices[0], self.android_devices[1]]
+        for i, mac in enumerate(factory_mac_addresses):
+            wutils.verify_mac_not_found_in_pcap(ads[i], mac, pcaps)
 
         # Verify random MACs are being used and in pcaps
-        for mac in mac_addresses:
-            wutils.verify_mac_is_found_in_pcap(mac, pcaps)
+        for i, mac in enumerate(mac_addresses):
+            wutils.verify_mac_is_found_in_pcap(ads[i], mac, pcaps)
 
     def transfer_mac_format(self, mac):
         """add ':' to mac String, and transfer to lower case
diff --git a/acts/tests/google/wifi/aware/functional/NonConcurrencyTest.py b/acts/tests/google/wifi/aware/functional/NonConcurrencyTest.py
index f0286f2..4f40509 100644
--- a/acts/tests/google/wifi/aware/functional/NonConcurrencyTest.py
+++ b/acts/tests/google/wifi/aware/functional/NonConcurrencyTest.py
@@ -40,6 +40,7 @@
         AwareBaseTest.teardown_test(self)
         for ad in self.android_devices:
             ad.droid.wifiP2pClose()
+            ad.droid.connectivityStopTethering(0)
 
     def run_aware_then_incompat_service(self, is_p2p):
         """Run test to validate that a running Aware session terminates when an
@@ -81,6 +82,15 @@
         # expect an announcement about Aware non-availability
         autils.wait_for_event(dut, aconsts.BROADCAST_WIFI_AWARE_NOT_AVAILABLE)
 
+        # Wifi state and location mode changes should not make Aware available
+        wutils.wifi_toggle_state(dut, False)
+        utils.set_location_service(dut, False)
+        wutils.wifi_toggle_state(dut, True)
+        utils.set_location_service(dut, True)
+        autils.fail_on_event(dut, aconsts.BROADCAST_WIFI_AWARE_AVAILABLE)
+        asserts.assert_false(dut.droid.wifiIsAwareAvailable(),
+                             "Aware is available (it shouldn't be)")
+
         # try starting anyway (expect failure)
         dut.droid.wifiAwareAttach()
         autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACH_FAILED)
@@ -133,6 +143,7 @@
         p_id = dut.droid.wifiAwareAttach()
         autils.wait_for_event(dut, aconsts.EVENT_CB_ON_ATTACHED)
 
+        wutils.start_wifi_connection_scan_and_ensure_network_found(dut, ap_ssid)
         wutils.wifi_connect(dut, config, check_connectivity=False)
         autils.wait_for_event(dut, wconsts.WIFI_STATE_CHANGED)
 
diff --git a/acts/tests/google/wifi/aware/performance/ThroughputTest.py b/acts/tests/google/wifi/aware/performance/ThroughputTest.py
index a90734d..2dab276 100644
--- a/acts/tests/google/wifi/aware/performance/ThroughputTest.py
+++ b/acts/tests/google/wifi/aware/performance/ThroughputTest.py
@@ -406,3 +406,35 @@
             [self.PASSPHRASE, self.PASSPHRASE2], results=results)
         asserts.explicit_pass(
             "test_iperf_max_ndi_aware_only_passphrases passes", extras=results)
+
+    def run_test_traffic_latency_single_ndp_ib_aware_only_open(self):
+        """Measure IPv6 traffic latency performance(ping) on NDP between 2 devices.
+        Security config is open.
+        """
+        p_dut = self.android_devices[0]
+        p_dut.pretty_name = "publisher"
+        s_dut = self.android_devices[1]
+        s_dut.pretty_name = "subscriber"
+        ndp_info = autils.create_ib_ndp(p_dut,
+                                        s_dut,
+                                        autils.create_discovery_config(
+                                            self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+                                        autils.create_discovery_config(
+                                            self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+                                        self.device_startup_offset)
+        p_req_key = ndp_info[0]
+        s_req_key = ndp_info[1]
+        p_aware_if = ndp_info[2]
+        s_aware_if = ndp_info[3]
+        p_ipv6 = ndp_info[4]
+        s_ipv6 = ndp_info[5]
+        self.log.info("Interface names: P=%s, S=%s", p_aware_if, s_aware_if)
+        self.log.info("Interface addresses (IPv6): P=%s, S=%s", p_ipv6, s_ipv6)
+        self.log.info("Start ping %s from %s", s_ipv6, p_ipv6)
+        latency_result = autils.run_ping6(p_dut, s_ipv6)
+        self.log.info("The latency results are %s", latency_result)
+
+    def test_traffic_latency_single_ndp_ib_aware_only_open(self):
+        """Test IPv6 traffic latency performance on NDP with security config is open.
+        """
+        self.run_test_traffic_latency_single_ndp_ib_aware_only_open()
diff --git a/acts/tests/google/wifi/aware/stress/MessagesStressTest.py b/acts/tests/google/wifi/aware/stress/MessagesStressTest.py
index fbe95d4..153a81f 100644
--- a/acts/tests/google/wifi/aware/stress/MessagesStressTest.py
+++ b/acts/tests/google/wifi/aware/stress/MessagesStressTest.py
@@ -30,10 +30,18 @@
 
 class MessagesStressTest(AwareBaseTest):
     """Set of stress tests for Wi-Fi Aware L2 (layer 2) message exchanges."""
+    # Number of the message queue depth per Uid from framework
+    MESSAGE_QUEUE_DEPTH_PER_UID = 50
 
     # Number of iterations in the stress test (number of messages)
+    # Should be larger than MESSAGE_QUEUE_DEPTH_PER_UID
     NUM_ITERATIONS = 100
 
+    # Number of message to send per round to avoid exceed message queue depth limit
+    # Should be less than or equal to 1/2 of MESSAGE_QUEUE_DEPTH_PER_UID
+    NUM_PER_ROUND = 20
+    NUM_ROUNDS = 5
+
     # Maximum permitted percentage of messages which fail to be transmitted
     # correctly
     MAX_TX_FAILURE_PERCENTAGE = 2
@@ -172,39 +180,166 @@
             if data[KEY_TX_OK_COUNT] > 0:
                 results["tx_count_success"] = results["tx_count_success"] + 1
             if data[KEY_TX_OK_COUNT] > 1:
-                results["tx_count_duplicate_success"] = (
-                    results["tx_count_duplicate_success"] + 1)
+                results["tx_count_duplicate_success"] += 1
             if data[KEY_TX_FAIL_COUNT] > 0:
-                results["tx_count_fail"] = results["tx_count_fail"] + 1
+                results["tx_count_fail"] += 1
             if data[KEY_TX_FAIL_COUNT] > 1:
-                results[
-                    "tx_count_duplicate_fail"] = results["tx_count_duplicate_fail"] + 1
+                results["tx_count_duplicate_fail"] += 1
             if (data[KEY_TX_OK_COUNT] == 0 and data[KEY_TX_FAIL_COUNT] == 0
                     and data[KEY_ID] != -1):
-                results["tx_count_neither"] = results["tx_count_neither"] + 1
+                results["tx_count_neither"] += 1
             if data[KEY_TX_OK_COUNT] > 0 and data[KEY_RX_COUNT] == 0:
-                results["tx_count_tx_ok_but_no_rx"] = (
-                    results["tx_count_tx_ok_but_no_rx"] + 1)
+                results["tx_count_tx_ok_but_no_rx"] += 1
             if data[KEY_RX_COUNT] > 0:
-                results["rx_count"] = results["rx_count"] + 1
+                results["rx_count"] += 1
             if data[KEY_RX_COUNT] > 1:
-                results[
-                    "rx_count_duplicate"] = results["rx_count_duplicate"] + 1
+                results["rx_count_duplicate"] += 1
             if data[KEY_RX_COUNT] > 0 and data[KEY_TX_OK_COUNT] == 0:
-                results["rx_count_no_ok_tx_indication"] = (
-                    results["rx_count_no_ok_tx_indication"] + 1)
+                results["rx_count_no_ok_tx_indication"] += 1
             if data[KEY_RX_COUNT] > 0 and data[KEY_TX_FAIL_COUNT] > 0:
-                results["rx_count_fail_tx_indication"] = (
-                    results["rx_count_fail_tx_indication"] + 1)
+                results["rx_count_fail_tx_indication"] += 1
             if data[KEY_RX_COUNT] > 0 and data[KEY_ID] == -1:
-                results[
-                    "rx_count_no_tx_message"] = results["rx_count_no_tx_message"] + 1
+                results["rx_count_no_tx_message"] += 1
 
     #######################################################################
 
     @test_tracker_info(uuid="e88c060f-4ca7-41c1-935a-d3d62878ec0b")
-    def test_stress_message(self):
-        """Stress test for bi-directional message transmission and reception."""
+    def test_stress_message_no_throttling(self):
+        """Stress test for bi-directional message transmission and reception no throttling"""
+        p_dut = self.android_devices[0]
+        s_dut = self.android_devices[1]
+
+        # Start up a discovery session
+        discovery_data = autils.create_discovery_pair(
+            p_dut,
+            s_dut,
+            p_config=autils.create_discovery_config(
+                self.SERVICE_NAME, aconsts.PUBLISH_TYPE_UNSOLICITED),
+            s_config=autils.create_discovery_config(
+                self.SERVICE_NAME, aconsts.SUBSCRIBE_TYPE_PASSIVE),
+            device_startup_offset=self.device_startup_offset,
+            msg_id=self.get_next_msg_id())
+        p_id = discovery_data[0]
+        s_id = discovery_data[1]
+        p_disc_id = discovery_data[2]
+        s_disc_id = discovery_data[3]
+        peer_id_on_sub = discovery_data[4]
+        peer_id_on_pub = discovery_data[5]
+
+        # Store information on Tx & Rx messages
+        messages_by_msg = {}  # keyed by message text
+        # {text -> {id, tx_ok_count, tx_fail_count, rx_count}}
+        messages_by_id = {}  # keyed by message ID {id -> text}
+        iterations = 0
+        p_tx_ok_count_total = 0
+        p_tx_fail_count_total = 0
+        p_tx_unknown_id_total = 0
+        s_tx_ok_count_total = 0
+        s_tx_fail_count_total = 0
+        s_tx_unknown_id_total = 0
+
+        # First round will fill up the message queue
+        num_of_messages_this_round = self.MESSAGE_QUEUE_DEPTH_PER_UID
+
+        # send messages (one in each direction) in rounds to avoid exceed the queue limit
+        for j in range(self.NUM_ROUNDS):
+            for k in range(num_of_messages_this_round):
+                msg_p2s = "Message Publisher -> Subscriber #%d" % iterations
+                next_msg_id = self.get_next_msg_id()
+                self.init_info(msg_p2s, next_msg_id, messages_by_msg,
+                               messages_by_id)
+                p_dut.droid.wifiAwareSendMessage(p_disc_id, peer_id_on_pub,
+                                                 next_msg_id, msg_p2s, 0)
+
+                msg_s2p = "Message Subscriber -> Publisher #%d" % iterations
+                next_msg_id = self.get_next_msg_id()
+                self.init_info(msg_s2p, next_msg_id, messages_by_msg,
+                               messages_by_id)
+                s_dut.droid.wifiAwareSendMessage(s_disc_id, peer_id_on_sub,
+                                                 next_msg_id, msg_s2p, 0)
+                iterations += 1
+
+            # wait for message tx confirmation
+            (p_tx_ok_count, p_tx_fail_count, p_tx_unknown_id) = self.wait_for_tx_events(
+                p_dut, self.NUM_PER_ROUND, messages_by_msg, messages_by_id)
+            p_tx_ok_count_total += p_tx_ok_count
+            p_tx_fail_count_total += p_tx_fail_count
+            p_tx_unknown_id_total += p_tx_unknown_id
+            (s_tx_ok_count, s_tx_fail_count, s_tx_unknown_id) = self.wait_for_tx_events(
+                s_dut, self.NUM_PER_ROUND, messages_by_msg, messages_by_id)
+            s_tx_ok_count_total += s_tx_ok_count
+            s_tx_fail_count_total += s_tx_fail_count
+            s_tx_unknown_id_total += s_tx_unknown_id
+
+            num_of_messages_this_round = self.NUM_PER_ROUND
+
+        # wait for the rest message tx confirmation
+        p_tx_total = p_tx_ok_count_total + p_tx_fail_count_total + p_tx_unknown_id_total
+        s_tx_total = s_tx_ok_count_total + s_tx_fail_count_total + s_tx_unknown_id_total
+        (p_tx_ok_count, p_tx_fail_count, p_tx_unknown_id) = self.wait_for_tx_events(
+            p_dut, iterations - p_tx_total, messages_by_msg, messages_by_id)
+        (s_tx_ok_count, s_tx_fail_count, s_tx_unknown_id) = self.wait_for_tx_events(
+            s_dut, iterations - s_tx_total, messages_by_msg, messages_by_id)
+        p_tx_ok_count_total += p_tx_ok_count
+        p_tx_fail_count_total += p_tx_fail_count
+        p_tx_unknown_id_total += p_tx_unknown_id
+        s_tx_ok_count_total += s_tx_ok_count
+        s_tx_fail_count_total += s_tx_fail_count
+        s_tx_unknown_id_total += s_tx_unknown_id
+        self.log.info(
+            "Transmission done: pub=%d, sub=%d transmitted successfully",
+            p_tx_ok_count_total, s_tx_ok_count_total)
+
+        # wait for message rx confirmation (giving it the total number of messages
+        # transmitted rather than just those transmitted correctly since sometimes
+        # the Tx doesn't get that information correctly. I.e. a message the Tx
+        # thought was not transmitted correctly is actually received - missing ACK?
+        # bug?)
+        self.wait_for_rx_events(p_dut, iterations, messages_by_msg)
+        self.wait_for_rx_events(s_dut, iterations, messages_by_msg)
+
+        # analyze results
+        results = {}
+        results["tx_count"] = 2 * iterations
+        results["tx_unknown_ids"] = p_tx_unknown_id_total + s_tx_unknown_id_total
+        self.analyze_results(results, messages_by_msg)
+
+        # clear errors
+        asserts.assert_equal(results["tx_unknown_ids"], 0,
+                             "Message ID corruption", results)
+        asserts.assert_equal(results["tx_count_neither"], 0,
+                             "Tx message with no success or fail indication",
+                             results)
+        asserts.assert_equal(results["tx_count_duplicate_fail"], 0,
+                             "Duplicate Tx fail messages", results)
+        asserts.assert_equal(results["tx_count_duplicate_success"], 0,
+                             "Duplicate Tx success messages", results)
+        asserts.assert_equal(results["rx_count_no_tx_message"], 0,
+                             "Rx message which wasn't sent - message corruption?", results)
+        asserts.assert_equal(results["tx_count_tx_ok_but_no_rx"], 0,
+                             "Tx got ACK but Rx didn't get message", results)
+
+        # possibly ok - but flag since most frequently a bug
+        asserts.assert_equal(results["rx_count_no_ok_tx_indication"], 0,
+                             "Message received but Tx didn't get ACK", results)
+        asserts.assert_equal(results["rx_count_fail_tx_indication"], 0,
+                             "Message received but Tx didn't get ACK", results)
+
+        # permissible failures based on thresholds
+        asserts.assert_true(
+            results["tx_count_fail"] <=
+            (self.MAX_TX_FAILURE_PERCENTAGE * iterations * 2 / 100),
+            "Number of Tx failures exceeds threshold", extras=results)
+        asserts.assert_true(
+            results["rx_count_duplicate"] <=
+            (self.MAX_DUPLICATE_RX_PERCENTAGE * iterations * 2 / 100),
+            "Number of duplicate Rx exceeds threshold", extras=results)
+
+        asserts.explicit_pass("test_stress_message_no_throttling done", extras=results)
+
+    @test_tracker_info(uuid="546b0c6f-3071-4330-8e23-842ecbd07018")
+    def test_stress_message_throttling(self):
+        """Stress test for bi-directional message transmission and reception with throttling"""
         p_dut = self.android_devices[0]
         s_dut = self.android_devices[1]
 
@@ -295,14 +430,16 @@
 
         # permissible failures based on thresholds
         asserts.assert_true(
-            results["tx_count_fail"] <=
-            (self.MAX_TX_FAILURE_PERCENTAGE * self.NUM_ITERATIONS / 100),
-            "Number of Tx failures exceeds threshold",
-            extras=results)
-        asserts.assert_true(
             results["rx_count_duplicate"] <=
-            (self.MAX_DUPLICATE_RX_PERCENTAGE * self.NUM_ITERATIONS / 100),
-            "Number of duplicate Rx exceeds threshold",
-            extras=results)
+            (self.MAX_DUPLICATE_RX_PERCENTAGE * results["tx_count_success"] / 100),
+            "Number of duplicate Rx exceeds threshold", extras=results)
 
-        asserts.explicit_pass("test_stress_message done", extras=results)
+        # check working status message queue limit per UID
+        asserts.assert_true(
+            results["tx_count_success"] >= self.MESSAGE_QUEUE_DEPTH_PER_UID * 2,
+            "Number of messages did not reach uid message queue limit", extras=results)
+        asserts.assert_true(
+            results["tx_count_success"] < self.NUM_ITERATIONS * 2,
+            "Seems uid message queue limit is not working, Tx all message", extras=results)
+
+        asserts.explicit_pass("test_stress_message_throttling done", extras=results)
diff --git a/acts/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py b/acts/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py
index f9d2b23..2a1cb92 100644
--- a/acts/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py
+++ b/acts/tests/google/wifi/p2p/functional/WifiP2pSnifferTest.py
@@ -17,6 +17,7 @@
 import acts.test_utils.wifi.wifi_test_utils as wutils
 import acts.utils
 import time
+import re
 
 from acts import asserts
 from acts import utils
@@ -56,7 +57,6 @@
             self.packet_capture, '2g', self.test_name)
 
     def teardown_test(self):
-        self.verify_mac_no_leakage()
         super(WifiP2pSnifferTest, self).teardown_test()
 
     def configure_packet_capture(self):
@@ -74,8 +74,8 @@
         pcap_fname = '%s_%s.pcap' % (self.pcap_procs[BAND_2G][1],
                                      BAND_2G.upper())
         packets = rdpcap(pcap_fname)
-        wutils.verify_mac_not_found_in_pcap(self.dut1_mac, packets)
-        wutils.verify_mac_not_found_in_pcap(self.dut2_mac, packets)
+        wutils.verify_mac_not_found_in_pcap(self.dut1, self.dut1_mac, packets)
+        wutils.verify_mac_not_found_in_pcap(self.dut2, self.dut2_mac, packets)
 
     """Test Cases"""
     @test_tracker_info(uuid=" d04e62dc-e1ef-4cea-86e6-39f0dd08fb6b")
@@ -88,6 +88,7 @@
         self.log.info("Device discovery")
         wp2putils.find_p2p_device(self.dut1, self.dut2)
         wp2putils.find_p2p_device(self.dut2, self.dut1)
+        self.verify_mac_no_leakage()
 
     @test_tracker_info(uuid="6a02be84-912d-4b5b-8dfa-fd80d2554c55")
     def test_p2p_connect_via_pbc_and_ping_and_reconnect_sniffer(self):
@@ -136,3 +137,6 @@
         wp2putils.p2p_disconnect(gc_dut)
         wp2putils.check_disconnect(go_dut)
         time.sleep(p2pconsts.DEFAULT_FUNCTION_SWITCH_TIME)
+
+        # teardown
+        self.verify_mac_no_leakage()