Merge "Turn screen off when doing pno stress test"
diff --git a/acts/framework/acts/controllers/access_point.py b/acts/framework/acts/controllers/access_point.py
index 9e87d43..ff6eb12 100755
--- a/acts/framework/acts/controllers/access_point.py
+++ b/acts/framework/acts/controllers/access_point.py
@@ -162,6 +162,7 @@
         # interfaces need to be brought down as part of the AP initialization
         # process, otherwise test would fail.
         try:
+            self.ssh.run('stop wpasupplicant')
             self.ssh.run('stop hostapd')
         except job.Error:
             self.log.debug('No hostapd running')
diff --git a/acts/framework/acts/controllers/android_device.py b/acts/framework/acts/controllers/android_device.py
index 7bed102..51067ca 100755
--- a/acts/framework/acts/controllers/android_device.py
+++ b/acts/framework/acts/controllers/android_device.py
@@ -72,10 +72,6 @@
     """Raised when there is an error in AndroidDevice."""
 
 
-class DoesNotExistError(AndroidDeviceError):
-    """Raised when something that does not exist is referenced."""
-
-
 def create(configs):
     """Creates AndroidDevice controller objects.
 
@@ -103,9 +99,9 @@
 
     for ad in ads:
         if not ad.is_connected():
-            raise DoesNotExistError(("Android device %s is specified in config"
-                                     " but is not attached.") % ad.serial,
-                                    serial=ad.serial)
+            raise AndroidDeviceError(("Android device %s is specified in config"
+                                      " but is not attached.") % ad.serial,
+                                      serial=ad.serial)
     _start_services_on_ads(ads)
     return ads
 
diff --git a/acts/framework/acts/controllers/android_lib/logcat.py b/acts/framework/acts/controllers/android_lib/logcat.py
index 9a62eb8..04a787c 100644
--- a/acts/framework/acts/controllers/android_lib/logcat.py
+++ b/acts/framework/acts/controllers/android_lib/logcat.py
@@ -92,7 +92,7 @@
     """
     logger = log_stream.create_logger(
         'adblog_%s' % serial, base_path=base_path,
-        log_styles=(LogStyles.LOG_DEBUG | LogStyles.MONOLITH_LOG))
+        log_styles=(LogStyles.LOG_DEBUG | LogStyles.TESTCASE_LOG))
     process = Process(('adb -s %s logcat -T 1 -v year %s' %
                        (serial, extra_params)).split(' '))
     timestamp_tracker = TimestampTracker()
diff --git a/acts/framework/acts/controllers/anritsu_lib/md8475a.py b/acts/framework/acts/controllers/anritsu_lib/md8475a.py
index ea3c93f..4def316 100644
--- a/acts/framework/acts/controllers/anritsu_lib/md8475a.py
+++ b/acts/framework/acts/controllers/anritsu_lib/md8475a.py
@@ -3234,8 +3234,7 @@
         except:
             raise ValueError(
                 'The parameter slot has to be a tuple containing two ints '
-                'indicating (dl,ul) slots.'
-            )
+                'indicating (dl,ul) slots.')
 
         # Validate
         if dl < 1 or ul < 1 or dl + ul > 5:
diff --git a/acts/framework/acts/controllers/arduino_wifi_dongle.py b/acts/framework/acts/controllers/arduino_wifi_dongle.py
index 7978f30..290466a 100644
--- a/acts/framework/acts/controllers/arduino_wifi_dongle.py
+++ b/acts/framework/acts/controllers/arduino_wifi_dongle.py
@@ -17,36 +17,35 @@
 import logging
 import os
 import re
-import serial
 import subprocess
 import threading
 import time
+from datetime import datetime
+
+from serial import Serial
 
 from acts import logger
 from acts import signals
-from acts import tracelogger
 from acts import utils
 from acts.test_utils.wifi import wifi_test_utils as wutils
 
-from datetime import datetime
+ACTS_CONTROLLER_CONFIG_NAME = 'ArduinoWifiDongle'
+ACTS_CONTROLLER_REFERENCE_NAME = 'arduino_wifi_dongles'
 
-ACTS_CONTROLLER_CONFIG_NAME = "ArduinoWifiDongle"
-ACTS_CONTROLLER_REFERENCE_NAME = "arduino_wifi_dongles"
+WIFI_DONGLE_EMPTY_CONFIG_MSG = 'Configuration is empty, abort!'
+WIFI_DONGLE_NOT_LIST_CONFIG_MSG = 'Configuration should be a list, abort!'
 
-WIFI_DONGLE_EMPTY_CONFIG_MSG = "Configuration is empty, abort!"
-WIFI_DONGLE_NOT_LIST_CONFIG_MSG = "Configuration should be a list, abort!"
-
-DEV = "/dev/"
-IP = "IP: "
-STATUS = "STATUS: "
-SSID = "SSID: "
-RSSI = "RSSI: "
-PING = "PING: "
-SCAN_BEGIN = "Scan Begin"
-SCAN_END = "Scan End"
+DEV = '/dev/'
+IP = 'IP: '
+STATUS = 'STATUS: '
+SSID = 'SSID: '
+RSSI = 'RSSI: '
+PING = 'PING: '
+SCAN_BEGIN = 'Scan Begin'
+SCAN_END = 'Scan End'
 READ_TIMEOUT = 10
 BAUD_RATE = 9600
-TMP_DIR = "tmp/"
+TMP_DIR = 'tmp/'
 SSID_KEY = wutils.WifiEnums.SSID_KEY
 PWD_KEY = wutils.WifiEnums.PWD_KEY
 
@@ -54,8 +53,6 @@
 class ArduinoWifiDongleError(signals.ControllerError):
     pass
 
-class DoesNotExistError(ArduinoWifiDongleError):
-    """Raised when something that does not exist is referenced."""
 
 def create(configs):
     """Creates ArduinoWifiDongle objects.
@@ -67,41 +64,42 @@
     Returns:
         A list of Wifi dongle objects.
     """
-    wcs = []
     if not configs:
         raise ArduinoWifiDongleError(WIFI_DONGLE_EMPTY_CONFIG_MSG)
     elif not isinstance(configs, list):
         raise ArduinoWifiDongleError(WIFI_DONGLE_NOT_LIST_CONFIG_MSG)
     elif isinstance(configs[0], str):
         # Configs is a list of serials.
-        wcs = get_instances(configs)
+        return get_instances(configs)
     else:
         # Configs is a list of dicts.
-        wcs = get_instances_with_configs(configs)
+        return get_instances_with_configs(configs)
 
-    return wcs
 
 def destroy(wcs):
     for wc in wcs:
         wc.clean_up()
 
+
 def get_instances(configs):
     wcs = []
     for s in configs:
         wcs.append(ArduinoWifiDongle(s))
     return wcs
 
+
 def get_instances_with_configs(configs):
     wcs = []
     for c in configs:
         try:
-            s = c.pop("serial")
+            s = c.pop('serial')
         except KeyError:
             raise ArduinoWifiDongleError(
-                "'serial' is missing for ArduinoWifiDongle config %s." % c)
+                '"serial" is missing for ArduinoWifiDongle config %s.' % c)
         wcs.append(ArduinoWifiDongle(s))
     return wcs
 
+
 class ArduinoWifiDongle(object):
     """Class representing an arduino wifi dongle.
 
@@ -120,16 +118,24 @@
         scan_results: Most recent scan results.
         ping: Ping status in bool - ping to www.google.com
     """
-    def __init__(self, serial=''):
-        """Initializes the ArduinoWifiDongle object."""
+
+    def __init__(self, serial):
+        """Initializes the ArduinoWifiDongle object.
+
+        Args:
+            serial: The serial number for the wifi dongle.
+        """
+        if not serial:
+            raise ArduinoWifiDongleError(
+                'The ArduinoWifiDongle serial number must not be empty.')
         self.serial = serial
         self.port = self._get_serial_port()
         self.log = logger.create_tagged_trace_logger(
-            "ArduinoWifiDongle|%s" % self.serial)
-        log_path_base = getattr(logging, "log_path", "/tmp/logs")
+            'ArduinoWifiDongle|%s' % self.serial)
+        log_path_base = getattr(logging, 'log_path', '/tmp/logs')
         self.log_file_path = os.path.join(
-            log_path_base, "ArduinoWifiDongle_%s_serial_log.txt" % self.serial)
-        self.log_file_fd = open(self.log_file_path, "a")
+            log_path_base, 'ArduinoWifiDongle_%s_serial_log.txt' % self.serial)
+        self.log_file_fd = open(self.log_file_path, 'a')
 
         self.set_logging = True
         self.lock = threading.Lock()
@@ -142,15 +148,10 @@
         self.scanning = False
         self.ping = False
 
-        try:
-            os.stat(TMP_DIR)
-        except:
-            os.mkdir(TMP_DIR)
+        os.makedirs(TMP_DIR, exist_ok=True)
 
     def clean_up(self):
-        """Cleans up the ArduinoifiDongle object and releases any resources it
-        claimed.
-        """
+        """Cleans up the controller and releases any resources it claimed."""
         self.stop_controller_log()
         self.log_file_fd.close()
 
@@ -160,24 +161,21 @@
         Returns:
             Serial port in string if the dongle is attached.
         """
-        if not self.serial:
-            raise ArduinoWifiDongleError(
-                "Wifi dongle's serial should not be empty")
-        cmd = "ls %s" % DEV
-        serial_ports = utils.exe_cmd(cmd).decode("utf-8", "ignore").split("\n")
+        cmd = 'ls %s' % DEV
+        serial_ports = utils.exe_cmd(cmd).decode('utf-8', 'ignore').split('\n')
         for port in serial_ports:
-            if "USB" not in port:
+            if 'USB' not in port:
                 continue
-            tty_port = "%s%s" % (DEV, port)
-            cmd = "udevadm info %s" % tty_port
-            udev_output = utils.exe_cmd(cmd).decode("utf-8", "ignore")
-            result = re.search("ID_SERIAL_SHORT=(.*)\n", udev_output)
+            tty_port = '%s%s' % (DEV, port)
+            cmd = 'udevadm info %s' % tty_port
+            udev_output = utils.exe_cmd(cmd).decode('utf-8', 'ignore')
+            result = re.search('ID_SERIAL_SHORT=(.*)\n', udev_output)
             if self.serial == result.group(1):
-                logging.info("Found wifi dongle %s at serial port %s" %
+                logging.info('Found wifi dongle %s at serial port %s' %
                              (self.serial, tty_port))
                 return tty_port
-        raise ArduinoWifiDongleError("Wifi dongle %s is specified in config"
-                                    " but is not attached." % self.serial)
+        raise ArduinoWifiDongleError('Wifi dongle %s is specified in config'
+                                     ' but is not attached.' % self.serial)
 
     def write(self, arduino, file_path, network=None):
         """Write an ino file to the arduino wifi dongle.
@@ -192,19 +190,20 @@
             False: if not.
         """
         return_result = True
-        self.stop_controller_log("Flashing %s\n" % file_path)
-        cmd = arduino + file_path + " --upload --port " + self.port
+        self.stop_controller_log('Flashing %s\n' % file_path)
+        cmd = arduino + file_path + ' --upload --port ' + self.port
         if network:
             cmd = self._update_ino_wifi_network(arduino, file_path, network)
-        self.log.info("Command is %s" % cmd)
+        self.log.info('Command is %s' % cmd)
         proc = subprocess.Popen(cmd,
-            stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
-        out, err = proc.communicate()
+                                stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+                                shell=True)
+        _, _ = proc.communicate()
         return_code = proc.returncode
         if return_code != 0:
-            self.log.error("Failed to write file %s" % return_code)
+            self.log.error('Failed to write file %s' % return_code)
             return_result = False
-        self.start_controller_log("Flashing complete\n")
+        self.start_controller_log('Flashing complete\n')
         return return_result
 
     def _update_ino_wifi_network(self, arduino, file_path, network):
@@ -216,17 +215,18 @@
             network: wifi network to update the ino file with
 
         Returns:
-            cmd: arduino command to run to flash the ino file
+            cmd: arduino command to run to flash the .ino file
         """
-        tmp_file = "%s%s" % (TMP_DIR, file_path.split('/')[-1])
-        utils.exe_cmd("cp %s %s" % (file_path, tmp_file))
+        tmp_file = '%s%s' % (TMP_DIR, file_path.split('/')[-1])
+        utils.exe_cmd('cp %s %s' % (file_path, tmp_file))
         ssid = network[SSID_KEY]
         pwd = network[PWD_KEY]
-        sed_cmd = "sed -i 's/\"wifi_tethering_test\"/\"%s\"/' %s" % (ssid, tmp_file)
+        sed_cmd = 'sed -i \'s/"wifi_tethering_test"/"%s"/\' %s' % (
+            ssid, tmp_file)
         utils.exe_cmd(sed_cmd)
-        sed_cmd = "sed -i  's/\"password\"/\"%s\"/' %s" % (pwd, tmp_file)
+        sed_cmd = 'sed -i  \'s/"password"/"%s"/\' %s' % (pwd, tmp_file)
         utils.exe_cmd(sed_cmd)
-        cmd = "%s %s --upload --port %s" %(arduino, tmp_file, self.port)
+        cmd = "%s %s --upload --port %s" % (arduino, tmp_file, self.port)
         return cmd
 
     def start_controller_log(self, msg=None):
@@ -241,7 +241,7 @@
         """
         if msg:
             curr_time = str(datetime.now())
-            self.log_file_fd.write(curr_time + " INFO: " + msg)
+            self.log_file_fd.write(curr_time + ' INFO: ' + msg)
         t = threading.Thread(target=self._start_log)
         t.daemon = True
         t.start()
@@ -256,20 +256,20 @@
             self.set_logging = False
         if msg:
             curr_time = str(datetime.now())
-            self.log_file_fd.write(curr_time + " INFO: " + msg)
+            self.log_file_fd.write(curr_time + ' INFO: ' + msg)
 
     def _start_log(self):
         """Target method called by start_controller_log().
 
-        This method is called as a daemon thread, which continously reads the
+        This method is called as a daemon thread, which continuously reads the
         serial port. Stops when set_logging is set to False or when the test
         ends.
         """
         self.set_logging = True
-        ser = serial.Serial(self.port, BAUD_RATE)
+        ser = Serial(self.port, BAUD_RATE)
         while True:
             curr_time = str(datetime.now())
-            data = ser.readline().decode("utf-8", "ignore")
+            data = ser.readline().decode('utf-8', 'ignore')
             self._set_vars(data)
             with self.lock:
                 if not self.set_logging:
@@ -292,9 +292,9 @@
         # Ex: data = "connect_wifi: loop(): STATUS: 3" then val = "3"
         # Similarly, we check when the scan has begun and ended and get all the
         # scan results in between.
-        if data.count(":") != 3:
+        if data.count(':') != 3:
             return
-        val = data.split(":")[-1].lstrip().rstrip()
+        val = data.split(':')[-1].lstrip().rstrip()
         if SCAN_BEGIN in data:
             self.scan_results = []
             self.scanning = True
@@ -303,13 +303,13 @@
         elif self.scanning:
             self.scan_results.append(data)
         elif IP in data:
-            self.ip_addr = None if val == "0.0.0.0" else val
+            self.ip_addr = None if val == '0.0.0.0' else val
         elif SSID in data:
             self.ssid = val
         elif STATUS in data:
             self.status = int(val)
         elif PING in data:
-            self.ping = False if int(val) == 0 else True
+            self.ping = int(val) != 0
 
     def ip_address(self, exp_result=True, timeout=READ_TIMEOUT):
         """Get the ip address of the wifi dongle.
@@ -325,9 +325,9 @@
         """
         curr_time = time.time()
         while time.time() < curr_time + timeout:
-            if (exp_result and self.ip_addr) or \
-                (not exp_result and not self.ip_addr):
-                  break
+            if (exp_result and self.ip_addr) or (
+                    not exp_result and not self.ip_addr):
+                break
             time.sleep(1)
         return self.ip_addr
 
@@ -340,9 +340,9 @@
         """
         curr_time = time.time()
         while time.time() < curr_time + timeout:
-            if (exp_result and self.status == 3) or \
-                (not exp_result and not self.status):
-                  break
+            if (exp_result and self.status == 3) or (
+                    not exp_result and not self.status):
+                break
             time.sleep(1)
         return self.status == 3
 
@@ -362,16 +362,16 @@
         d = {}
         curr_time = time.time()
         while time.time() < curr_time + timeout:
-            if (exp_result and self.scan_results) or \
-                (not exp_result and not self.scan_results):
-                  break
+            if (exp_result and self.scan_results) or (
+                    not exp_result and not self.scan_results):
+                break
             time.sleep(1)
         for i in range(len(self.scan_results)):
             if SSID in self.scan_results[i]:
-                d = {}
-                d[SSID] = self.scan_results[i].split(":")[-1].rstrip()
+                d.clear()
+                d[SSID] = self.scan_results[i].split(':')[-1].rstrip()
             elif RSSI in self.scan_results[i]:
-                d[RSSI] = self.scan_results[i].split(":")[-1].rstrip()
+                d[RSSI] = self.scan_results[i].split(':')[-1].rstrip()
                 scan_networks.append(d)
 
         return scan_networks
@@ -385,8 +385,7 @@
         """
         curr_time = time.time()
         while time.time() < curr_time + timeout:
-            if (exp_result and self.ping) or \
-                (not exp_result and not self.ping):
-                  break
+            if (exp_result and self.ping) or (not exp_result and not self.ping):
+                break
             time.sleep(1)
         return self.ping
diff --git a/acts/framework/acts/controllers/packet_capture.py b/acts/framework/acts/controllers/packet_capture.py
index 3947bda..caf70b7 100755
--- a/acts/framework/acts/controllers/packet_capture.py
+++ b/acts/framework/acts/controllers/packet_capture.py
@@ -18,8 +18,12 @@
 from acts.controllers.ap_lib.hostapd_constants import AP_DEFAULT_CHANNEL_2G
 from acts.controllers.ap_lib.hostapd_constants import AP_DEFAULT_CHANNEL_5G
 from acts.controllers.utils_lib.ssh import connection
+from acts.controllers.utils_lib.ssh import formatter
 from acts.controllers.utils_lib.ssh import settings
+from acts.libs.logging import log_stream
+from acts.libs.proc.process import Process
 
+import logging
 import os
 import threading
 import time
@@ -44,10 +48,12 @@
 def create(configs):
     return [PacketCapture(c) for c in configs]
 
+
 def destroy(pcaps):
     for pcap in pcaps:
         pcap.close()
 
+
 def get_info(pcaps):
     return [pcap.ssh_settings.hostname for pcap in pcaps]
 
@@ -56,17 +62,13 @@
     """Class to maintain packet capture properties after starting tcpdump.
 
     Attributes:
-        pid: proccess id of tcpdump
-        pcap_dir: tmp dir location where pcap files are saved
+        proc: Process object of tcpdump
         pcap_file: pcap file name
-        pcap_thread: thread used to push files to logpath
     """
-    def __init__(self, pid, pcap_dir, pcap_file, pcap_thread):
+    def __init__(self, proc, pcap_file):
         """Initialize object."""
-        self.pid = pid
-        self.pcap_dir = pcap_dir
+        self.proc = proc
         self.pcap_file = pcap_file
-        self.pcap_thread = pcap_thread
 
 
 class PacketCaptureError(Exception):
@@ -81,8 +83,8 @@
     wifi networks; 'wlan2' which is a dual band interface.
 
     Attributes:
-        pcap: dict that specifies packet capture properties for a band.
-        tmp_dirs: list of tmp directories created for pcap files.
+        pcap_properties: dict that specifies packet capture properties for a
+            band.
     """
     def __init__(self, configs):
         """Initialize objects.
@@ -101,7 +103,6 @@
 
         self.pcap_properties = dict()
         self._pcap_stop_lock = threading.Lock()
-        self.tmp_dirs = []
 
     def _create_interface(self, iface, mode):
         """Create interface of monitor/managed mode.
@@ -157,62 +158,6 @@
                 network = {}
         return scan_networks
 
-    def _check_if_tcpdump_started(self, pcap_log):
-        """Check if tcpdump started.
-
-        This method ensures that tcpdump has started successfully.
-        We look for 'listening on' from the stdout indicating that tcpdump
-        is started.
-
-        Args:
-            pcap_log: log file that has redirected output of starting tcpdump.
-
-        Returns:
-            True/False if tcpdump is started or not.
-        """
-        curr_time = time.time()
-        timeout = 3
-        find_str = 'listening on'
-        while time.time() < curr_time + timeout:
-            result = self.ssh.run('grep "%s" %s' % (find_str, pcap_log),
-                                  ignore_status=True)
-            if result.stdout and find_str in result.stdout:
-                return True
-            time.sleep(1)
-        return False
-
-    def _pull_pcap(self, band, pcap_file, log_path):
-        """Pulls pcap files to test log path from onhub.
-
-        Called by start_packet_capture(). This method moves a pcap file to log
-        path once it has reached 50MB.
-
-        Args:
-            index: param that indicates if the tcpdump is stopped.
-            pcap_file: pcap file to move.
-            log_path: log path to move the pcap file to.
-        """
-        curr_no = 0
-        while True:
-            next_no = curr_no + 1
-            curr_fno = '%02i' % curr_no
-            next_fno = '%02i' % next_no
-            curr_file = '%s%s' % (pcap_file, curr_fno)
-            next_file = '%s%s' % (pcap_file, next_fno)
-
-            result = self.ssh.run('ls %s' % next_file, ignore_status=True)
-            if not result.stderr and next_file in result.stdout:
-                self.ssh.pull_file(log_path, curr_file)
-                self.ssh.run('rm -rf %s' % curr_file, ignore_status=True)
-                curr_no += 1
-                continue
-
-            with self._pcap_stop_lock:
-                if band not in self.pcap_properties:
-                    self.ssh.pull_file(log_path, curr_file)
-                    break
-            time.sleep(2) # wait before looking for file again
-
     def get_wifi_scan_results(self):
         """Starts a wifi scan on wlan2 interface.
 
@@ -275,66 +220,53 @@
         band = 2G starts tcpdump on 'mon0' interface.
         band = 5G starts tcpdump on 'mon1' interface.
 
-        This method splits the pcap file every 50MB for 100 files.
-        Since, the size of the pcap file could become large, each split file
-        is moved to log_path once a new file is generated. This ensures that
-        there is no crash on the onhub router due to lack of space.
-
         Args:
             band: '2g' or '2G' and '5g' or '5G'.
             log_path: test log path to save the pcap file.
             pcap_file: name of the pcap file.
 
         Returns:
-            pid: process id of the tcpdump.
+            pcap_proc: Process object of the tcpdump.
         """
         band = band.upper()
         if band not in BAND_IFACE.keys() or band in self.pcap_properties:
             self.log.error("Invalid band or packet capture already running")
             return None
 
-        pcap_dir = self.ssh.run('mktemp -d', ignore_status=True).stdout.rstrip()
-        self.tmp_dirs.append(pcap_dir)
-        pcap_file = os.path.join(pcap_dir, "%s_%s.pcap" % (pcap_file, band))
-        pcap_log = os.path.join(pcap_dir, "%s.log" % pcap_file)
+        pcap_name = "%s_pcap" % band
+        pcap_file = os.path.join(log_path, pcap_name)
 
-        cmd = 'tcpdump -i %s -W 100 -C 50 -w %s > %s 2>&1 & echo $!' % (
-            BAND_IFACE[band], pcap_file, pcap_log)
-        result = self.ssh.run(cmd, ignore_status=True)
-        if not self._check_if_tcpdump_started(pcap_log):
-            self.log.error("Failed to start packet capture")
-            return None
+        pcap_logger = log_stream.create_logger(
+            pcap_name, base_path=log_path,
+            log_styles=(log_stream.LogStyles.LOG_DEBUG +
+                        log_stream.LogStyles.MONOLITH_LOG))
+        pcap_logger.setLevel(logging.DEBUG)
+        cmd = formatter.SshFormatter().format_command(
+            'tcpdump -i %s -l' %
+            (BAND_IFACE[band]), None, self.ssh_settings)
+        pcap_proc = Process(cmd)
+        pcap_proc.set_on_output_callback(lambda msg: pcap_logger.debug(msg))
+        pcap_proc.start()
 
-        pcap_thread = threading.Thread(target=self._pull_pcap,
-                                       args=(band, pcap_file, log_path))
-        pcap_thread.start()
+        self.pcap_properties[band] = PcapProperties(pcap_proc, pcap_file)
+        return pcap_proc
 
-        pid = int(result.stdout)
-        self.pcap_properties[band] = PcapProperties(
-            pid, pcap_dir, pcap_file, pcap_thread)
-        return pid
-
-    def stop_packet_capture(self, pid):
+    def stop_packet_capture(self, proc):
         """Stop the packet capture.
 
         Args:
-            pid: process id of tcpdump to kill.
+            proc: Process object of tcpdump to kill.
         """
         for key, val in self.pcap_properties.items():
-            if val.pid == pid:
+            if val.proc is proc:
                 break
         else:
-            self.log.error("Failed to stop tcpdump. Invalid PID %s" % pid)
+            self.log.error("Failed to stop tcpdump. Invalid process.")
             return
 
-        pcap_dir = val.pcap_dir
-        pcap_thread = val.pcap_thread
-        self.ssh.run('kill %s' % pid, ignore_status=True)
+        proc.stop()
         with self._pcap_stop_lock:
             del self.pcap_properties[key]
-        pcap_thread.join()
-        self.ssh.run('rm -rf %s' % pcap_dir, ignore_status=True)
-        self.tmp_dirs.remove(pcap_dir)
 
     def close(self):
         """Cleanup.
@@ -343,6 +275,4 @@
         """
         self._cleanup_interface(MON_2G)
         self._cleanup_interface(MON_5G)
-        for tmp_dir in self.tmp_dirs:
-            self.ssh.run('rm -rf %s' % tmp_dir, ignore_status=True)
         self.ssh.close()
diff --git a/acts/framework/acts/controllers/packet_sender.py b/acts/framework/acts/controllers/packet_sender.py
index 6b3898a..a7d2f08 100644
--- a/acts/framework/acts/controllers/packet_sender.py
+++ b/acts/framework/acts/controllers/packet_sender.py
@@ -53,6 +53,14 @@
 MDNS_RECURSIVE = 1
 MDNS_V6_IP_DST = 'FF02::FB'
 MDNS_V6_MAC_DST = '33:33:00:00:00:FB'
+ETH_TYPE_IP = 2048
+SAP_SPANNING_TREE = 0x42
+SNAP_OUI = 12
+SNAP_SSAP = 170
+SNAP_DSAP = 170
+SNAP_CTRL = 3
+LLC_XID_CONTROL = 191
+PAD_LEN_BYTES = 128
 
 
 def create(configs):
@@ -281,11 +289,19 @@
         else:
             self.src_ipv4 = config_params['src_ipv4']
 
-    def generate(self, ip_dst=None, hwsrc=None, hwdst=None, eth_dst=None):
+    def generate(self,
+                 op=scapy.ARP.who_has,
+                 ip_dst=None,
+                 ip_src=None,
+                 hwsrc=None,
+                 hwdst=None,
+                 eth_dst=None):
         """Generates a custom ARP packet.
 
         Args:
+            op: ARP type (request or reply)
             ip_dst: ARP ipv4 destination (Optional)
+            ip_src: ARP ipv4 source address (Optional)
             hwsrc: ARP hardware source address (Optional)
             hwdst: ARP hardware destination address (Optional)
             eth_dst: Ethernet (layer 2) destination address (Optional)
@@ -294,8 +310,9 @@
         hw_src = (hwsrc if hwsrc is not None else self.src_mac)
         hw_dst = (hwdst if hwdst is not None else ARP_DST)
         ipv4_dst = (ip_dst if ip_dst is not None else self.dst_ipv4)
+        ipv4_src = (ip_src if ip_src is not None else self.src_ipv4)
         ip4 = scapy.ARP(
-            pdst=ipv4_dst, psrc=self.src_ipv4, hwdst=hw_dst, hwsrc=hw_src)
+            op=op, pdst=ipv4_dst, psrc=ipv4_src, hwdst=hw_dst, hwsrc=hw_src)
 
         # Create Ethernet layer
         mac_dst = (eth_dst if eth_dst is not None else MAC_BROADCAST)
@@ -778,3 +795,118 @@
 
         self.packet = ethernet / ip4 / udp / mDNS
         return self.packet
+
+
+class Dot3Generator(object):
+    """Creates a custom 802.3 Ethernet Frame
+
+    Attributes:
+        packet: desired built custom packet
+        src_mac: MAC address (Layer 2) of the source node
+        src_ipv4: IPv4 address (Layer 3) of the source node
+    """
+
+    def __init__(self, **config_params):
+        """Initialize the class with the required network and packet params.
+
+        Args:
+            config_params: contains all the necessary packet parameters.
+              Some fields can be generated automatically. For example:
+              {'subnet_mask': '255.255.255.0',
+               'dst_ipv4': '192.168.1.3',
+               'src_ipv4: 'get_local', ...
+              The key can also be 'get_local' which means the code will read
+              and use the local interface parameters
+        """
+        interf = config_params['interf']
+        self.packet = None
+        self.dst_mac = config_params['dst_mac']
+        if config_params['src_mac'] == GET_FROM_LOCAL_INTERFACE:
+            self.src_mac = scapy.get_if_hwaddr(interf)
+        else:
+            self.src_mac = config_params['src_mac']
+
+    def _build_ether(self, eth_dst=None):
+        """Creates the basic frame for 802.3
+
+        Args:
+            eth_dst: Ethernet (layer 2) destination address (Optional)
+        """
+        # Overwrite standard fields if desired
+        sta_hw = (eth_dst if eth_dst is not None else self.dst_mac)
+        # Create Ethernet layer
+        dot3_base = scapy.Dot3(src=self.src_mac, dst=sta_hw)
+
+        return dot3_base
+
+    def _pad_frame(self, frame):
+        """Pads the frame with default length and values
+
+        Args:
+            frame: Ethernet (layer 2) to be padded
+        """
+        frame.len = PAD_LEN_BYTES
+        pad = scapy.Padding()
+        pad.load = '\x00' * PAD_LEN_BYTES
+        return frame / pad
+
+    def generate(self, eth_dst=None):
+        """Generates the basic 802.3 frame and adds padding
+
+        Args:
+            eth_dst: Ethernet (layer 2) destination address (Optional)
+        """
+        # Create 802.3 Base
+        ethernet = self._build_ether(eth_dst)
+
+        self.packet = self._pad_frame(ethernet)
+        return self.packet
+
+    def generate_llc(self, eth_dst=None, dsap=2, ssap=3, ctrl=LLC_XID_CONTROL):
+        """Generates the 802.3 frame with LLC and adds padding
+
+        Args:
+            eth_dst: Ethernet (layer 2) destination address (Optional)
+            dsap: Destination Service Access Point (Optional)
+            ssap: Source Service Access Point (Optional)
+            ctrl: Control (Optional)
+        """
+        # Create 802.3 Base
+        ethernet = self._build_ether(eth_dst)
+
+        # Create LLC layer
+        llc = scapy.LLC(dsap=dsap, ssap=ssap, ctrl=ctrl)
+
+        # Append and create packet
+        self.packet = self._pad_frame(ethernet / llc)
+        return self.packet
+
+    def generate_snap(self,
+                      eth_dst=None,
+                      dsap=SNAP_DSAP,
+                      ssap=SNAP_SSAP,
+                      ctrl=SNAP_CTRL,
+                      oui=SNAP_OUI,
+                      code=ETH_TYPE_IP):
+        """Generates the 802.3 frame with LLC and SNAP and adds padding
+
+        Args:
+            eth_dst: Ethernet (layer 2) destination address (Optional)
+            dsap: Destination Service Access Point (Optional)
+            ssap: Source Service Access Point (Optional)
+            ctrl: Control (Optional)
+            oid: Protocol Id or Org Code (Optional)
+            code: EtherType (Optional)
+        """
+        # Create 802.3 Base
+        ethernet = self._build_ether(eth_dst)
+
+        # Create 802.2 LLC header
+        llc = scapy.LLC(dsap=dsap, ssap=ssap, ctrl=ctrl)
+
+        # Create 802.3 SNAP header
+        snap = scapy.SNAP(OUI=oui, code=code)
+
+        # Append and create packet
+        self.packet = self._pad_frame(ethernet / llc / snap)
+        return self.packet
diff --git a/acts/framework/acts/libs/logging/log_stream.py b/acts/framework/acts/libs/logging/log_stream.py
index a10ffae..6fcc587 100644
--- a/acts/framework/acts/libs/logging/log_stream.py
+++ b/acts/framework/acts/libs/logging/log_stream.py
@@ -49,6 +49,8 @@
     TO_ACTS_LOG   = 0x1000
     ROTATE_LOGS   = 0x2000
 
+    ALL_FILE_LOGS = MONOLITH_LOG + TESTCLASS_LOG + TESTCASE_LOG
+
     LEVEL_NAMES = {
         LOG_DEBUG: 'debug',
         LOG_INFO: 'info',
@@ -255,6 +257,7 @@
         os.makedirs(self.base_path, exist_ok=True)
         self.stream_format = stream_format
         self.file_format = file_format
+        self._test_run_only_log_handlers = []
         self._test_case_handler_descriptors = []
         self._test_case_log_handlers = []
         self._test_class_handler_descriptors = []
@@ -310,13 +313,16 @@
                                     (log_location, level))
                             else:
                                 levels_dict[level] |= log_location
-                    # Check that for a given log-level, not both TESTCLASS_LOG
-                    # and TESTCASE_LOG have been set.
-                    if not ~levels_dict[level] & (
-                            LogStyles.TESTCLASS_LOG | LogStyles.TESTCASE_LOG):
+                    # Check that for a given log-level, not more than one
+                    # of MONOLITH_LOG, TESTCLASS_LOG, TESTCASE_LOG is set.
+                    locations = levels_dict[level] & LogStyles.ALL_FILE_LOGS
+                    valid_locations = [
+                        LogStyles.TESTCASE_LOG, LogStyles.TESTCLASS_LOG,
+                        LogStyles.MONOLITH_LOG, LogStyles.NONE]
+                    if locations not in valid_locations:
                         invalid_style_error(
-                            'Both TESTCLASS_LOG and TESTCASE_LOG locations '
-                            'have been set for log level %s.' % level)
+                            'More than one of MONOLITH_LOG, TESTCLASS_LOG, '
+                            'TESTCASE_LOG is set for log level %s.' % level)
             if log_style & LogStyles.ALL_LEVELS == 0:
                 invalid_style_error('LogStyle %s needs to set a log '
                                     'level.' % log_style)
@@ -371,18 +377,21 @@
 
         # Handle streaming logs to log-level files
         for log_level in LogStyles.LOG_LEVELS:
-            if not log_style & log_level:
+            if not (log_style & log_level and
+                    log_style & LogStyles.ALL_FILE_LOGS):
                 continue
             descriptor = self.HandlerDescriptor(handler_creator, log_level,
                                                 self.name, self.file_format)
+
+            handler = descriptor.create(self.base_path)
+            self.logger.addHandler(handler)
+            if not log_style & LogStyles.MONOLITH_LOG:
+                self._test_run_only_log_handlers.append(handler)
             if log_style & LogStyles.TESTCASE_LOG:
                 self._test_case_handler_descriptors.append(descriptor)
                 self._test_class_only_handler_descriptors.append(descriptor)
             if log_style & LogStyles.TESTCLASS_LOG:
                 self._test_class_handler_descriptors.append(descriptor)
-            if log_style & LogStyles.MONOLITH_LOG:
-                handler = descriptor.create(self.base_path)
-                self.logger.addHandler(handler)
 
     def __remove_handler(self, handler):
         """Removes a handler from the logger, unless it's a NullHandler."""
@@ -411,7 +420,7 @@
         """Internal use only. To be called when a test case has ended."""
         self.__clear_handlers(self._test_case_log_handlers)
 
-        # Re-add handlers residing only in test class level contexts
+        # Enable handlers residing only in test class level contexts
         for handler in self._test_class_only_log_handlers:
             self.logger.addHandler(handler)
 
@@ -420,7 +429,7 @@
         # Close test case handlers from previous tests.
         self.__clear_handlers(self._test_case_log_handlers)
 
-        # Remove handlers residing only in test class level contexts
+        # Disable handlers residing only in test class level contexts
         for handler in self._test_class_only_log_handlers:
             self.logger.removeHandler(handler)
 
@@ -434,12 +443,20 @@
         self.__clear_handlers(self._test_class_log_handlers)
         self.__clear_handlers(self._test_class_only_log_handlers)
 
+        # Enable handlers residing only in test run level contexts
+        for handler in self._test_run_only_log_handlers:
+            self.logger.addHandler(handler)
+
     def on_test_class_begin(self, test_class_event):
         """Internal use only. To be called when a test class has begun."""
-        # Close test case handlers from previous tests.
+        # Close test class handlers from previous tests.
         self.__clear_handlers(self._test_class_log_handlers)
         self.__clear_handlers(self._test_class_only_log_handlers)
 
+        # Disable handlers residing only in test run level contexts
+        for handler in self._test_run_only_log_handlers:
+            self.logger.removeHandler(handler)
+
         # Create new handlers for this test class.
         self.__create_handlers_from_descriptors(
             self._test_class_handler_descriptors,
diff --git a/acts/framework/acts/libs/proc/process.py b/acts/framework/acts/libs/proc/process.py
index 477cfc6..82b6c8f 100644
--- a/acts/framework/acts/libs/proc/process.py
+++ b/acts/framework/acts/libs/proc/process.py
@@ -20,6 +20,10 @@
 import time
 
 
+class ProcessError(Exception):
+    """Raised when invalid operations are run on a Process."""
+
+
 class Process(object):
     """A Process object used to run various commands.
 
@@ -33,7 +37,7 @@
         _on_output_callback: The callback to call when output is received.
         _on_terminate_callback: The callback to call when the process terminates
                                 without stop() being called first.
-        _started: Whether or not the Process is in the running state.
+        _started: Whether or not start() was called.
         _stopped: Whether or not stop() was called.
     """
 
@@ -52,6 +56,7 @@
         self._on_output_callback = lambda *args, **kw: None
         self._on_terminate_callback = lambda *args, **kw: ''
 
+        self._started = False
         self._stopped = False
 
     def set_on_output_callback(self, on_output_callback):
@@ -92,8 +97,10 @@
 
     def start(self):
         """Starts the process's execution."""
+        if self._started:
+            raise ProcessError('Process has already started.')
+        self._started = True
         self._process = None
-        self._stopped = False
 
         self._listening_thread = Thread(target=self._exec_loop)
         self._listening_thread.start()
@@ -104,6 +111,8 @@
             if time.time() > time_up_at:
                 raise OSError('Unable to open process!')
 
+        self._stopped = False
+
     @staticmethod
     def _get_timeout_left(timeout, start_time):
         return max(.1, timeout - (time.time() - start_time))
@@ -116,36 +125,41 @@
         """
         return self._process is not None and self._process.poll() is None
 
+    def _join_threads(self):
+        """Waits for the threads associated with the process to terminate."""
+        if self._listening_thread is not None:
+            self._listening_thread.join()
+            self._listening_thread = None
+
+        if self._redirection_thread is not None:
+            self._redirection_thread.join()
+            self._redirection_thread = None
+
     def wait(self, kill_timeout=60.0):
         """Waits for the process to finish execution.
 
         If the process has reached the kill_timeout, the process will be killed
         instead.
 
+        Note: the on_self_terminate callback will NOT be called when calling
+        this function.
+
         Args:
             kill_timeout: The amount of time to wait until killing the process.
         """
-        start_time = time.time()
+        if self._stopped:
+            raise ProcessError('Process is already being stopped.')
+        self._stopped = True
 
         try:
             self._process.wait(kill_timeout)
         except subprocess.TimeoutExpired:
-            self._stopped = True
             self._process.kill()
+        finally:
+            self._join_threads()
+            self._started = False
 
-        time_left = self._get_timeout_left(kill_timeout, start_time)
-
-        if self._listening_thread is not None:
-            self._listening_thread.join(timeout=time_left)
-            self._listening_thread = None
-
-        time_left = self._get_timeout_left(kill_timeout, start_time)
-
-        if self._redirection_thread is not None:
-            self._redirection_thread.join(timeout=time_left)
-            self._redirection_thread = None
-
-    def stop(self, timeout=60.0):
+    def stop(self):
         """Stops the process.
 
         This command is effectively equivalent to kill, but gives time to clean
@@ -153,18 +167,8 @@
 
         Note: the on_self_terminate callback will NOT be called when calling
         this function.
-
-        Args:
-            timeout: The amount of time to wait for the program output to finish
-                     being handled.
         """
-        self._stopped = True
-
-        start_time = time.time()
-
-        if self.is_running():
-            self._process.kill()
-        self.wait(self._get_timeout_left(timeout, start_time))
+        self.wait(0)
 
     def _redirect_output(self):
         """Redirects the output from the command into the on_output_callback."""
diff --git a/acts/framework/acts/logger.py b/acts/framework/acts/logger.py
index b3b7662..eb1dda1 100755
--- a/acts/framework/acts/logger.py
+++ b/acts/framework/acts/logger.py
@@ -146,7 +146,7 @@
     """
     logging.log_path = log_path
     log_styles = [LogStyles.LOG_INFO + LogStyles.TO_STDOUT,
-                  LogStyles.DEFAULT_LEVELS + LogStyles.MONOLITH_LOG]
+                  LogStyles.DEFAULT_LEVELS + LogStyles.TESTCASE_LOG]
     terminal_format = log_line_format
     if prefix:
         terminal_format = "[{}] {}".format(prefix, log_line_format)
diff --git a/acts/framework/acts/test_utils/fuchsia/bt_test_utils.py b/acts/framework/acts/test_utils/fuchsia/bt_test_utils.py
index 1f25ccf..a19f4f7 100644
--- a/acts/framework/acts/test_utils/fuchsia/bt_test_utils.py
+++ b/acts/framework/acts/test_utils/fuchsia/bt_test_utils.py
@@ -29,7 +29,7 @@
         The dictionary of device information.
     """
     scan_filter = {"name_substring": search_name}
-    fd.ble_lib.bleStartBleScan(scan_filter)
+    fd.gattc_lib.bleStartBleScan(scan_filter)
     end_time = time.time() + timeout
     found_device = None
     while time.time() < end_time and not found_device:
@@ -42,7 +42,7 @@
                 log.info("Successfully found advertisement! name, id: {}, {}".
                          format(name, did))
                 found_device = device
-    fd.ble_lib.bleStopBleScan()
+    fd.gattc_lib.bleStopBleScan()
     if not found_device:
         log.error("Failed to find device with name {}.".format(search_name))
         return found_device
diff --git a/acts/framework/acts/test_utils/power/PowerCellularLabBaseTest.py b/acts/framework/acts/test_utils/power/PowerCellularLabBaseTest.py
index 676dafb..e3495aa 100644
--- a/acts/framework/acts/test_utils/power/PowerCellularLabBaseTest.py
+++ b/acts/framework/acts/test_utils/power/PowerCellularLabBaseTest.py
@@ -150,8 +150,12 @@
         self.simulation.detach()
 
         # Parse simulation parameters.
-        # This may return false if incorrect values are passed.
-        if not self.simulation.parse_parameters(self.parameters):
+        # This may throw a ValueError exception if incorrect values are passed
+        # or if required arguments are omitted.
+        try:
+            self.simulation.parse_parameters(self.parameters)
+        except ValueError as error:
+            self.log.error(str(error))
             return False
 
         # Wait for new params to settle
@@ -161,8 +165,7 @@
         if not self.simulation.attach():
             return False
 
-        if not self.simulation.start_test_case():
-            return False
+        self.simulation.start_test_case()
 
         # Make the device go to sleep
         self.dut.droid.goToSleepNow()
diff --git a/acts/framework/acts/test_utils/power/tel_simulations/BaseSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/BaseSimulation.py
index 70f8405..bb191a7 100644
--- a/acts/framework/acts/test_utils/power/tel_simulations/BaseSimulation.py
+++ b/acts/framework/acts/test_utils/power/tel_simulations/BaseSimulation.py
@@ -83,10 +83,11 @@
             self.log.warning("The '{} 'key is not set in the testbed "
                              "parameters. Setting to off by default. To "
                              "turn calibration on, include the key with "
-                             "a true/false value.".format(self.KEY_CALIBRATION))
+                             "a true/false value.".format(
+                                 self.KEY_CALIBRATION))
 
-        self.calibration_required = test_config.get(self.KEY_CALIBRATION, False)
-
+        self.calibration_required = test_config.get(self.KEY_CALIBRATION,
+                                                    False)
 
         # Gets BTS1 since this sim only has 1 BTS
         self.bts1 = self.anritsu.get_BTS(BtsNumber.BTS1)
@@ -187,10 +188,10 @@
 
         # Set signal levels obtained from the test parameters
         if self.sim_dl_power:
-            self.set_downlink_rx_power(self.sim_dl_power)
+            self.set_downlink_rx_power(self.bts1, self.sim_dl_power)
             time.sleep(2)
         if self.sim_ul_power:
-            self.set_uplink_tx_power(self.sim_ul_power)
+            self.set_uplink_tx_power(self.bts1, self.sim_ul_power)
             time.sleep(2)
 
         return True
@@ -236,11 +237,9 @@
 
         Args:
             parameters: list of parameters
-        Returns:
-            False if there was an error while parsing parameters
         """
 
-        return True
+        pass
 
     def consume_parameter(self, parameters, parameter_name, num_values=0):
         """ Parses a parameter from a list.
@@ -270,17 +269,17 @@
             for j in range(num_values + 1):
                 return_list.append(parameters.pop(i))
         except IndexError:
-            self.log.error(
+            raise ValueError(
                 "Parameter {} has to be followed by {} values.".format(
                     parameter_name, num_values))
-            raise ValueError()
 
         return return_list
 
-    def set_downlink_rx_power(self, signal_level):
+    def set_downlink_rx_power(self, bts, signal_level):
         """ Sets downlink rx power using calibration if available
 
         Args:
+            bts: the base station in which to change the signal level
             signal_level: desired downlink received power, can be either a
             key value pair, an int or a float
         """
@@ -310,7 +309,7 @@
             self.log.info(
                 "Requested phone DL Rx power of {} dBm, setting callbox Tx "
                 "power at {} dBm".format(power, calibrated_power))
-            self.bts1.output_level = calibrated_power
+            bts.output_level = calibrated_power
             time.sleep(2)
             # Power has to be a natural number so calibration wont be exact.
             # Inform the actual received power after rounding.
@@ -318,14 +317,15 @@
                 "Phone downlink received power is {0:.2f} dBm".format(
                     calibrated_power - self.dl_path_loss))
         except TypeError:
-            self.bts1.output_level = round(power)
+            bts.output_level = round(power)
             self.log.info("Phone downlink received power set to {} (link is "
                           "uncalibrated).".format(round(power)))
 
-    def set_uplink_tx_power(self, signal_level):
+    def set_uplink_tx_power(self, bts, signal_level):
         """ Sets uplink tx power using calibration if available
 
         Args:
+            bts: the base station in which to change the signal level
             signal_level: desired uplink transmitted power, can be either a
             key value pair, an int or a float
         """
@@ -353,7 +353,7 @@
             self.log.info(
                 "Requested phone UL Tx power of {} dBm, setting callbox Rx "
                 "power at {} dBm".format(power, calibrated_power))
-            self.bts1.input_level = calibrated_power
+            bts.input_level = calibrated_power
             time.sleep(2)
             # Power has to be a natural number so calibration wont be exact.
             # Inform the actual transmitted power after rounding.
@@ -361,7 +361,7 @@
                 "Phone uplink transmitted power is {0:.2f} dBm".format(
                     calibrated_power + self.ul_path_loss))
         except TypeError:
-            self.bts1.input_level = round(power)
+            bts.input_level = round(power)
             self.log.info("Phone uplink transmitted power set to {} (link is "
                           "uncalibrated).".format(round(power)))
 
@@ -437,7 +437,8 @@
                 cmd = 'OPERATEIPTRAFFIC START,1'
                 self.anritsu.send_command(cmd)
             except AnritsuError as inst:
-                self.log.warning("{}\n".format(inst))  # Typically RUNNING already
+                self.log.warning(
+                    "{}\n".format(inst))  # Typically RUNNING already
             time.sleep(4)
 
         down_power_measured = []
@@ -456,7 +457,8 @@
                 cmd = 'OPERATEIPTRAFFIC STOP,1'
                 self.anritsu.send_command(cmd)
             except AnritsuError as inst:
-                self.log.warning("{}\n".format(inst))  # Typically STOPPED already
+                self.log.warning(
+                    "{}\n".format(inst))  # Typically STOPPED already
             time.sleep(2)
 
         # Reset phone and bts to original settings
@@ -523,7 +525,8 @@
                 cmd = 'OPERATEIPTRAFFIC START,1'
                 self.anritsu.send_command(cmd)
             except AnritsuError as inst:
-                self.log.warning("{}\n".format(inst))  # Typically RUNNING already
+                self.log.warning(
+                    "{}\n".format(inst))  # Typically RUNNING already
             time.sleep(4)
 
         up_power_per_chain = []
@@ -554,7 +557,8 @@
                 cmd = 'OPERATEIPTRAFFIC STOP,1'
                 self.anritsu.send_command(cmd)
             except AnritsuError as inst:
-                self.log.warning("{}\n".format(inst))  # Typically STOPPED already
+                self.log.warning(
+                    "{}\n".format(inst))  # Typically STOPPED already
             time.sleep(2)
 
         # Reset phone and bts to original settings
@@ -650,10 +654,6 @@
         """ Starts a test case in the current simulation.
 
         Requires the phone to be attached.
-
-        Returns:
-            True if the case was successfully started.
-
         """
 
-        return True
\ No newline at end of file
+        pass
diff --git a/acts/framework/acts/test_utils/power/tel_simulations/GsmSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/GsmSimulation.py
index 67fb981..16bfa8a 100644
--- a/acts/framework/acts/test_utils/power/tel_simulations/GsmSimulation.py
+++ b/acts/framework/acts/test_utils/power/tel_simulations/GsmSimulation.py
@@ -87,22 +87,18 @@
 
         Args:
             parameters: list of parameters
-        Returns:
-            False if there was an error while parsing the config
         """
 
-        if not super().parse_parameters(parameters):
-            return False
+        super().parse_parameters(parameters)
 
         # Setup band
 
         values = self.consume_parameter(parameters, self.PARAM_BAND, 1)
 
         if not values:
-            self.log.error(
+            raise ValueError(
                 "The test name needs to include parameter '{}' followed by "
                 "the required band number.".format(self.PARAM_BAND))
-            return False
 
         self.set_band(self.bts1, values[1])
 
@@ -115,24 +111,19 @@
         elif self.consume_parameter(parameters, self.PARAM_NO_GPRS):
             self.bts1.gsm_gprs_mode = BtsGprsMode.NO_GPRS
         else:
-            self.log.error(
+            raise ValueError(
                 "GPRS mode needs to be indicated in the test name with either "
                 "{}, {} or {}.".format(self.PARAM_GPRS, self.PARAM_EGPRS,
                                        self.PARAM_NO_GPRS))
-            return False
 
         # Setup slot allocation
 
         values = self.consume_parameter(parameters, self.PARAM_SLOTS, 2)
 
         if not values:
-            self.log.error(
+            raise ValueError(
                 "The test name needs to include parameter {} followed by two "
                 "int values indicating DL and UL slots.".format(
                     self.PARAM_SLOTS))
-            return False
 
         self.bts1.gsm_slots = (int(values[1]), int(values[2]))
-
-        # No errors were found
-        return True
diff --git a/acts/framework/acts/test_utils/power/tel_simulations/LteCaSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/LteCaSimulation.py
index 4e793d4..f401465 100644
--- a/acts/framework/acts/test_utils/power/tel_simulations/LteCaSimulation.py
+++ b/acts/framework/acts/test_utils/power/tel_simulations/LteCaSimulation.py
@@ -17,6 +17,7 @@
 import time
 
 from acts.controllers.anritsu_lib.md8475a import BtsNumber
+from acts.controllers.anritsu_lib.md8475a import BtsPacketRate
 from acts.controllers.anritsu_lib.md8475a import TestProcedure
 from acts.controllers.anritsu_lib.md8475a import TestPowerControl
 from acts.controllers.anritsu_lib.md8475a import TestMeasurement
@@ -67,23 +68,19 @@
 
         Args:
             parameters: list of parameters
-        Returns:
-            False if there was an error while parsing the config
         """
 
-        if not super(LteSimulation, self).parse_parameters(parameters):
-            return False
+        super(LteSimulation, self).parse_parameters(parameters)
 
         # Get the CA band configuration
 
         values = self.consume_parameter(parameters, self.PARAM_CA, 1)
 
         if not values:
-            self.log.error(
+            raise ValueError(
                 "The test name needs to include parameter '{}' followed by "
                 "the CA configuration. For example: ca_3c7c28a".format(
                     self.PARAM_CA))
-            return False
 
         # Carrier aggregation configurations are indicated with the band numbers
         # followed by the CA classes in a single string. For example, for 5 CA
@@ -91,10 +88,9 @@
         ca_configs = re.findall(r'(\d+[abcABC])', values[1])
 
         if not ca_configs:
-            self.log.error(
+            raise ValueError(
                 "The CA configuration has to be indicated with one string as "
                 "in the following example: ca_3c7c28a".format(self.PARAM_CA))
-            return False
 
         carriers = []
         bts_index = 0
@@ -108,20 +104,19 @@
             ca_class = ca[-1]
 
             if ca_class.upper() == 'B':
-                self.log.error("Class B carrier aggregation is not supported.")
-                return False
+                raise ValueError(
+                    "Class B carrier aggregation is not supported.")
 
             if band in carriers:
-                self.log.error("Intra-band non contiguous carrier aggregation "
-                               "is not supported.")
-                return False
+                raise ValueError(
+                    "Intra-band non contiguous carrier aggregation "
+                    "is not supported.")
 
             if ca_class.upper() == 'A':
 
                 if bts_index >= len(self.bts):
-                    self.log.error("This callbox model doesn't allow the "
-                                   "requested CA configuration")
-                    return False
+                    raise ValueError("This callbox model doesn't allow the "
+                                     "requested CA configuration")
 
                 self.set_band_with_defaults(
                     self.bts[bts_index],
@@ -133,9 +128,8 @@
             elif ca_class.upper() == 'C':
 
                 if bts_index + 1 >= len(self.bts):
-                    self.log.error("This callbox model doesn't allow the "
-                                   "requested CA configuration")
-                    return False
+                    raise ValueError("This callbox model doesn't allow the "
+                                     "requested CA configuration")
 
                 self.set_band_with_defaults(
                     self.bts[bts_index],
@@ -149,18 +143,16 @@
                 bts_index += 2
 
             else:
-                self.log.error("Invalid carrier aggregation configuration: "
-                               "{}{}.".format(band, ca_class))
-                return False
+                raise ValueError("Invalid carrier aggregation configuration: "
+                                 "{}{}.".format(band, ca_class))
 
             carriers.append(band)
 
         # Ensure there are at least two carriers being used
         self.num_carriers = bts_index
         if self.num_carriers < 2:
-            self.log.error("At least two carriers need to be indicated for the"
-                           " carrier aggregation sim.")
-            return False
+            raise ValueError("At least two carriers need to be indicated for "
+                             "the carrier aggregation sim.")
 
         # Get the bw for each carrier
         # This is an optional parameter, by default the maximum bandwidth for
@@ -213,17 +205,16 @@
                                              self.num_carriers)
 
         if not mimo_values:
-            self.log.error("The test parameter '{}' has to be included in the "
-                           "test name followed by the MIMO mode for each "
-                           "carrier separated by underscores.".format(
-                               self.PARAM_MIMO))
-            return False
+            raise ValueError(
+                "The test parameter '{}' has to be included in the "
+                "test name followed by the MIMO mode for each "
+                "carrier separated by underscores.".format(self.PARAM_MIMO))
 
         if len(mimo_values) != self.num_carriers + 1:
-            self.log.error("The test parameter '{}' has to be followed by "
-                           "a number of MIMO mode values equal to the number "
-                           "of carriers being used.".format(self.PARAM_MIMO))
-            return False
+            raise ValueError(
+                "The test parameter '{}' has to be followed by "
+                "a number of MIMO mode values equal to the number "
+                "of carriers being used.".format(self.PARAM_MIMO))
 
         for bts_index in range(self.num_carriers):
 
@@ -234,16 +225,15 @@
                     requested_mimo = mimo_mode
                     break
             else:
-                self.log.error("The mimo mode must be one of %s." %
-                               {elem.value
-                                for elem in LteSimulation.MimoMode})
-                return False
+                raise ValueError(
+                    "The mimo mode must be one of %s." %
+                    {elem.value
+                     for elem in LteSimulation.MimoMode})
 
             if (requested_mimo == LteSimulation.MimoMode.MIMO_4x4
                     and self.anritsu._md8475_version == 'A'):
-                self.log.error("The test requires 4x4 MIMO, but that is not "
-                               "supported by the MD8475A callbox.")
-                return False
+                raise ValueError("The test requires 4x4 MIMO, but that is not "
+                                 "supported by the MD8475A callbox.")
 
             self.set_mimo_mode(self.bts[bts_index], requested_mimo)
 
@@ -255,11 +245,10 @@
                         requested_tm = tm
                         break
                 else:
-                    self.log.error(
+                    raise ValueError(
                         "The TM must be one of %s." %
                         {elem.value
                          for elem in LteSimulation.MimoMode})
-                    return False
             else:
                 # Provide default values if the TM parameter is not set
                 if requested_mimo == LteSimulation.MimoMode.MIMO_1x1:
@@ -272,8 +261,101 @@
             self.log.info("Cell {} was set to {} and {} MIMO.".format(
                 bts_index + 1, requested_tm.value, requested_mimo.value))
 
-        # No errors were found
-        return True
+        # Get uplink power
+
+        ul_power = self.get_uplink_power_from_parameters(parameters)
+
+        # Power is not set on the callbox until after the simulation is
+        # started. Saving this value in a variable for later
+        self.sim_ul_power = ul_power
+
+        # Get downlink power
+
+        dl_power = self.get_downlink_power_from_parameters(parameters)
+
+        # Power is not set on the callbox until after the simulation is
+        # started. Saving this value in a variable for later
+        self.sim_dl_power = dl_power
+
+        # Setup scheduling mode
+
+        values = self.consume_parameter(parameters, self.PARAM_SCHEDULING, 1)
+
+        if not values:
+            scheduling = LteSimulation.SchedulingMode.STATIC
+            self.log.warning(
+                "The test name does not include the '{}' parameter. Setting to "
+                "{} by default.".format(scheduling.value,
+                                        self.PARAM_SCHEDULING))
+        else:
+            for scheduling_mode in LteSimulation.SchedulingMode:
+                if values[1].upper() == scheduling_mode.value:
+                    scheduling = scheduling_mode
+                    break
+            else:
+                raise ValueError(
+                    "The test name parameter '{}' has to be followed by one of "
+                    "{}.".format(
+                        self.PARAM_SCHEDULING,
+                        {elem.value
+                         for elem in LteSimulation.SchedulingMode}))
+
+        if scheduling == LteSimulation.SchedulingMode.STATIC:
+
+            values = self.consume_parameter(parameters, self.PARAM_PATTERN, 2)
+
+            if not values:
+                self.log.warning(
+                    "The '{}' parameter was not set, using 100% RBs for both "
+                    "DL and UL. To set the percentages of total RBs include "
+                    "the '{}' parameter followed by two ints separated by an "
+                    "underscore indicating downlink and uplink percentages."
+                    .format(self.PARAM_PATTERN, self.PARAM_PATTERN))
+                dl_pattern = 100
+                ul_pattern = 100
+            else:
+                dl_pattern = int(values[1])
+                ul_pattern = int(values[2])
+
+            if (dl_pattern, ul_pattern) not in [(0, 100), (100, 0), (100,
+                                                                     100)]:
+                raise ValueError(
+                    "Only full RB allocation for DL or UL is supported in CA "
+                    "sims. The allowed combinations are 100/0, 0/100 and "
+                    "100/100.")
+
+            if self.dl_256_qam and bw == 1.4:
+                mcs_dl = 26
+            elif not self.dl_256_qam and self.tbs_pattern_on and bw != 1.4:
+                mcs_dl = 28
+            else:
+                mcs_dl = 27
+
+            if self.ul_64_qam:
+                mcs_ul = 28
+            else:
+                mcs_ul = 23
+
+            for bts_index in range(self.num_carriers):
+
+                dl_rbs, ul_rbs = self.allocation_percentages_to_rbs(
+                    self.bts[bts_index], dl_pattern, ul_pattern)
+
+                self.set_scheduling_mode(
+                    self.bts[bts_index],
+                    LteSimulation.SchedulingMode.STATIC,
+                    packet_rate=BtsPacketRate.LTE_MANUAL,
+                    nrb_dl=dl_rbs,
+                    nrb_ul=ul_rbs,
+                    mcs_ul=mcs_ul,
+                    mcs_dl=mcs_dl)
+
+        else:
+
+            for bts_index in range(self.num_carriers):
+
+                self.set_scheduling_mode(self.bts[bts_index],
+                                         LteSimulation.SchedulingMode.DYNAMIC)
 
     def set_band_with_defaults(self, bts, band, calibrate_if_necessary=True):
         """ Switches to the given band restoring default values
@@ -296,6 +378,22 @@
 
         self.set_band(bts, band, calibrate_if_necessary=calibrate_if_necessary)
 
+    def set_downlink_rx_power(self, bts, rsrp):
+        """ Sets downlink rx power in RSRP using calibration for every cell
+
+        Calls the method in the parent class for each base station.
+
+        Args:
+            bts: this argument is ignored, as all the basestations need to have
+                the same downlink rx power
+            rsrp: desired rsrp, contained in a key value pair
+        """
+
+        for bts_index in range(self.num_carriers):
+            self.log.info("Setting DL power for BTS{}.".format(bts_index + 1))
+            # Use parent method to set signal level
+            super().set_downlink_rx_power(self.bts[bts_index], rsrp)
+
     def start_test_case(self):
         """ Attaches the phone to all the other basestations.
 
@@ -321,8 +419,17 @@
         while self.anritsu.get_testcase_status() == "0":
             retry_counter += 1
             if retry_counter == 3:
-                self.log.error("The test case failed to start.")
-                return False
+                raise RuntimeError("The test case failed to start after {} "
+                                   "retries. The connection between the phone "
+                                   "and the basestation might be unstable."
+                                   .format(retry_counter))
             time.sleep(10)
 
-        return True
+    def maximum_downlink_throughput(self):
+        """ Calculates maximum downlink throughput as the sum of all the active
+        carriers.
+        """
+
+        return sum(
+            self.bts_maximum_downlink_throughtput(self.bts[bts_index])
+            for bts_index in range(self.num_carriers))
diff --git a/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py
index 9daa405..73a5cd0 100644
--- a/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py
+++ b/acts/framework/acts/test_utils/power/tel_simulations/LteSimulation.py
@@ -301,22 +301,18 @@
 
         Args:
             parameters: list of parameters
-        Returns:
-            False if there was an error while parsing the config
         """
 
-        if not super().parse_parameters(parameters):
-            return False
+        super().parse_parameters(parameters)
 
         # Setup band
 
         values = self.consume_parameter(parameters, self.PARAM_BAND, 1)
 
         if not values:
-            self.log.error(
+            raise ValueError(
                 "The test name needs to include parameter '{}' followed by "
                 "the required band number.".format(self.PARAM_BAND))
-            return False
 
         band = values[1]
 
@@ -324,16 +320,16 @@
 
         # Set DL/UL frame configuration
         if self.get_duplex_mode(band) == self.DuplexMode.TDD:
-            try:
-                values = self.consume_parameter(parameters,
-                                                self.PARAM_FRAME_CONFIG, 1)
-                frame_config = int(values[1])
-            except:
-                self.log.error("When a TDD band is selected the frame "
-                               "structure has to be indicated with the '{}' "
-                               "parameter followed by a number from 0 to 6."
-                               .format(self.PARAM_FRAME_CONFIG))
-                return False
+
+            values = self.consume_parameter(parameters,
+                                            self.PARAM_FRAME_CONFIG, 1)
+            if not values:
+                raise ValueError("When a TDD band is selected the frame "
+                                 "structure has to be indicated with the '{}' "
+                                 "parameter followed by a number from 0 to 6."
+                                 .format(self.PARAM_FRAME_CONFIG))
+
+            frame_config = int(values[1])
 
             self.set_dlul_configuration(self.bts1, frame_config)
 
@@ -342,10 +338,10 @@
         values = self.consume_parameter(parameters, self.PARAM_BW, 1)
 
         if not values:
-            self.log.error(
-                "The test name needs to include parameter {} followed by an int"
-                " value (to indicate 1.4 MHz use 14).".format(self.PARAM_BW))
-            return False
+            raise ValueError(
+                "The test name needs to include parameter {} followed by an "
+                "int value (to indicate 1.4 MHz use 14).".format(
+                    self.PARAM_BW))
 
         bw = float(values[1])
 
@@ -359,25 +355,22 @@
         values = self.consume_parameter(parameters, self.PARAM_MIMO, 1)
 
         if not values:
-            self.log.error(
-                "The test name needs to include parameter '{}' followed by the"
-                " mimo mode.".format(self.PARAM_MIMO))
-            return False
+            raise ValueError(
+                "The test name needs to include parameter '{}' followed by the "
+                "mimo mode.".format(self.PARAM_MIMO))
 
         for mimo_mode in LteSimulation.MimoMode:
             if values[1] == mimo_mode.value:
                 self.set_mimo_mode(self.bts1, mimo_mode)
                 break
         else:
-            self.log.error("The {} parameter needs to be followed by either "
-                           "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO))
-            return False
+            raise ValueError("The {} parameter needs to be followed by either "
+                             "1x1, 2x2 or 4x4.".format(self.PARAM_MIMO))
 
         if (mimo_mode == LteSimulation.MimoMode.MIMO_4x4
                 and self.anritsu._md8475_version == 'A'):
-            self.log.error("The test requires 4x4 MIMO, but that is not "
-                           "supported by the MD8475A callbox.")
-            return False
+            raise ValueError("The test requires 4x4 MIMO, but that is not "
+                             "supported by the MD8475A callbox.")
 
         self.set_mimo_mode(self.bts1, mimo_mode)
 
@@ -386,21 +379,19 @@
         values = self.consume_parameter(parameters, self.PARAM_TM, 1)
 
         if not values:
-            self.log.error(
-                "The test name needs to include parameter {} followed by an int"
-                " value from 1 to 4 indicating transmission mode.".format(
+            raise ValueError(
+                "The test name needs to include parameter {} followed by an "
+                "int value from 1 to 4 indicating transmission mode.".format(
                     self.PARAM_TM))
-            return False
 
         for tm in LteSimulation.TransmissionMode:
             if values[1] == tm.value[2:]:
                 self.set_transmission_mode(self.bts1, tm)
                 break
         else:
-            self.log.error("The {} parameter needs to be followed by either "
-                           "TM1, TM2, TM3, TM4, TM7, TM8 or TM9.".format(
-                               self.PARAM_MIMO))
-            return False
+            raise ValueError("The {} parameter needs to be followed by either "
+                             "TM1, TM2, TM3, TM4, TM7, TM8 or TM9.".format(
+                                 self.PARAM_MIMO))
 
         # Setup scheduling mode
 
@@ -416,10 +407,9 @@
         elif values[1] == self.PARAM_SCHEDULING_STATIC:
             scheduling = LteSimulation.SchedulingMode.STATIC
         else:
-            self.log.error(
+            raise ValueError(
                 "The test name parameter '{}' has to be followed by either "
                 "'dynamic' or 'static'.".format(self.PARAM_SCHEDULING))
-            return False
 
         if scheduling == LteSimulation.SchedulingMode.STATIC:
 
@@ -439,10 +429,9 @@
                 ul_pattern = int(values[2])
 
             if not (0 <= dl_pattern <= 100 and 0 <= ul_pattern <= 100):
-                self.log.error(
+                raise ValueError(
                     "The scheduling pattern parameters need to be two "
                     "positive numbers between 0 and 100.")
-                return False
 
             dl_rbs, ul_rbs = self.allocation_percentages_to_rbs(
                 self.bts1, dl_pattern, ul_pattern)
@@ -473,66 +462,74 @@
             self.set_scheduling_mode(self.bts1,
                                      LteSimulation.SchedulingMode.DYNAMIC)
 
-        # Setup uplink power
+        # Get uplink power
+
+        ul_power = self.get_uplink_power_from_parameters(parameters)
+
+        # Power is not set on the callbox until after the simulation is
+        # started. Saving this value in a variable for later
+        self.sim_ul_power = ul_power
+
+        # Get downlink power
+
+        dl_power = self.get_downlink_power_from_parameters(parameters)
+
+        # Power is not set on the callbox until after the simulation is
+        # started. Saving this value in a variable for later
+        self.sim_dl_power = dl_power
+
+    def get_uplink_power_from_parameters(self, parameters):
+        """ Reads uplink power from a list of parameters. """
 
         values = self.consume_parameter(parameters, self.PARAM_UL_PW, 1)
 
         if not values or values[1] not in self.uplink_signal_level_dictionary:
-            self.log.error(
+            raise ValueError(
                 "The test name needs to include parameter {} followed by one "
                 "the following values: {}.".format(self.PARAM_UL_PW, [
-                    "\n" + val
-                    for val in self.uplink_signal_level_dictionary.keys()
+                    val for val in self.uplink_signal_level_dictionary.keys()
                 ]))
-            return False
 
-        # Power is not set on the callbox until after the simulation is
-        # started. Will save this value in a variable and use it lated
-        self.sim_ul_power = self.uplink_signal_level_dictionary[values[1]]
+        return self.uplink_signal_level_dictionary[values[1]]
 
-        # Setup downlink power
+    def get_downlink_power_from_parameters(self, parameters):
+        """ Reads downlink power from a list of parameters. """
 
         values = self.consume_parameter(parameters, self.PARAM_DL_PW, 1)
 
         if values:
             if values[1] not in self.downlink_rsrp_dictionary:
-                self.log.error("Invalid signal level value {}.".format(
+                raise ValueError("Invalid signal level value {}.".format(
                     values[1]))
-                return False
             else:
-                power = self.downlink_rsrp_dictionary[values[1]]
+                return self.downlink_rsrp_dictionary[values[1]]
         else:
             # Use default value
             power = self.downlink_rsrp_dictionary['excellent']
-            self.log.error(
-                "No DL signal level value was indicated in the test parameters."
-                " Using default value of {} RSRP.".format(power))
+            self.log.info(
+                "No DL signal level value was indicated in the test "
+                "parameters. Using default value of {} RSRP.".format(power))
+            return power
 
-        # Power is not set on the callbox until after the simulation is
-        # started. Will save this value in a variable and use it later
-        self.sim_dl_power = power
-
-        # No errors were found
-        return True
-
-    def set_downlink_rx_power(self, rsrp):
+    def set_downlink_rx_power(self, bts, rsrp):
         """ Sets downlink rx power in RSRP using calibration
 
         Lte simulation overrides this method so that it can convert from
         RSRP to total signal power transmitted from the basestation.
 
         Args:
+            bts: the base station in which to change the signal level
             rsrp: desired rsrp, contained in a key value pair
         """
 
-        power = self.rsrp_to_signal_power(rsrp, self.bts1)
+        power = self.rsrp_to_signal_power(rsrp, bts)
 
         self.log.info(
             "Setting downlink signal level to {} RSRP ({} dBm)".format(
                 rsrp, power))
 
         # Use parent method to set signal level
-        super().set_downlink_rx_power(power)
+        super().set_downlink_rx_power(bts, power)
 
     def downlink_calibration(self,
                              bts,
@@ -602,11 +599,24 @@
 
         """
 
-        bandwidth = self.bts1.bandwidth
-        rb_ratio = float(
-            self.bts1.nrb_dl) / self.total_rbs_dictionary[bandwidth]
-        streams = float(self.bts1.dl_antenna)
-        mcs = self.bts1.lte_mcs_dl
+        return self.bts_maximum_downlink_throughtput(self.bts1)
+
+    def bts_maximum_downlink_throughtput(self, bts):
+        """ Calculates maximum achievable downlink throughput for the selected
+        basestation.
+
+        Args:
+            bts: basestation handle
+
+        Returns:
+            Maximum throughput in mbps.
+
+        """
+
+        bandwidth = bts.bandwidth
+        rb_ratio = float(bts.nrb_dl) / self.total_rbs_dictionary[bandwidth]
+        streams = float(bts.dl_antenna)
+        mcs = bts.lte_mcs_dl
 
         max_rate_per_stream = None
 
@@ -673,10 +683,23 @@
 
         """
 
-        bandwidth = self.bts1.bandwidth
-        rb_ratio = float(
-            self.bts1.nrb_ul) / self.total_rbs_dictionary[bandwidth]
-        mcs = self.bts1.lte_mcs_ul
+        return self.bts_maximum_uplink_throughtput(self.bts1)
+
+    def bts_maximum_uplink_throughtput(self, bts):
+        """ Calculates maximum achievable uplink throughput for the selected
+        basestation.
+
+        Args:
+            bts: basestation handle
+
+        Returns:
+            Maximum throughput in mbps.
+
+        """
+
+        bandwidth = bts.bandwidth
+        rb_ratio = float(bts.nrb_ul) / self.total_rbs_dictionary[bandwidth]
+        mcs = bts.lte_mcs_ul
 
         max_rate_per_stream = None
         if mcs == "23" and not self.ul_64_qam:
diff --git a/acts/framework/acts/test_utils/power/tel_simulations/UmtsSimulation.py b/acts/framework/acts/test_utils/power/tel_simulations/UmtsSimulation.py
index a2cb7ac..aa89d01 100644
--- a/acts/framework/acts/test_utils/power/tel_simulations/UmtsSimulation.py
+++ b/acts/framework/acts/test_utils/power/tel_simulations/UmtsSimulation.py
@@ -107,22 +107,18 @@
 
         Args:
             parameters: list of parameters
-        Returns:
-            False if there was an error while parsing the config
         """
 
-        if not super().parse_parameters(parameters):
-            return False
+        super().parse_parameters(parameters)
 
         # Setup band
 
         values = self.consume_parameter(parameters, self.PARAM_BAND, 1)
 
         if not values:
-            self.log.error(
+            raise ValueError(
                 "The test name needs to include parameter '{}' followed by "
                 "the required band number.".format(self.PARAM_BAND))
-            return False
 
         self.set_band(self.bts1, values[1])
 
@@ -135,10 +131,9 @@
                 self.PARAM_RELEASE_VERSION_7, self.PARAM_RELEASE_VERSION_8,
                 self.PARAM_RELEASE_VERSION_99
         ]:
-            self.log.error(
+            raise ValueError(
                 "The test name needs to include the parameter {} followed by a "
                 "valid release version.".format(self.PARAM_RELEASE_VERSION))
-            return False
 
         self.set_release_version(self.bts1, values[1])
 
@@ -146,13 +141,12 @@
 
         values = self.consume_parameter(parameters, self.PARAM_UL_PW, 1)
         if not values or values[1] not in self.uplink_signal_level_dictionary:
-            self.log.error(
+            raise ValueError(
                 "The test name needs to include parameter {} followed by "
                 "one the following values: {}.".format(self.PARAM_UL_PW, [
                     "\n" + val
                     for val in self.uplink_signal_level_dictionary.keys()
                 ]))
-            return False
 
         # Power is not set on the callbox until after the simulation is
         # started. Will save this value in a variable and use it later
@@ -163,22 +157,16 @@
         values = self.consume_parameter(parameters, self.PARAM_DL_PW, 1)
 
         if not values or values[1] not in self.downlink_rscp_dictionary:
-            self.log.error(
+            raise ValueError(
                 "The test name needs to include parameter {} followed by "
                 "one of the following values: {}.".format(
-                    self.PARAM_DL_PW, [
-                        "\n" + val
-                        for val in self.downlink_rscp_dictionary.keys()
-                    ]))
-            return False
+                    self.PARAM_DL_PW,
+                    [val for val in self.downlink_rscp_dictionary.keys()]))
 
         # Power is not set on the callbox until after the simulation is
         # started. Will save this value in a variable and use it later
         self.sim_dl_power = self.downlink_rscp_dictionary[values[1]]
 
-        # No errors were found
-        return True
-
     def set_release_version(self, bts, release_version):
         """ Sets the release version.
 
diff --git a/acts/framework/acts/test_utils/tel/TelephonyBaseTest.py b/acts/framework/acts/test_utils/tel/TelephonyBaseTest.py
index d158e25..05ec9e2 100644
--- a/acts/framework/acts/test_utils/tel/TelephonyBaseTest.py
+++ b/acts/framework/acts/test_utils/tel/TelephonyBaseTest.py
@@ -451,8 +451,8 @@
     def _take_bug_report(self, test_name, begin_time):
         test_log_path = os.path.join(self.log_path, test_name)
         utils.create_dir(test_log_path)
-        # Extract test_run_info.txt, test_run_detail.txt
-        for file_name in ("test_run_info.txt", "test_run_details.txt"):
+        # Extract test_run_info.txt, test_run_debug.txt
+        for file_name in ("test_run_info.txt", "test_run_debug.txt"):
             extract_test_log(self.log, os.path.join(self.log_path, file_name),
                              os.path.join(test_log_path,
                                           "%s_%s" % (test_name, file_name)),
diff --git a/acts/framework/acts/test_utils/tel/tel_data_utils.py b/acts/framework/acts/test_utils/tel/tel_data_utils.py
index f7d397f..1c384eb 100644
--- a/acts/framework/acts/test_utils/tel/tel_data_utils.py
+++ b/acts/framework/acts/test_utils/tel/tel_data_utils.py
@@ -381,8 +381,7 @@
             return False
 
         if not ad.droid.connectivityNetworkIsConnected():
-            ad.log.error("Network is NOT connected!")
-            return False
+            ad.log.warning("Network is NOT connected!")
 
         if not wait_for_cell_data_connection(log, ad, True):
             ad.log.error("Failed to enable cell data connection")
diff --git a/acts/framework/acts/test_utils/tel/tel_test_utils.py b/acts/framework/acts/test_utils/tel/tel_test_utils.py
index 07df355..37e9ddb 100644
--- a/acts/framework/acts/test_utils/tel/tel_test_utils.py
+++ b/acts/framework/acts/test_utils/tel/tel_test_utils.py
@@ -4404,7 +4404,8 @@
                 ad_tx.log.warning("No %s or %s event.", EventMmsSentSuccess,
                                   EventMmsSentFailure)
 
-            if not wait_for_matching_mms(log, ad_rx, phonenumber_tx, message):
+            if not wait_for_matching_mms(log, ad_rx, phonenumber_tx,
+                                         message, max_wait_time):
                 return False
         except Exception as e:
             log.error("Exception error %s", e)
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 67bac00..6d70aca 100755
--- a/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
+++ b/acts/framework/acts/test_utils/wifi/wifi_test_utils.py
@@ -16,7 +16,7 @@
 
 import logging
 import os
-import pprint
+import shutil
 import time
 
 from enum import IntEnum
@@ -49,12 +49,12 @@
         "AP1_on_AP2_off": [
             0,
             0,
-            95,
-            95
+            60,
+            60
         ],
         "AP1_off_AP2_on": [
-            95,
-            95,
+            60,
+            60,
             0,
             0
         ],
@@ -1664,6 +1664,36 @@
                        attn_val_name)
         raise
 
+def set_attns_steps(attenuator, attn_val_name, steps=10, wait_time=12):
+    """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.
+        steps: Number of attenuator changes to reach the target value.
+        wait_time: Sleep time for each change of attenuator.
+    """
+    logging.info("Set attenuation values to %s in %d step(s)", roaming_attn[attn_val_name], steps)
+    current_atten = [
+            attenuator[0].get_atten(), attenuator[1].get_atten(),
+            attenuator[2].get_atten(), attenuator[3].get_atten()]
+    target_atten = [
+            roaming_attn[attn_val_name][0], roaming_attn[attn_val_name][1],
+            roaming_attn[attn_val_name][2], roaming_attn[attn_val_name][3]]
+    while steps > 0:
+        next_atten = list(map(
+                lambda x, y: round((y + (steps - 1) * x) / steps) , current_atten, target_atten))
+        try:
+            attenuator[0].set_atten(next_atten[0])
+            attenuator[1].set_atten(next_atten[1])
+            attenuator[2].set_atten(next_atten[2])
+            attenuator[3].set_atten(next_atten[3])
+            time.sleep(wait_time)
+        except:
+            logging.exception("Failed to set attenuation values %s.", attn_val_name)
+            raise
+        current_atten = next_atten
+        steps = steps - 1
 
 def trigger_roaming_and_validate(dut, attenuator, attn_val_name, expected_con):
     """Sets attenuators to trigger roaming and validate the DUT connected
@@ -1678,9 +1708,7 @@
         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)
 
     verify_wifi_connection_info(dut, expected_con)
     expected_bssid = expected_con[WifiEnums.BSSID_KEY]
@@ -1710,8 +1738,8 @@
         test_name: test name to be used for pcap file name
 
     Returns:
-        Dictionary with pid of the tcpdump process as key and log path
-        of the file name as the value
+        Dictionary with wifi band as key and the tuple
+        (pcap Process object, log directory) as the value
     """
     log_dir = os.path.join(log_path, test_name)
     utils.create_dir(log_dir)
@@ -1719,13 +1747,13 @@
         bands = [BAND_2G, BAND_5G]
     else:
         bands = [wifi_band]
-    pids = {}
+    procs = {}
     for band in bands:
-        pid = pcap.start_packet_capture(band, log_dir, test_name)
-        pids[pid] = os.path.join(log_dir, test_name)
-    return pids
+        proc = pcap.start_packet_capture(band, log_dir, test_name)
+        procs[band] = (proc, os.path.join(log_dir, test_name))
+    return procs
 
-def stop_pcap(pcap, pids, test_status=None):
+def stop_pcap(pcap, procs, test_status=None):
     """Stop packet capture in monitor mode.
 
     Since, the pcap logs in monitor mode can be very large, we will
@@ -1734,14 +1762,14 @@
 
     Args:
         pcap: packet capture object
-        pids: dictionary returned by start_pcap
+        procs: dictionary returned by start_pcap
         test_status: status of the test case
     """
-    for pid, fname in pids.items():
-        pcap.stop_packet_capture(pid)
+    for proc, fname in procs.values():
+        pcap.stop_packet_capture(proc)
 
     if test_status:
-        os.system('rm -rf %s' % os.path.dirname(fname))
+        shutil.rmtree(os.path.dirname(fname))
 
 def start_cnss_diags(ads):
     for ad in ads:
diff --git a/acts/framework/tests/acts_import_test_utils_test.py b/acts/framework/tests/acts_import_test_utils_test.py
index a0a66fd..c44522c 100755
--- a/acts/framework/tests/acts_import_test_utils_test.py
+++ b/acts/framework/tests/acts_import_test_utils_test.py
@@ -16,39 +16,6 @@
 
 import unittest
 
-from acts import utils
-from acts.test_utils.bt import BleEnum
-from acts.test_utils.bt import BluetoothBaseTest
-from acts.test_utils.bt import BluetoothCarHfpBaseTest
-from acts.test_utils.bt import BtEnum
-from acts.test_utils.bt import GattConnectedBaseTest
-from acts.test_utils.bt import GattEnum
-from acts.test_utils.bt import bt_contacts_utils
-from acts.test_utils.bt import bt_gatt_utils
-from acts.test_utils.bt import bt_test_utils
-from acts.test_utils.bt import native_bt_test_utils
-
-from acts.test_utils.car import car_bt_utils
-from acts.test_utils.car import car_media_utils
-from acts.test_utils.car import car_telecom_utils
-from acts.test_utils.car import tel_telecom_utils
-
-from acts.test_utils.net import connectivity_const
-from acts.test_utils.net import connectivity_const
-
-from acts.test_utils.tel import TelephonyBaseTest
-from acts.test_utils.tel import tel_atten_utils
-from acts.test_utils.tel import tel_data_utils
-from acts.test_utils.tel import tel_defines
-from acts.test_utils.tel import tel_lookup_tables
-from acts.test_utils.tel import tel_subscription_utils
-from acts.test_utils.tel import tel_test_utils
-from acts.test_utils.tel import tel_video_utils
-from acts.test_utils.tel import tel_voice_utils
-
-from acts.test_utils.wifi import wifi_constants
-from acts.test_utils.wifi import wifi_test_utils
-
 
 class ActsImportTestUtilsTest(unittest.TestCase):
     """This test class has unit tests for the implementation of everything
@@ -60,8 +27,44 @@
 
         This test will fail if any import was unsuccessful.
         """
-        self.assertTrue(True)
+        try:
+            from acts import utils
+
+            from acts.test_utils.bt import BleEnum
+            from acts.test_utils.bt import BluetoothBaseTest
+            from acts.test_utils.bt import BluetoothCarHfpBaseTest
+            from acts.test_utils.bt import BtEnum
+            from acts.test_utils.bt import GattConnectedBaseTest
+            from acts.test_utils.bt import GattEnum
+            from acts.test_utils.bt import bt_contacts_utils
+            from acts.test_utils.bt import bt_gatt_utils
+            from acts.test_utils.bt import bt_test_utils
+            from acts.test_utils.bt import native_bt_test_utils
+
+            from acts.test_utils.car import car_bt_utils
+            from acts.test_utils.car import car_media_utils
+            from acts.test_utils.car import car_telecom_utils
+            from acts.test_utils.car import tel_telecom_utils
+
+            from acts.test_utils.net import connectivity_const
+            from acts.test_utils.net import connectivity_const
+
+            from acts.test_utils.tel import TelephonyBaseTest
+            from acts.test_utils.tel import tel_atten_utils
+            from acts.test_utils.tel import tel_data_utils
+            from acts.test_utils.tel import tel_defines
+            from acts.test_utils.tel import tel_lookup_tables
+            from acts.test_utils.tel import tel_subscription_utils
+            from acts.test_utils.tel import tel_test_utils
+            from acts.test_utils.tel import tel_video_utils
+            from acts.test_utils.tel import tel_voice_utils
+
+            from acts.test_utils.wifi import wifi_constants
+            from acts.test_utils.wifi import wifi_test_utils
+
+        except Exception:
+            self.fail('Unable to import all supported test_utils')
 
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     unittest.main()
diff --git a/acts/framework/tests/libs/logging/log_stream_test.py b/acts/framework/tests/libs/logging/log_stream_test.py
index 6c9b264..37fc803 100755
--- a/acts/framework/tests/libs/logging/log_stream_test.py
+++ b/acts/framework/tests/libs/logging/log_stream_test.py
@@ -89,10 +89,10 @@
             msg='__validate_styles did not raise the expected error message')
 
     @mock.patch('os.makedirs')
-    def test_validate_styles_raises_when_both_testclass_and_testcase_set(
+    def test_validate_styles_raises_when_multiple_file_outputs_set(
             self, *_):
-        """Tests that a style is invalid if both TESTCLASS_LOG and TESTCASE_LOG
-        locations are set for the same log level.
+        """Tests that a style is invalid if more than one of MONOLITH_LOG,
+        TESTCLASS_LOG, and TESTCASE_LOG is set for the same log level.
 
         If the error is NOT raised, then a LogStream can create a Logger that
         has multiple LogHandlers trying to write to the same file.
@@ -103,7 +103,26 @@
                 log_styles=[LogStyles.LOG_DEBUG | LogStyles.TESTCASE_LOG,
                             LogStyles.LOG_DEBUG | LogStyles.TESTCLASS_LOG])
         self.assertTrue(
-            'Both TESTCLASS_LOG' in catch.exception.args[0],
+            'More than one of' in catch.exception.args[0],
+            msg='__validate_styles did not raise the expected error message')
+
+        with self.assertRaises(InvalidStyleSetError) as catch:
+            log_stream.create_logger(
+                self._testMethodName,
+                log_styles=[LogStyles.LOG_DEBUG | LogStyles.TESTCASE_LOG,
+                            LogStyles.LOG_DEBUG | LogStyles.MONOLITH_LOG])
+        self.assertTrue(
+            'More than one of' in catch.exception.args[0],
+            msg='__validate_styles did not raise the expected error message')
+
+        with self.assertRaises(InvalidStyleSetError) as catch:
+            log_stream.create_logger(
+                self._testMethodName,
+                log_styles=[LogStyles.LOG_DEBUG | LogStyles.TESTCASE_LOG,
+                            LogStyles.LOG_DEBUG | LogStyles.TESTCLASS_LOG,
+                            LogStyles.LOG_DEBUG | LogStyles.MONOLITH_LOG])
+        self.assertTrue(
+            'More than one of' in catch.exception.args[0],
             msg='__validate_styles did not raise the expected error message')
 
     @mock.patch('os.makedirs')
@@ -189,8 +208,7 @@
         """Tests that handle_style does not create test case and test class
         only descriptors without LogStyle.TESTCASE_LOG.
         """
-        info_monolith_log = (LogStyles.LOG_INFO + LogStyles.MONOLITH_LOG +
-                             LogStyles.TESTCLASS_LOG)
+        info_monolith_log = (LogStyles.LOG_INFO + LogStyles.MONOLITH_LOG)
 
         with self.patch('FileHandler'):
             log_stream.create_logger(self._testMethodName,
@@ -232,8 +250,7 @@
         """Tests that handle_style does not create test class descriptors
         without LogStyle.TESTCLASS_LOG.
         """
-        info_monolith_log = (LogStyles.LOG_INFO + LogStyles.MONOLITH_LOG +
-                             LogStyles.TESTCASE_LOG)
+        info_monolith_log = (LogStyles.LOG_INFO + LogStyles.MONOLITH_LOG)
 
         with self.patch('FileHandler'):
             log_stream.create_logger(self._testMethodName,
@@ -379,7 +396,9 @@
         """Tests that the handlers generated from the descriptors are added
         to the associated logger and the given handlers list."""
         info_testcase_log = LogStyles.LOG_INFO + LogStyles.TESTCASE_LOG
-        with self.patch('FileHandler'):
+        with self.patch('FileHandler',
+                        side_effect=[mock.MagicMock(name='handler1'),
+                                     mock.MagicMock(name='handler2')]):
             log_stream.create_logger(self._testMethodName,
                                      log_styles=info_testcase_log)
 
@@ -416,16 +435,16 @@
     def test_on_test_case_end_enables_class_level_handlers(self, *_):
         info_testcase_log = LogStyles.LOG_INFO + LogStyles.TESTCASE_LOG
         with self.patch('FileHandler',
-                        side_effect=[mock.MagicMock(name='class_handler'),
+                        side_effect=[mock.MagicMock(name='run_handler'),
+                                     mock.MagicMock(name='class_handler'),
                                      mock.MagicMock(name='case_handler')]):
             log_stream.create_logger(self._testMethodName,
                                      log_styles=info_testcase_log)
 
         created_log_stream = log_stream._log_streams[self._testMethodName]
         created_log_stream.on_test_class_begin(TestClassBeginEvent(TestClass()))
-        for handler in created_log_stream.logger.handlers:
-            if not isinstance(handler, logging.NullHandler):
-                created_log_stream.logger.removeHandler(handler)
+        created_log_stream.on_test_case_begin(
+            TestCaseBeginEvent(TestClass(), TestClass.test_case))
         created_log_stream.on_test_case_end('')
 
         self.assertGreater(
@@ -455,15 +474,15 @@
     def test_on_test_case_begin_disables_class_level_handlers(self, *_):
         info_testcase_log = LogStyles.LOG_INFO + LogStyles.TESTCASE_LOG
         with self.patch('FileHandler',
-                        side_effect=[mock.MagicMock(name='class_handler'),
+                        side_effect=[mock.MagicMock(name='run_handler'),
+                                     mock.MagicMock(name='class_handler'),
                                      mock.MagicMock(name='case_handler')]):
             log_stream.create_logger(self._testMethodName,
                                      log_styles=info_testcase_log)
 
         created_log_stream = log_stream._log_streams[self._testMethodName]
 
-        created_log_stream.on_test_class_begin(
-            TestClassBeginEvent(TestClass()))
+        created_log_stream.on_test_class_begin(TestClassBeginEvent(TestClass()))
         created_log_stream.on_test_case_begin(
             TestCaseBeginEvent(TestClass(), TestClass.test_case))
 
@@ -495,6 +514,27 @@
                          0, 'The test class only log handlers were not '
                             'cleared.')
 
+    @mock.patch('os.makedirs')
+    def test_on_test_class_end_enables_run_level_handlers(self, *_):
+        info_testclass_log = LogStyles.LOG_INFO + LogStyles.TESTCLASS_LOG
+        with self.patch('FileHandler',
+                        side_effect=[mock.MagicMock(name='run_handler'),
+                                     mock.MagicMock(name='class_handler')]):
+            log_stream.create_logger(self._testMethodName,
+                                     log_styles=info_testclass_log)
+
+        created_log_stream = log_stream._log_streams[self._testMethodName]
+        created_log_stream.on_test_class_begin(TestClassBeginEvent(TestClass()))
+        created_log_stream.on_test_class_end('')
+
+        self.assertGreater(
+            len(created_log_stream._test_run_only_log_handlers), 0,
+            'The test run only log handlers list is empty.')
+        for handler in created_log_stream._test_run_only_log_handlers:
+            self.assertIn(handler, created_log_stream.logger.handlers,
+                          'A run level handler is not enabled on test class '
+                          'end.')
+
     # on_test_class_begin
 
     @mock.patch('os.makedirs')
@@ -514,6 +554,27 @@
         self.assertEqual(len(created_log_stream._test_class_only_log_handlers),
                          1)
 
+    @mock.patch('os.makedirs')
+    def test_on_test_class_begin_disables_run_level_handlers(self, *_):
+        info_testclass_log = LogStyles.LOG_INFO + LogStyles.TESTCLASS_LOG
+        with self.patch('FileHandler',
+                        side_effect=[mock.MagicMock(name='run_handler'),
+                                     mock.MagicMock(name='class_handler')]):
+            log_stream.create_logger(self._testMethodName,
+                                     log_styles=info_testclass_log)
+
+        created_log_stream = log_stream._log_streams[self._testMethodName]
+
+        created_log_stream.on_test_class_begin(TestClassBeginEvent(TestClass()))
+
+        self.assertGreater(
+            len(created_log_stream._test_run_only_log_handlers), 0,
+            'The test run only log handlers list is empty.')
+        for handler in created_log_stream._test_run_only_log_handlers:
+            self.assertNotIn(handler, created_log_stream.logger.handlers,
+                             'A run level handler is not disabled on test ' 
+                             'class begin.')
+
     # cleanup
 
     @mock.patch('os.makedirs')
diff --git a/acts/framework/tests/libs/proc/process_test.py b/acts/framework/tests/libs/proc/process_test.py
index 5ed2dc9..b327c0e 100644
--- a/acts/framework/tests/libs/proc/process_test.py
+++ b/acts/framework/tests/libs/proc/process_test.py
@@ -18,7 +18,7 @@
 import mock
 import subprocess
 from acts.libs.proc.process import Process
-
+from acts.libs.proc.process import ProcessError
 
 class FakeThread(object):
     def __init__(self, target=None):
@@ -78,6 +78,25 @@
 
     # start
 
+    def test_start_raises_if_called_back_to_back(self):
+        """Tests that start raises an exception if it has already been called
+        prior.
+
+        This is required to prevent references to processes and threads from
+        being overwritten, potentially causing ACTS to hang."""
+        process = Process('cmd')
+
+        # Here we need the thread to start the process object.
+        class FakeThreadImpl(FakeThread):
+            def _on_start(self):
+                process._process = mock.Mock()
+
+        with self.patch('Thread', FakeThreadImpl):
+            process.start()
+            expected_msg = 'Process has already started.'
+            with self.assertRaisesRegex(ProcessError, expected_msg):
+                process.start()
+
     def test_start_starts_listening_thread(self):
         """Tests that start starts the _exec_popen_loop function."""
         process = Process('cmd')
@@ -95,6 +114,17 @@
 
     # wait
 
+    def test_wait_raises_if_called_back_to_back(self):
+        """Tests that wait raises an exception if it has already been called
+        prior."""
+        process = Process('cmd')
+        process._process = mock.Mock()
+
+        process.wait(0)
+        expected_msg = 'Process is already being stopped.'
+        with self.assertRaisesRegex(ProcessError, expected_msg):
+            process.wait(0)
+
     def test_wait_kills_after_timeout(self):
         """Tests that if a TimeoutExpired error is thrown during wait, the
         process is killed."""
@@ -207,6 +237,7 @@
         process._process = mock.Mock()
         process._process.poll.return_value = None
         process._process.kill = test_call_order
+        process._process.wait.side_effect = subprocess.TimeoutExpired('', '')
 
         process.stop()
 
diff --git a/acts/tests/google/ble/gatt/GattConnectTest.py b/acts/tests/google/ble/gatt/GattConnectTest.py
index 329405d..c276c0e 100644
--- a/acts/tests/google/ble/gatt/GattConnectTest.py
+++ b/acts/tests/google/ble/gatt/GattConnectTest.py
@@ -279,6 +279,7 @@
         autoconnect = False
         mac_address, adv_callback, scan_callback = (
             get_mac_address_of_generic_advertisement(self.cen_ad, self.per_ad))
+        self.adv_instances.append(adv_callback)
         try:
             bluetooth_gatt, gatt_callback = setup_gatt_connection(
                 self.cen_ad, mac_address, autoconnect)
@@ -357,6 +358,7 @@
                 transport=gatt_transport['auto'],
                 opportunistic=False)
             self.cen_ad.droid.bleStopBleScan(scan_callback)
+            self.adv_instances.append(adv_callback)
             self.bluetooth_gatt_list.append(bluetooth_gatt_1)
         except GattTestUtilsError as err:
             self.log.error(err)
@@ -931,11 +933,22 @@
                                     and device['name'] == target_name):
                                 bonded = True
                                 break
+
+        # Dual mode devices will establish connection over the classic transport,
+        # in order to establish bond over both transports, and do SDP. Starting
+        # disconnection before all this is finished is not safe, might lead to
+        # race conditions, i.e. bond over classic tranport shows up after LE
+        # bond is already removed.
+        time.sleep(4)
+
         for ad in [self.cen_ad, self.per_ad]:
             if not clear_bonded_devices(ad):
                 return False
-            # Necessary sleep time for entries to update unbonded state
-            time.sleep(2)
+
+        # Necessary sleep time for entries to update unbonded state
+        time.sleep(2)
+
+        for ad in [self.cen_ad, self.per_ad]:
             bonded_devices = ad.droid.bluetoothGetBondedDevices()
             if len(bonded_devices) > 0:
                 self.log.error(
@@ -1096,16 +1109,24 @@
         self.per_ad.droid.bleStartBleAdvertising(
             advertise_callback, advertise_data, advertise_settings)
 
-        try:
-            mac_address_post_restart = self.cen_ad.ed.pop_event(
-                expected_event_name, self.default_timeout)['data']['Result'][
-                    'deviceInfo']['address']
-            self.log.info(
-                "Peripheral advertisement found with mac address: {}".format(
-                    mac_address_post_restart))
-        except Empty:
-            self.log.info("Peripheral advertisement not found")
-            test_result = False
+        mac_address_post_restart = mac_address_pre_restart
+
+        while True:
+            try:
+                mac_address_post_restart = self.cen_ad.ed.pop_event(
+                    expected_event_name, self.default_timeout)['data']['Result'][
+                        'deviceInfo']['address']
+                self.log.info(
+                    "Peripheral advertisement found with mac address: {}".format(
+                        mac_address_post_restart))
+            except Empty:
+                self.log.info("Peripheral advertisement not found")
+                test_result = False
+
+            if  mac_address_pre_restart != mac_address_post_restart:
+                break
+
+        self.cen_ad.droid.bleStopBleScan(scan_callback)
 
         # Steps 4: Try to connect to the first mac address
         gatt_callback = self.cen_ad.droid.gattCreateGattCallback()
@@ -1128,8 +1149,12 @@
 
         # Step 5: Cancel connection request.
         self.cen_ad.droid.gattClientDisconnect(bluetooth_gatt)
+        close_gatt_client(self.cen_ad, bluetooth_gatt)
+        if bluetooth_gatt in self.bluetooth_gatt_list:
+            self.bluetooth_gatt_list.remove(bluetooth_gatt)
 
         # Step 6: Connect to second mac address.
+        gatt_callback = self.cen_ad.droid.gattCreateGattCallback()
         self.log.info(
             "Gatt Connect to mac address {}.".format(mac_address_post_restart))
         bluetooth_gatt = self.cen_ad.droid.gattClientConnectGatt(
@@ -1152,4 +1177,8 @@
         except Empty:
             self.log.error("No connection update was found.")
             return False
-        return self.cen_ad.droid.gattClientDisconnect(bluetooth_gatt)
+
+        self.per_ad.droid.bleStopBleAdvertising(advertise_callback)
+
+        return self._orchestrate_gatt_disconnection(bluetooth_gatt,
+                                                    gatt_callback)
diff --git a/acts/tests/google/bt/headphone_automation/HeadphoneTest.py b/acts/tests/google/bt/headphone_automation/HeadphoneTest.py
index 943f548..74eda7a 100644
--- a/acts/tests/google/bt/headphone_automation/HeadphoneTest.py
+++ b/acts/tests/google/bt/headphone_automation/HeadphoneTest.py
@@ -99,6 +99,9 @@
                 headphone.power_off()
                 headphone.clean_up()
 
+        power_supply = self.relay_devices[-1]
+        power_supply.power_off()
+
         return clear_bonded_devices(self.ad)
 
     @property
@@ -107,7 +110,7 @@
 
     @property
     def headphone_list(self):
-        return self.relay_devices
+        return self.relay_devices[:-1]
 
     @BluetoothBaseTest.bt_test_wrap
     @test_tracker_info(uuid='157c1fa1-3d6f-4cfc-8f86-ad267746af71')
diff --git a/acts/tests/google/bt/performance/BtRangeCodecTest.py b/acts/tests/google/bt/performance/BtRangeCodecTest.py
new file mode 100755
index 0000000..252c587
--- /dev/null
+++ b/acts/tests/google/bt/performance/BtRangeCodecTest.py
@@ -0,0 +1,38 @@
+from acts.test_utils.bt.A2dpCodecBaseTest import A2dpCodecBaseTest
+
+
+# For more intuitive parameter parsing.
+#  If we are able to 'step' to end value, include it in the range.
+def inclusive_range(start, stop, step):
+    for i in range(start, stop, step):
+        yield i
+    if stop % step == 0:
+        yield stop
+
+
+class BtRangeCodecTest(A2dpCodecBaseTest):
+
+    def __init__(self, configs):
+        super().__init__(configs)
+        self.attenuator = self.attenuators[0]
+        req_params = ["bt_atten_start", "bt_atten_stop", "bt_atten_step",
+                      "codecs"]
+        opt_params = ["RelayDevice", "required_devices", "audio_params"]
+        self.unpack_userparams(req_params, opt_params)
+        attenuation_range = inclusive_range(self.bt_atten_start,
+                                            self.bt_atten_stop,
+                                            self.bt_atten_step)
+        for attenuation in attenuation_range:
+            for codec_config in self.codecs:
+                self.generate_test_case(codec_config, attenuation)
+
+    def generate_test_case(self, codec_config, attenuation):
+        def test_case_fn():
+            self.attenuator.set_atten(attenuation)
+            self.log.info("Setting bt attenuation to %s" % attenuation)
+            self.stream_music_on_codec(**codec_config)
+        test_case_name = "test_streaming_{}".format(
+            "_".join([str(codec_config[key]) for key in sorted(
+                      codec_config.keys(), reverse=True)] + [str(attenuation)])
+        )
+        setattr(self, test_case_name, test_case_fn)
diff --git a/acts/tests/google/power/tel/lab/PowerTelTestplanDataTest.py b/acts/tests/google/power/tel/lab/PowerTelTestplanDataTest.py
new file mode 100644
index 0000000..43888c5
--- /dev/null
+++ b/acts/tests/google/power/tel/lab/PowerTelTestplanDataTest.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from PowerTelTrafficTest import PowerTelTrafficTest
+
+class PowerTelTraffic_LTE_Test(PowerTelTrafficTest):
+    def test_lte_traffic_band_2_pdl_excellent_pul_low_bw_14_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_1(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_2_pdl_excellent_pul_low_bw_3_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_2(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_2_pdl_excellent_pul_low_bw_5_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_3(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_2_pdl_excellent_pul_low_bw_10_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_4(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_2_pdl_excellent_pul_low_bw_15_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_5(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_2_pdl_excellent_pul_low_bw_20_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_6(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_13_pdl_excellent_pul_max_bw_5_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_7(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_13_pdl_excellent_pul_high_bw_5_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_8(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_13_pdl_excellent_pul_medium_bw_5_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_9(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_13_pdl_excellent_pul_low_bw_5_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_10(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_13_pdl_excellent_pul_low_bw_5_tm_1_mimo_1x1_scheduling_static_direction_dl_pattern_100_0_11(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_13_pdl_excellent_pul_low_bw_5_tm_1_mimo_1x1_scheduling_static_direction_dlul_pattern_50_50_12(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_13_pdl_excellent_pul_low_bw_5_tm_1_mimo_1x1_scheduling_static_direction_dlul_pattern_75_25_13(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_13_pdl_excellent_pul_low_bw_5_tm_1_mimo_1x1_scheduling_static_direction_dlul_pattern_90_10_14(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_4_pdl_excellent_pul_low_bw_5_tm_1_mimo_1x1_scheduling_static_direction_dl_pattern_100_0_15(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_4_pdl_excellent_pul_low_bw_5_tm_4_mimo_2x2_scheduling_static_direction_dl_pattern_100_0_16(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_4_pdl_excellent_pul_max_bw_5_tm_3_mimo_4x4_scheduling_static_direction_dl_pattern_100_0_17(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_4_pdl_excellent_pul_low_bw_5_tm_3_mimo_4x4_scheduling_static_direction_dl_pattern_100_0_18(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_7_pdl_excellent_pul_max_bw_20_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_19(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_7_pdl_excellent_pul_high_bw_20_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_20(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_7_pdl_excellent_pul_medium_bw_20_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_21(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_7_pdl_excellent_pul_low_bw_20_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_22(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_2_pdl_excellent_pul_max_bw_5_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_29(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_4_pdl_excellent_pul_max_bw_5_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_30(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_5_pdl_excellent_pul_max_bw_5_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_31(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_7_pdl_excellent_pul_max_bw_5_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_32(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_12_pdl_excellent_pul_max_bw_5_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_33(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_13_pdl_excellent_pul_max_bw_10_tm_1_mimo_1x1_scheduling_static_direction_dl_pattern_100_0_34(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_4_pdl_excellent_pul_max_bw_10_tm_1_mimo_1x1_scheduling_static_direction_dl_pattern_100_0_35(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_7_pdl_excellent_pul_max_bw_10_tm_1_mimo_1x1_scheduling_static_direction_dl_pattern_100_0_36(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_3_pdl_excellent_pul_max_bw_10_tm_1_mimo_1x1_scheduling_static_direction_dl_pattern_100_0_37(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_1_pdl_excellent_pul_medium_bw_20_tm_3_mimo_4x4_scheduling_static_direction_dl_pattern_100_0_38(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_2_pdl_excellent_pul_medium_bw_20_tm_3_mimo_4x4_scheduling_static_direction_dl_pattern_100_0_39(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_3_pdl_excellent_pul_medium_bw_20_tm_3_mimo_4x4_scheduling_static_direction_dl_pattern_100_0_40(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_4_pdl_excellent_pul_medium_bw_20_tm_3_mimo_4x4_scheduling_static_direction_dl_pattern_100_0_41(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_7_pdl_excellent_pul_medium_bw_20_tm_3_mimo_4x4_scheduling_static_direction_dl_pattern_100_0_42(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_1_pdl_excellent_pul_low_bw_10_tm_4_mimo_2x2_scheduling_static_direction_dlul_pattern_75_25_43(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_2_pdl_excellent_pul_low_bw_10_tm_4_mimo_2x2_scheduling_static_direction_dlul_pattern_75_25_44(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_3_pdl_excellent_pul_low_bw_10_tm_4_mimo_2x2_scheduling_static_direction_dlul_pattern_75_25_45(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_4_pdl_excellent_pul_low_bw_10_tm_4_mimo_2x2_scheduling_static_direction_dlul_pattern_75_25_46(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_5_pdl_excellent_pul_low_bw_10_tm_4_mimo_2x2_scheduling_static_direction_dlul_pattern_75_25_47(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_7_pdl_excellent_pul_low_bw_10_tm_4_mimo_2x2_scheduling_static_direction_dlul_pattern_75_25_48(self):
+        self.power_tel_traffic_test()
+
+    def test_lte_traffic_band_12_pdl_excellent_pul_low_bw_10_tm_4_mimo_2x2_scheduling_static_direction_dlul_pattern_75_25_49(self):
+        self.power_tel_traffic_test()
+
+class PowerTelTraffic_UMTS_Test(PowerTelTrafficTest):
+    def test_umts_traffic_r_8_band_1_pul_edge_direction_ul_pattern_0_100_1(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_8_band_1_pul_weak_direction_ul_pattern_0_100_2(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_8_band_1_pul_medium_direction_ul_pattern_0_100_3(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_8_band_1_pul_excellent_direction_ul_pattern_0_100_4(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_7_band_1_pul_excellent_direction_ul_pattern_0_100_5(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_99_band_1_pul_excellent_direction_ul_pattern_0_100_6(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_8_band_4_pul_excellent_direction_ul_pattern_0_100_7(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_8_band_5_pul_excellent_direction_ul_pattern_0_100_8(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_8_band_5_pul_excellent_direction_dl_pattern_100_0_9(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_8_band_5_pul_excellent_direction_dlul_pattern_90_10_10(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_8_band_5_pul_excellent_direction_dlul_pattern_75_25_11(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_8_band_5_pul_excellent_direction_dlul_pattern_50_50_12(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_7_band_4_pul_edge_direction_dl_pattern_100_0_13(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_99_band_4_pul_edge_direction_dl_pattern_100_0_14(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_7_band_4_pul_edge_direction_ul_pattern_0_100_15(self):
+        self.power_tel_traffic_test()
+
+    def test_umts_traffic_r_99_band_4_pul_edge_direction_ul_pattern_0_100_16(self):
+        self.power_tel_traffic_test()
+
+
+class PowerTelTraffic_GSM_Test(PowerTelTrafficTest):
+    def test_gsm_traffic_band_1900_gprs_pul_edge_direction_ul_pattern_0_100_slots_1_4_1(self):
+        self.power_tel_traffic_test()
+
+    def test_gsm_traffic_band_1900_gprs_pul_weak_direction_ul_pattern_0_100_slots_1_4_2(self):
+        self.power_tel_traffic_test()
+
+    def test_gsm_traffic_band_1900_gprs_pul_medium_direction_ul_pattern_0_100_slots_1_4_3(self):
+        self.power_tel_traffic_test()
+
+    def test_gsm_traffic_band_1900_gprs_pul_excellent_direction_ul_pattern_0_100_slots_1_4_4(self):
+        self.power_tel_traffic_test()
+
+    def test_gsm_traffic_band_1900_edge_pul_excellent_direction_ul_pattern_0_100_slots_1_4_5(self):
+        self.power_tel_traffic_test()
+
+    def test_gsm_traffic_band_850_edge_pul_excellent_direction_ul_pattern_0_100_slots_1_4_6(self):
+        self.power_tel_traffic_test()
+
+    def test_gsm_traffic_band_900_edge_pul_excellent_direction_ul_pattern_0_100_slots_1_4_7(self):
+        self.power_tel_traffic_test()
+
+    def test_gsm_traffic_band_1800_edge_pul_excellent_direction_ul_pattern_0_100_slots_1_4_8(self):
+        self.power_tel_traffic_test()
\ No newline at end of file
diff --git a/acts/tests/google/power/tel/lab/PowerTelTestplanHotspotTest.py b/acts/tests/google/power/tel/lab/PowerTelTestplanHotspotTest.py
new file mode 100644
index 0000000..d15abb5
--- /dev/null
+++ b/acts/tests/google/power/tel/lab/PowerTelTestplanHotspotTest.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3.4
+#
+#   Copyright 2018 - The Android Open Source Project
+#
+#   Licensed under the Apache License, Version 2.0 (the 'License');
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an 'AS IS' BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+from PowerTelHotspotTest import PowerTelHotspotTest
+
+
+class PowerTelHotspot_LTE_Test(PowerTelHotspotTest):
+
+    def test_lte_hotspot_band_2_pdl_excellent_pul_low_bw_14_tm_1_mimo_1x1_scheduling_static_direction_dl_pattern_100_0_wifiband_5g_1(self):
+        self.power_tel_tethering_test()
+
+    def test_lte_hotspot_band_13_pdl_excellent_pul_high_bw_10_tm_4_mimo_2x2_scheduling_static_direction_ul_pattern_0_100_wifiband_5g_4(self):
+        self.power_tel_tethering_test()
+
+    def test_lte_hotspot_band_13_pdl_excellent_pul_high_bw_10_tm_4_mimo_2x2_scheduling_static_direction_dl_pattern_100_0_wifiband_5g_5(self):
+        self.power_tel_tethering_test()
+
+    def test_lte_hotspot_band_7_pdl_excellent_pul_max_bw_20_tm_4_mimo_2x2_scheduling_static_direction_dl_pattern_100_0_wifiband_2g_6(self):
+        self.power_tel_tethering_test()
+
+    def test_lte_hotspot_band_3_pdl_excellent_pul_low_bw_10_tm_1_mimo_2x2_scheduling_static_direction_dl_pattern_100_0_wifiband_5g_9(self):
+        self.power_tel_tethering_test()
+
+    def test_lte_hotspot_band_3_pdl_excellent_pul_low_bw_10_tm_1_mimo_2x2_scheduling_static_direction_dlul_pattern_50_50_wifiband_2g_10(self):
+        self.power_tel_tethering_test()
+
+    def test_lte_hotspot_band_2_pdl_excellent_pul_max_bw_20_tm_1_mimo_1x1_scheduling_static_direction_dl_pattern_100_0_wifiband_2g_11(self):
+        self.power_tel_tethering_test()
+
+    def test_lte_hotspot_band_12_pdl_excellent_pul_medium_bw_5_tm_1_mimo_1x1_scheduling_static_direction_dlul_pattern_50_50_wifiband_5g_13(self):
+        self.power_tel_tethering_test()
+
+    def test_lte_hotspot_band_12_pdl_excellent_pul_medium_bw_5_tm_4_mimo_2x2_scheduling_static_direction_ul_pattern_0_100_wifiband_5g_14(self):
+        self.power_tel_tethering_test()
+
+    def test_lte_hotspot_band_5_pdl_excellent_pul_low_bw_3_tm_1_mimo_1x1_scheduling_static_direction_ul_pattern_0_100_wifiband_5g_15(self):
+        self.power_tel_tethering_test()
diff --git a/acts/tests/google/power/tel/lab/PowerTelTrafficTest.py b/acts/tests/google/power/tel/lab/PowerTelTrafficTest.py
index c84d871..b34089c 100644
--- a/acts/tests/google/power/tel/lab/PowerTelTrafficTest.py
+++ b/acts/tests/google/power/tel/lab/PowerTelTrafficTest.py
@@ -18,7 +18,7 @@
 
 import scapy.all as scapy
 
-import asserts
+from acts import asserts
 from acts.test_utils.power import IperfHelper as IPH
 from acts.test_utils.power import PowerCellularLabBaseTest as PWCEL
 from acts.test_utils.wifi import wifi_power_test_utils as wputils
@@ -374,9 +374,9 @@
         for pw in sweep_range:
 
             if self.sweep == self.PARAM_SWEEP_DOWNLINK:
-                self.simulation.set_downlink_rx_power(pw)
+                self.simulation.set_downlink_rx_power(self.simulation.bts1, pw)
             elif self.sweep == self.PARAM_SWEEP_UPLINK:
-                self.simulation.set_uplink_tx_power(pw)
+                self.simulation.set_uplink_tx_power(self.simulation.bts1, pw)
 
             i, t = self.power_tel_traffic_test()
             self.log.info("---------------------")
diff --git a/acts/tests/google/power/wifi/PowerWiFimulticastTest.py b/acts/tests/google/power/wifi/PowerWiFimulticastTest.py
index f43f6a5..cdc75b0 100644
--- a/acts/tests/google/power/wifi/PowerWiFimulticastTest.py
+++ b/acts/tests/google/power/wifi/PowerWiFimulticastTest.py
@@ -82,6 +82,49 @@
         packet = pkt_gen.generate(self.ipv4_dst_fake)
         self.sendPacketAndMeasure(packet)
 
+    @test_tracker_info(uuid='dd3ff80d-97ce-4408-92f8-f2c72ce8d79c')
+    def test_screen_OFF_band_5g_unicast_invalid_ip_arp_request(self):
+        self.set_connection()
+        self.pkt_gen_config = wputils.create_pkt_config(self)
+        pkt_gen = pkt_utils.ArpGenerator(**self.pkt_gen_config)
+        packet = pkt_gen.generate(
+            ip_dst='0.0.0.0', eth_dst=self.pkt_gen_config['dst_mac'])
+        self.sendPacketAndMeasure(packet)
+
+    @test_tracker_info(uuid='5dcb16f1-725c-45de-8103-340104d60a22')
+    def test_screen_OFF_band_5g_unicast_misdirected_arp_request(self):
+        self.set_connection()
+        self.pkt_gen_config = wputils.create_pkt_config(self)
+        pkt_gen = pkt_utils.ArpGenerator(**self.pkt_gen_config)
+        packet = pkt_gen.generate(
+            ip_dst=self.ipv4_dst_fake, eth_dst=self.pkt_gen_config['dst_mac'])
+        self.sendPacketAndMeasure(packet)
+
+    @test_tracker_info(uuid='5ec4800f-a82e-4462-8b65-4fcd0b1940a2')
+    def test_screen_OFF_band_5g_unicast_invalid_src_arp_reply(self):
+        self.set_connection()
+        self.pkt_gen_config = wputils.create_pkt_config(self)
+        pkt_gen = pkt_utils.ArpGenerator(**self.pkt_gen_config)
+        packet = pkt_gen.generate(
+            op=scapy.ARP.is_at,
+            ip_src='0.0.0.0',
+            ip_dst=self.ipv4_dst_fake,
+            hwdst=self.mac_dst_fake,
+            eth_dst=self.pkt_gen_config['dst_mac'])
+        self.sendPacketAndMeasure(packet)
+
+    @test_tracker_info(uuid='6c5c0e9e-7a00-43d0-a6e8-355141467703')
+    def test_screen_OFF_band_5g_unicast_misdirected_arp_reply(self):
+        self.set_connection()
+        self.pkt_gen_config = wputils.create_pkt_config(self)
+        pkt_gen = pkt_utils.ArpGenerator(**self.pkt_gen_config)
+        packet = pkt_gen.generate(
+            op=scapy.ARP.is_at,
+            ip_dst=self.ipv4_dst_fake,
+            hwdst=self.mac_dst_fake,
+            eth_dst=self.pkt_gen_config['dst_mac'])
+        self.sendPacketAndMeasure(packet)
+
     @test_tracker_info(uuid='8e534d3b-5a25-429a-a1bb-8119d7d28b5a')
     def test_screen_OFF_band_5g_directed_ns(self):
         self.set_connection()
@@ -196,6 +239,30 @@
         packet = pkt_gen.generate()
         self.sendPacketAndMeasure(packet)
 
+    @test_tracker_info(uuid='16dabe69-27a6-470b-a474-4774cd3e4715')
+    def test_screen_OFF_band_5g_dot3(self):
+        self.set_connection()
+        self.pkt_gen_config = wputils.create_pkt_config(self)
+        pkt_gen = pkt_utils.Dot3Generator(**self.pkt_gen_config)
+        packet = pkt_gen.generate()
+        self.sendPacketAndMeasure(packet)
+
+    @test_tracker_info(uuid='55d00670-f34b-4289-95fa-b618e509c15c')
+    def test_screen_OFF_band_5g_dot3_llc(self):
+        self.set_connection()
+        self.pkt_gen_config = wputils.create_pkt_config(self)
+        pkt_gen = pkt_utils.Dot3Generator(**self.pkt_gen_config)
+        packet = pkt_gen.generate_llc()
+        self.sendPacketAndMeasure(packet)
+
+    @test_tracker_info(uuid='75ba3435-fe63-4a21-8cbe-2f631043f632')
+    def test_screen_OFF_band_5g_dot3_snap(self):
+        self.set_connection()
+        self.pkt_gen_config = wputils.create_pkt_config(self)
+        pkt_gen = pkt_utils.Dot3Generator(**self.pkt_gen_config)
+        packet = pkt_gen.generate_snap()
+        self.sendPacketAndMeasure(packet)
+
     # Test cases: screen ON
     @test_tracker_info(uuid='b9550149-bf36-4f86-9b4b-6e900756a90e')
     def test_screen_ON_band_5g_directed_arp(self):
diff --git a/acts/tests/google/power/wifi/PowerWiFiscanTest.py b/acts/tests/google/power/wifi/PowerWiFiscanTest.py
index 513e974..46c605e 100644
--- a/acts/tests/google/power/wifi/PowerWiFiscanTest.py
+++ b/acts/tests/google/power/wifi/PowerWiFiscanTest.py
@@ -32,20 +32,20 @@
             ' -e class com.google.android.platform.powertests.'
             'WifiScanTest#testWifiSingleShotScan'
             ' com.google.android.platform.powertests/'
-            'android.test.InstrumentationTestRunner > /dev/null &' %
+            'androidx.test.runner.AndroidJUnitRunner > /dev/null &' %
             (self.mon_duration + self.mon_offset + 10))
         BACKGROUND_SCAN = ('am instrument -w -r -e min_scan_count \"1\" -e '
                            'WifiScanTest-testWifiBackgroundScan %d -e class '
                            'com.google.android.platform.powertests.WifiScan'
                            'Test#testWifiBackgroundScan com.google.android.'
-                           'platform.powertests/android.test.Instrumentation'
-                           'TestRunner > /dev/null &' %
+                           'platform.powertests/androidx.test.runner.'
+                           'AndroidJUnitRunner > /dev/null &' %
                            (self.mon_duration + self.mon_offset + 10))
         WIFI_SCAN = ('am instrument -w -r -e min_scan_count \"1\" -e '
                      'WifiScanTest-testWifiScan %d -e class '
                      'com.google.android.platform.powertests.WifiScanTest#'
                      'testWifiScan com.google.android.platform.powertests/'
-                     'android.test.InstrumentationTestRunner > /dev/null &' %
+                     'androidx.test.runner.AndroidJUnitRunner > /dev/null &' %
                      (self.mon_duration + self.mon_offset + 10))
         self.APK_SCAN_CMDS = {
             'singleshot': SINGLE_SHOT_SCAN,
diff --git a/acts/tests/google/tel/live/TelLiveSmsTest.py b/acts/tests/google/tel/live/TelLiveSmsTest.py
index 3fac9af..f448c34 100644
--- a/acts/tests/google/tel/live/TelLiveSmsTest.py
+++ b/acts/tests/google/tel/live/TelLiveSmsTest.py
@@ -192,6 +192,8 @@
             self.log, ads[0], ads[1], [("Test Message", "Basic Message Body",
                                         None)]
         ]
+        if get_operator_name(self.log, ads[0]) in ["spt", "Sprint"]:
+            args.append(30)
         if not mms_send_receive_verify(*args):
             self.log.info("MMS send in call is suspended.")
             if not mms_receive_verify_after_call_hangup(*args):
diff --git a/acts/tests/google/wifi/WifiChaosTest.py b/acts/tests/google/wifi/WifiChaosTest.py
index 8357d74..8a66dd4 100755
--- a/acts/tests/google/wifi/WifiChaosTest.py
+++ b/acts/tests/google/wifi/WifiChaosTest.py
@@ -122,10 +122,10 @@
         self.dut.droid.wakeUpNow()
 
     def on_pass(self, test_name, begin_time):
-        wutils.stop_pcap(self.pcap, self.pcap_pid, True)
+        wutils.stop_pcap(self.pcap, self.pcap_procs, True)
 
     def on_fail(self, test_name, begin_time):
-        wutils.stop_pcap(self.pcap, self.pcap_pid, False)
+        wutils.stop_pcap(self.pcap, self.pcap_procs, False)
         self.dut.take_bug_report(test_name, begin_time)
         self.dut.cat_adb_log(test_name, begin_time)
 
@@ -193,8 +193,7 @@
            Raises: TestFailure if the network connection fails.
 
         """
-        for attempt in range(1):
-            # TODO:(bmahadev) Change it to 5 or more attempts later.
+        for attempt in range(5):
             try:
                 begin_time = time.time()
                 ssid = network[WifiEnums.SSID_KEY]
@@ -279,7 +278,7 @@
 
         self.get_band_and_chan(ssid)
         self.pcap.configure_monitor_mode(self.band, self.chan)
-        self.pcap_pid = wutils.start_pcap(
+        self.pcap_procs = wutils.start_pcap(
                 self.pcap, self.band.lower(), self.log_path, self.test_name)
         self.run_connect_disconnect(network, hostname, rpm_port, rpm_ip,
                                     release_ap)
diff --git a/acts/tests/google/wifi/WifiCrashStressTest.py b/acts/tests/google/wifi/WifiCrashStressTest.py
index c628b6b..0c50629 100755
--- a/acts/tests/google/wifi/WifiCrashStressTest.py
+++ b/acts/tests/google/wifi/WifiCrashStressTest.py
@@ -85,7 +85,7 @@
             del self.user_params["reference_networks"]
 
     """Helper Functions"""
-    def trigger_wifi_firmware_crash(self, ad, timeout=30):
+    def trigger_wifi_firmware_crash(self, ad, timeout=15):
         pre_timestamp = ad.adb.getprop("vendor.debug.ssrdump.timestamp")
         ad.adb.shell(
             "setprop persist.vendor.sys.modem.diag.mdlog false", ignore_status=True)
@@ -153,14 +153,13 @@
         asserts.assert_true(
             utils.adb_shell_ping(self.dut_client, count=10, dest_ip=dut_addr, timeout=20),
             "%s ping %s failed" % (self.dut_client.serial, dut_addr))
-        wutils.reset_wifi(self.dut_client)
         for count in range(self.stress_count):
             self.log.info("%s: %d/%d" %
                 (self.current_test_name, count + 1, self.stress_count))
+            wutils.reset_wifi(self.dut_client)
             # Trigger firmware crash
             self.trigger_wifi_firmware_crash(self.dut)
             # Connect DUT to Network
-            wutils.wifi_toggle_state(self.dut_client, True)
             wutils.connect_to_wifi_network(self.dut_client, config, check_connectivity=False)
             # Ping the DUT
             server_addr = self.dut.droid.connectivityGetIPv4Addresses("wlan0")[0]
@@ -199,11 +198,11 @@
         # Client connects to Softap
         wutils.wifi_toggle_state(self.dut_client, True)
         wutils.connect_to_wifi_network(self.dut_client, config)
-        wutils.reset_wifi(self.dut_client)
-        wutils.reset_wifi(self.dut)
         for count in range(self.stress_count):
             self.log.info("%s: %d/%d" %
                 (self.current_test_name, count + 1, self.stress_count))
+            wutils.reset_wifi(self.dut_client)
+            wutils.reset_wifi(self.dut)
             # Trigger firmware crash
             self.trigger_wifi_firmware_crash(self.dut)
             wutils.connect_to_wifi_network(self.dut, self.network)
diff --git a/acts/tests/google/wifi/WifiNetworkSelectorTest.py b/acts/tests/google/wifi/WifiNetworkSelectorTest.py
index ffeb6b5..7015636 100644
--- a/acts/tests/google/wifi/WifiNetworkSelectorTest.py
+++ b/acts/tests/google/wifi/WifiNetworkSelectorTest.py
@@ -74,7 +74,7 @@
         self.dut.ed.clear_all_events()
 
         if hasattr(self, 'packet_capture'):
-            self.pcap_pids = wutils.start_pcap(
+            self.pcap_procs = wutils.start_pcap(
                 self.packet_capture, 'dual', self.log_path, self.test_name)
 
     def teardown_test(self):
@@ -84,11 +84,11 @@
 
     def on_pass(self, test_name, begin_time):
         if hasattr(self, 'packet_capture'):
-            wutils.stop_pcap(self.packet_capture, self.pcap_pids, 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_pids, 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)