1#/usr/bin/env python3 2# 3# Copyright (C) 2018 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16"""Bluetooth 1st time force pair and connect test implementation.""" 17# Quick way to get the Apollo serial number: 18# python3.5 -c "from acts.controllers.buds_lib.apollo_lib import get_devices; [print(d['serial_number']) for d in get_devices()]" 19 20import statistics 21import time 22 23from acts import logger 24from acts.base_test import BaseTestClass 25from acts.controllers.buds_lib.test_actions.bt_utils import BTUtils 26from acts.controllers.buds_lib.test_actions.bt_utils import BTUtilsError 27from acts.controllers.buds_lib.test_actions.apollo_acts import ApolloTestActions 28from acts.signals import TestFailure 29from acts.signals import TestPass 30from acts.test_decorators import test_tracker_info 31from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest 32from acts.test_utils.bt.bt_test_utils import factory_reset_bluetooth 33from acts.test_utils.bt.bt_test_utils import enable_bluetooth 34from acts.test_utils.bt.bt_test_utils import setup_multiple_devices_for_bt_test 35from acts.test_utils.bt.loggers.bluetooth_metric_logger import BluetoothMetricLogger 36from acts.utils import set_location_service 37 38 39# The number of pairing and connection attempts 40PAIR_CONNECT_ATTEMPTS = 200 41 42 43class BluetoothPairConnectError(TestFailure): 44 pass 45 46 47class BluetoothPairAndConnectTest(BaseTestClass): 48 """Pairs and connects a phone and an Apollo buds device. 49 50 Attributes: 51 phone: An Android phone object 52 apollo: An Apollo earbuds object 53 apollo_act: An Apollo test action object 54 dut_bt_addr: The Bluetooth address of the Apollo earbuds 55 bt_utils: BTUtils test action object 56 """ 57 58 def setup_class(self): 59 super().setup_class() 60 # Sanity check of the devices under test 61 # TODO(b/119051823): Investigate using a config validator to replace this. 62 if not self.android_devices: 63 raise ValueError( 64 'Cannot find android phone (need at least one).') 65 self.phone = self.android_devices[0] 66 67 if not self.buds_devices: 68 raise ValueError( 69 'Cannot find apollo device (need at least one).') 70 self.apollo = self.buds_devices[0] 71 self.log.info('Successfully found needed devices.') 72 73 # Staging the test, create result object, etc. 74 self.apollo_act = ApolloTestActions(self.apollo, self.log) 75 self.dut_bt_addr = self.apollo.bluetooth_address 76 self.bt_utils = BTUtils() 77 self.bt_logger = BluetoothMetricLogger.for_test_case() 78 79 def setup_test(self): 80 setup_multiple_devices_for_bt_test(self.android_devices) 81 # Make sure Bluetooth is on 82 enable_bluetooth(self.phone.droid, self.phone.ed) 83 set_location_service(self.phone, True) 84 self.apollo_act.factory_reset() 85 self.log.info('===== START BLUETOOTH PAIR AND CONNECT TEST =====') 86 87 def teardown_test(self): 88 self.log.info('Teardown test, shutting down all services...') 89 self.apollo_act.factory_reset() 90 self.apollo.close() 91 92 def _get_device_pair_and_connect_times(self): 93 """Gets the pair and connect times of the phone and buds device. 94 95 Pairs the phone with the buds device. Gets the pair and connect times. 96 Unpairs the devices. 97 98 Returns: 99 pair_time: The time it takes to pair the devices in ms. 100 connection_time: The time it takes to connect the devices for the 101 first time after pairing. 102 103 Raises: 104 BluetoothPairConnectError 105 """ 106 107 try: 108 pair_time = self.bt_utils.bt_pair(self.phone, self.apollo) 109 except BTUtilsError: 110 raise BluetoothPairConnectError('Failed to pair devices') 111 112 pair_time *= 1000 113 connection_start_time = time.perf_counter() 114 if not self.apollo_act.wait_for_bluetooth_a2dp_hfp(30): 115 raise BluetoothPairConnectError('Failed to connect devices') 116 connection_end_time = time.perf_counter() 117 connection_time = (connection_end_time - 118 connection_start_time) * 1000 119 120 return pair_time, connection_time 121 122 @BluetoothBaseTest.bt_test_wrap 123 @test_tracker_info(uuid='c914fd08-350d-465a-96cf-970d40e71060') 124 def test_bluetooth_connect(self): 125 # Store metrics 126 metrics = {} 127 pair_connect_success = 0 128 pair_connect_failures = [] 129 pair_times = [] 130 connect_times = [] 131 first_connection_failure = None 132 133 for attempt in range(PAIR_CONNECT_ATTEMPTS): 134 self.log.info('Pair and connection attempt {}'.format(attempt + 1)) 135 pair_connect_timestamp = time.strftime(logger.log_line_time_format, 136 time.localtime()) 137 try: 138 pair_time, connect_time = (self. 139 _get_device_pair_and_connect_times()) 140 except BluetoothPairConnectError as err: 141 self.log.error(err) 142 failure_data = {'timestamp': pair_connect_timestamp, 143 'error': str(err), 144 'pair_and_connect_attempt': attempt + 1} 145 pair_connect_failures.append(failure_data) 146 if not first_connection_failure: 147 first_connection_failure = err 148 else: 149 connect_times.append(connect_time) 150 pair_times.append(pair_time) 151 pair_connect_success += 1 152 153 factory_reset_bluetooth([self.phone]) 154 self.log.info('Factory resetting Apollo device...') 155 self.apollo_act.factory_reset() 156 157 # Buffer between pair and connect attempts 158 time.sleep(3) 159 160 metrics['pair_attempt_count'] = PAIR_CONNECT_ATTEMPTS 161 metrics['pair_successful_count'] = pair_connect_success 162 metrics['pair_failed_count'] = (PAIR_CONNECT_ATTEMPTS - 163 pair_connect_success) 164 165 if len(pair_times) > 0: 166 metrics['pair_max_time_millis'] = int(max(pair_times)) 167 metrics['pair_min_time_millis'] = int(min(pair_times)) 168 metrics['pair_avg_time_millis'] = int(statistics.mean(pair_times)) 169 170 if len(connect_times) > 0: 171 metrics['first_connection_max_time_millis'] = int( 172 max(connect_times)) 173 metrics['first_connection_min_time_millis'] = int( 174 min(connect_times)) 175 metrics['first_connection_avg_time_millis'] = int( 176 (statistics.mean(connect_times))) 177 178 if pair_connect_failures: 179 metrics['pair_conn_failure_info'] = pair_connect_failures 180 181 proto = self.bt_logger.get_results(metrics, 182 self.__class__.__name__, 183 self.phone, 184 self.apollo) 185 186 self.log.info('Metrics: {}'.format(metrics)) 187 188 if PAIR_CONNECT_ATTEMPTS != pair_connect_success: 189 raise TestFailure(str(first_connection_failure), extras=proto) 190 else: 191 raise TestPass('Bluetooth pair and connect test passed', 192 extras=proto) 193