Add LE pairing stress test and global timer logging

Bug: b/31460129
Change-Id: I6a2b011b6f779fa6276200e6c494e9a0f8689417
(cherry picked from commit 24b398ca658acd34230355141cfc020f0c24fc56)
diff --git a/acts/framework/acts/test_utils/bt/BluetoothBaseTest.py b/acts/framework/acts/test_utils/bt/BluetoothBaseTest.py
index 49f9620..abe7cb4 100644
--- a/acts/framework/acts/test_utils/bt/BluetoothBaseTest.py
+++ b/acts/framework/acts/test_utils/bt/BluetoothBaseTest.py
@@ -29,6 +29,10 @@
 
 
 class BluetoothBaseTest(BaseTestClass):
+    DEFAULT_TIMEOUT = 10
+    start_time = 0
+    timer_list = []
+
     def __init__(self, controllers):
         BaseTestClass.__init__(self, controllers)
 
@@ -50,6 +54,7 @@
         return setup_multiple_devices_for_bt_test(self.android_devices)
 
     def setup_test(self):
+        self.timer_list = []
         self.log.debug(log_energy_info(self.android_devices, "Start"))
         for a in self.android_devices:
             a.ed.clear_all_events()
@@ -89,3 +94,26 @@
             except:
                 ad.log.error("Failed to take a bug report for {}, {}"
                              .format(ad.serial, test_name))
+
+    def _get_time_in_milliseconds(self):
+        return int(round(time.time() * 1000))
+
+    def start_timer(self):
+        self.start_time = self._get_time_in_milliseconds()
+
+    def end_timer(self):
+        total_time = self._get_time_in_milliseconds() - self.start_time
+        self.timer_list.append(total_time)
+        self.start_time = 0
+        return total_time
+
+    def log_stats(self):
+        if self.timer_list:
+            self.log.info("Overall list {}".format(self.timer_list))
+            self.log.info("Average of list {}".format(
+                sum(self.timer_list) / float(len(self.timer_list))))
+            self.log.info("Maximum of list {}".format(max(self.timer_list)))
+            self.log.info("Minimum of list {}".format(min(self.timer_list)))
+            self.log.info("Total items in list {}".format(len(
+                self.timer_list)))
+        self.timer_list = []
diff --git a/acts/framework/acts/test_utils/bt/bt_test_utils.py b/acts/framework/acts/test_utils/bt/bt_test_utils.py
index c9740a1..6606fb7 100644
--- a/acts/framework/acts/test_utils/bt/bt_test_utils.py
+++ b/acts/framework/acts/test_utils/bt/bt_test_utils.py
@@ -635,7 +635,6 @@
             if d['address'] == target_address:
                 log.info("Successfully bonded to device")
                 return True
-        time.sleep(1)
     # Timed out trying to bond.
     log.info("Failed to bond devices.")
     return False
diff --git a/acts/tests/google/ble/gatt/GattToolTest.py b/acts/tests/google/ble/gatt/GattToolTest.py
index edcb818..cdb73d8 100644
--- a/acts/tests/google/ble/gatt/GattToolTest.py
+++ b/acts/tests/google/ble/gatt/GattToolTest.py
@@ -68,7 +68,7 @@
 
     def teardown_test(self):
         super().teardown_test()
-        self._log_stats()
+        self.log_stats()
         self.timer_list = []
         return True
 
@@ -81,22 +81,8 @@
             if self.ble_mac_address in {d['address'] for d in bonded_devices}:
                 self.log.info("Successfully bonded to device")
                 return True
-            time.sleep(1)
         return False
 
-    def _get_time_in_milliseconds(self):
-        return int(round(time.time() * 1000))
-
-    def _log_stats(self):
-        if self.timer_list:
-            self.log.info("Overall list {}".format(self.timer_list))
-            self.log.info("Average of list {}".format(
-                sum(self.timer_list) / float(len(self.timer_list))))
-            self.log.info("Maximum of list {}".format(max(self.timer_list)))
-            self.log.info("Minimum of list {}".format(min(self.timer_list)))
-            self.log.info("Total items in list {}".format(len(
-                self.timer_list)))
-
     def _is_peripheral_advertising(self):
         self.cen_ad.droid.bleSetScanFilterDeviceAddress(self.ble_mac_address)
         self.cen_ad.droid.bleSetScanSettingsScanMode(
@@ -213,7 +199,7 @@
         iterations = 1000
         n = 0
         while n < iterations:
-            start_time = self._get_time_in_milliseconds()
+            self.start_timer()
             try:
                 bluetooth_gatt, gatt_callback = (setup_gatt_connection(
                     self.cen_ad, self.ble_mac_address, self.AUTOCONNECT,
@@ -221,10 +207,7 @@
             except GattTestUtilsError as err:
                 self.log.error(err)
                 return False
-            end_time = self._get_time_in_milliseconds()
-            total_time = end_time - start_time
-            self.timer_list.append(total_time)
-            self.log.info("Total time (ms): {}".format(total_time))
+            self.log.info("Total time (ms): {}".format(self.end_timer()))
             try:
                 disconnect_gatt_connection(self.cen_ad, bluetooth_gatt,
                                            gatt_callback)
@@ -352,13 +335,10 @@
         """
         iterations = 100
         for _ in range(iterations):
-            start_time = self._get_time_in_milliseconds()
+            start_time = self.start_timer()
             if not self._pair_with_peripheral():
                 return False
-            end_time = self._get_time_in_milliseconds()
-            total_time = end_time - start_time
-            self.timer_list.append(total_time)
-            self.log.info("Total time (ms): {}".format(total_time))
+            self.log.info("Total time (ms): {}".format(self.end_timer()))
             if not self._unbond_device():
                 return False
         return True
diff --git a/acts/tests/google/ble/system_tests/BleStressTest.py b/acts/tests/google/ble/system_tests/BleStressTest.py
index ba55534..bfc7caa 100644
--- a/acts/tests/google/ble/system_tests/BleStressTest.py
+++ b/acts/tests/google/ble/system_tests/BleStressTest.py
@@ -23,15 +23,18 @@
 
 from queue import Empty
 from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts.test_utils.bt.bt_test_utils import BtTestUtilsError
+from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
 from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
 from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
 from acts.test_utils.bt.bt_test_utils import get_advanced_droid_list
+from acts.test_utils.bt.bt_test_utils import get_mac_address_of_generic_advertisement
 from acts.test_utils.bt.bt_test_utils import reset_bluetooth
 from acts.test_utils.bt.bt_test_utils import scan_result
 
-
 class BleStressTest(BluetoothBaseTest):
     default_timeout = 10
+    PAIRING_TIMEOUT = 20
 
     def __init__(self, controllers):
         BluetoothBaseTest.__init__(self, controllers)
@@ -39,6 +42,10 @@
         self.scn_ad = self.android_devices[0]
         self.adv_ad = self.android_devices[1]
 
+    def teardown_test(self):
+        super().teardown_test()
+        self.log_stats()
+
     def bleadvertise_verify_onsuccess_handler(self, event):
         test_result = True
         self.log.debug("Verifying onSuccess event")
@@ -263,3 +270,67 @@
         self.scn_ad.droid.bleStartBleScan(filter_list, scan_settings,
                                           scan_callback)
         return test_result
+
+    def _verify_successful_bond(self, target_address):
+        end_time = time.time() + self.PAIRING_TIMEOUT
+        self.log.info("Verifying devices are bonded")
+        while time.time() < end_time:
+            bonded_devices = self.scn_ad.droid.bluetoothGetBondedDevices()
+            if target_address in {d['address'] for d in bonded_devices}:
+                self.log.info("Successfully bonded to device")
+                return True
+        return False
+
+    def _get_time_in_milliseconds(self):
+        return int(round(time.time() * 1000))
+
+    @BluetoothBaseTest.bt_test_wrap
+    def test_le_pairing(self):
+        """Test LE pairing transport stress
+
+        This will test LE pairing between two android devices.
+
+        Steps:
+        1. Start an LE advertisement on secondary device.
+        2. Find address from primary device.
+        3. Discover and bond to LE address.
+        4. Stop LE advertisement on secondary device.
+        5. Repeat steps 1-4 100 times
+
+        Expected Result:
+        LE pairing should pass 100 times.
+
+        Returns:
+          Pass if True
+          Fail if False
+
+        TAGS: LE, Scanning, Stress, Pairing
+        Priority: 1
+        """
+        iterations = 100
+        for i in range(iterations):
+            try:
+                target_address, adv_callback = get_mac_address_of_generic_advertisement(
+                    self.scn_ad, self.adv_ad)
+            except BtTestUtilsError as err:
+                self.log.error(err)
+                return False
+            self.log.info("Begin interation {}/{}".format(i + 1, iterations))
+            self.scn_ad.droid.bluetoothStartPairingHelper()
+            self.adv_ad.droid.bluetoothStartPairingHelper()
+            start_time = self.start_timer()
+            self.scn_ad.droid.bluetoothDiscoverAndBond(target_address)
+            if not self._verify_successful_bond(target_address):
+                self.log.error("Failed to bond devices.")
+                return False
+            self.log.info("Total time (ms): {}".format(self.end_timer()))
+            if not clear_bonded_devices(self.scn_ad):
+                self.log.error("Failed to unbond device from scanner.")
+                return False
+            if not clear_bonded_devices(self.adv_ad):
+                self.log.error("Failed to unbond device from advertiser.")
+                return False
+            self.adv_ad.droid.bleStopBleAdvertising(adv_callback)
+            # Magic sleep to let unbonding finish
+            time.sleep(2)
+        return True
diff --git a/acts/tests/google/bt/system_tests/BtStressTest.py b/acts/tests/google/bt/system_tests/BtStressTest.py
index f2113c8..78ecb47 100644
--- a/acts/tests/google/bt/system_tests/BtStressTest.py
+++ b/acts/tests/google/bt/system_tests/BtStressTest.py
@@ -19,6 +19,7 @@
 
 import time
 from acts.base_test import BaseTestClass
+from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
 from acts.test_utils.bt.bt_test_utils import clear_bonded_devices
 from acts.test_utils.bt.bt_test_utils import log_energy_info
 from acts.test_utils.bt.bt_test_utils import pair_pri_to_sec
@@ -26,28 +27,17 @@
 from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test
 
 
-class BtStressTest(BaseTestClass):
+class BtStressTest(BluetoothBaseTest):
     default_timeout = 20
     iterations = 100
 
     def __init__(self, controllers):
-        BaseTestClass.__init__(self, controllers)
+        BluetoothBaseTest.__init__(self, controllers)
 
-    def setup_class(self):
-        return setup_multiple_devices_for_bt_test(self.android_devices)
-
-    def setup_test(self):
-        return reset_bluetooth(self.android_devices)
-
-    def setup_test(self):
-        setup_result = reset_bluetooth(self.android_devices)
-        self.log.debug(log_energy_info(self.android_devices, "Start"))
-        for a in self.android_devices:
-            a.ed.clear_all_events()
-        return setup_result
 
     def teardown_test(self):
-        self.log.debug(log_energy_info(self.android_devices, "End"))
+        super().teardown_test()
+        self.log_stats()
         return True
 
     def test_toggle_bluetooth(self):
@@ -103,14 +93,16 @@
         """
         for n in range(self.iterations):
             self.log.info("Pair bluetooth iteration {}.".format(n + 1))
+            self.start_timer()
             if (not pair_pri_to_sec(self.android_devices[0].droid,
                                 self.android_devices[1].droid)):
                 self.log.error("Failed to bond devices.")
                 return False
+            self.log.info("Total time (ms): {}".format(self.end_timer()))
             for ad in self.android_devices:
                 if not clear_bonded_devices(ad):
                     return False
-                #Necessary sleep time for entries to update unbonded state
+                # Necessary sleep time for entries to update unbonded state
                 time.sleep(1)
                 bonded_devices = ad.droid.bluetoothGetBondedDevices()
                 if len(bonded_devices) > 0: