1#!/usr/bin/env python3 2# 3# Copyright 2019 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the 'License'); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of 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, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import logging 18import os 19import pandas as pd 20import re 21import time 22import acts.test_utils.bt.bt_test_utils as bt_utils 23 24from acts.libs.proc import job 25from acts.base_test import BaseTestClass 26from acts.test_utils.bt.bt_power_test_utils import MediaControl 27from acts.test_utils.abstract_devices.bluetooth_handsfree_abstract_device import BluetoothHandsfreeAbstractDeviceFactory as bt_factory 28 29PHONE_MUSIC_FILE_DIRECTORY = '/sdcard/Music' 30FORCE_SAR_ADB_COMMAND = ('am broadcast -n' 31 'com.google.android.apps.scone/.coex.TestReceiver -a ' 32 'com.google.android.apps.scone.coex.SIMULATE_STATE ') 33 34DEFAULT_DURATION = 5 35DEFAULT_MAX_ERROR_THRESHOLD = 2 36DEFAULT_AGG_MAX_ERROR_THRESHOLD = 2 37 38 39class BtSarBaseTest(BaseTestClass): 40 """ Base class for all BT SAR Test classes. 41 42 This class implements functions common to BT SAR test Classes. 43 """ 44 BACKUP_BT_SAR_TABLE_NAME = 'backup_bt_sar_table.csv' 45 46 def __init__(self, controllers): 47 BaseTestClass.__init__(self, controllers) 48 self.power_file_paths = [ 49 '/vendor/etc/bluetooth_power_limits.csv', 50 '/data/vendor/radio/bluetooth_power_limits.csv' 51 ] 52 self.sar_file_name = os.path.basename(self.power_file_paths[0]) 53 54 def setup_class(self): 55 """Initializes common test hardware and parameters. 56 57 This function initializes hardware and compiles parameters that are 58 common to all tests in this class and derived classes. 59 """ 60 super().setup_class() 61 62 self.test_params = self.user_params.get('bt_sar_test_params', {}) 63 if not self.test_params: 64 self.log.warning( 65 'bt_sar_test_params was not found in the config file.') 66 67 self.user_params.update(self.test_params) 68 req_params = ['bt_devices', 'calibration_params'] 69 70 self.unpack_userparams( 71 req_params, 72 duration=DEFAULT_DURATION, 73 custom_sar_path=None, 74 music_files=None, 75 sort_order=None, 76 max_error_threshold=DEFAULT_MAX_ERROR_THRESHOLD, 77 agg_error_threshold=DEFAULT_AGG_MAX_ERROR_THRESHOLD, 78 tpc_threshold=[2, 8], 79 ) 80 81 self.attenuator = self.attenuators[0] 82 self.dut = self.android_devices[0] 83 84 # To prevent default file from being overwritten 85 self.dut.adb.shell('cp {} {}'.format(self.power_file_paths[0], 86 self.power_file_paths[1])) 87 88 self.sar_file_path = self.power_file_paths[1] 89 self.atten_min = 0 90 self.atten_max = int(self.attenuator.get_max_atten()) 91 92 # Initializing media controller 93 if self.music_files: 94 music_src = self.music_files[0] 95 music_dest = PHONE_MUSIC_FILE_DIRECTORY 96 success = self.dut.push_system_file(music_src, music_dest) 97 if success: 98 self.music_file = os.path.join(PHONE_MUSIC_FILE_DIRECTORY, 99 os.path.basename(music_src)) 100 # Initialize media_control class 101 self.media = MediaControl(self.dut, self.music_file) 102 103 #Initializing BT device controller 104 if self.bt_devices: 105 attr, idx = self.bt_devices.split(':') 106 self.bt_device_controller = getattr(self, attr)[int(idx)] 107 self.bt_device = bt_factory().generate(self.bt_device_controller) 108 else: 109 self.log.error('No BT devices config is provided!') 110 111 bt_utils.enable_bqr(self.android_devices) 112 113 self.log_path = os.path.join(logging.log_path, 'results') 114 os.makedirs(self.log_path, exist_ok=True) 115 116 # Reading BT SAR table from the phone 117 self.bt_sar_df = self.read_sar_table(self.dut) 118 119 def setup_test(self): 120 super().setup_test() 121 122 # Starting BT on the master 123 self.dut.droid.bluetoothFactoryReset() 124 bt_utils.enable_bluetooth(self.dut.droid, self.dut.ed) 125 126 # Starting BT on the slave 127 self.bt_device.reset() 128 self.bt_device.power_on() 129 130 # Connect master and slave 131 bt_utils.connect_phone_to_headset(self.dut, self.bt_device, 60) 132 133 # Playing music 134 self.media.play() 135 136 # Find and set PL10 level for the DUT 137 self.pl10_atten = self.set_PL10_atten_level(self.dut) 138 139 def teardown_test(self): 140 141 #Stopping Music 142 if hasattr(self, 'media'): 143 self.media.stop() 144 145 # Stopping BT on slave 146 self.bt_device.reset() 147 self.bt_device.power_off() 148 149 #Stopping BT on master 150 bt_utils.disable_bluetooth(self.dut.droid) 151 152 #Resetting the atten to initial levels 153 self.attenuator.set_atten(self.atten_min) 154 self.log.info('Attenuation set to {} dB'.format(self.atten_min)) 155 156 def teardown_class(self): 157 158 super().teardown_class() 159 self.dut.droid.bluetoothFactoryReset() 160 161 # Stopping BT on slave 162 self.bt_device.reset() 163 self.bt_device.power_off() 164 165 #Stopping BT on master 166 bt_utils.disable_bluetooth(self.dut.droid) 167 168 def set_sar_state(self, ad, signal_dict): 169 """Sets the SAR state corresponding to the BT SAR signal. 170 171 The SAR state is forced using an adb command that takes 172 device signals as input. 173 174 Args: 175 ad: android_device object. 176 signal_dict: dict of BT SAR signals read from the SAR file. 177 Returns: 178 enforced_state: dict of device signals. 179 """ 180 181 signal_dict = {k: max(int(v), 0) for (k, v) in signal_dict.items()} 182 183 #Reading signal_dict 184 head = signal_dict['Head'] 185 wifi_5g = signal_dict['WIFI5Ghz'] 186 hotspot_voice = signal_dict['HotspotVoice'] 187 btmedia = signal_dict['BTMedia'] 188 cell = signal_dict['Cell'] 189 imu = signal_dict['IMU'] 190 191 wifi = wifi_5g 192 wifi_24g = 0 if wifi_5g else 1 193 194 enforced_state = { 195 'Wifi': wifi, 196 'Wifi AP': hotspot_voice, 197 'Earpiece': head, 198 'Bluetooth': 1, 199 'Motion': imu, 200 'Voice': 0, 201 'Wifi 2.4G': wifi_24g, 202 'Radio': cell, 203 'Bluetooth connected': 1, 204 'Bluetooth media': btmedia 205 } 206 207 #Forcing the SAR state 208 adb_output = ad.adb.shell('{} ' 209 '--ei earpiece {} ' 210 '--ei wifi {} ' 211 '--ei wifi_24g {} ' 212 '--ei voice 0 ' 213 '--ei wifi_ap {} ' 214 '--ei bluetooth 1 ' 215 '--ei bt_media {} ' 216 '--ei radio_power {} ' 217 '--ei motion {} ' 218 '--ei bt_connected 1'.format( 219 FORCE_SAR_ADB_COMMAND, head, wifi, 220 wifi_24g, hotspot_voice, btmedia, cell, 221 imu)) 222 223 # Checking if command was successfully enforced 224 if 'result=0' in adb_output: 225 self.log.info('Requested BT SAR state successfully enforced.') 226 return enforced_state 227 else: 228 self.log.error("Couldn't force BT SAR state.") 229 230 def parse_bt_logs(self, ad, begin_time, regex=''): 231 """Returns bt software stats by parsing logcat since begin_time. 232 233 The quantity to be fetched is dictated by the regex provided. 234 235 Args: 236 ad: android_device object. 237 begin_time: time stamp to start the logcat parsing. 238 regex: regex for fetching the required BT software stats. 239 240 Returns: 241 stat: the desired BT stat. 242 """ 243 # Waiting for logcat to update 244 time.sleep(1) 245 bt_adb_log = ad.adb.logcat('-b all -t %s' % begin_time) 246 for line in bt_adb_log.splitlines(): 247 if re.findall(regex, line): 248 stat = re.findall(regex, line)[0] 249 return stat 250 251 raise ValueError('Failed to parse BT logs.') 252 253 def get_current_power_cap(self, ad, begin_time): 254 """ Returns the enforced software power cap since begin_time. 255 256 Returns the enforced power cap since begin_time by parsing logcat. 257 Function should follow a function call that forces a SAR state 258 259 Args: 260 ad: android_device obj. 261 begin_time: time stamp to start. 262 263 Returns: 264 read enforced power cap 265 """ 266 power_cap_regex = 'Bluetooth Tx Power Cap\s+(\d+)' 267 power_cap = self.parse_bt_logs(ad, begin_time, power_cap_regex) 268 return int(power_cap) 269 270 def get_current_device_state(self, ad, begin_time): 271 """ Returns the device state of the android dut since begin_time. 272 273 Returns the device state of the android dut by parsing logcat since 274 begin_time. Function should follow a function call that forces 275 a SAR state. 276 277 Args: 278 ad: android_device obj. 279 begin_time: time stamp to start. 280 281 Returns: 282 device_state: device state of the android device. 283 """ 284 285 device_state_regex = 'updateDeviceState: DeviceState: ([\s*\S+\s]+)' 286 device_state = self.parse_bt_logs(ad, begin_time, device_state_regex) 287 return device_state 288 289 def read_sar_table(self, ad): 290 """Extracts the BT SAR table from the phone. 291 292 Extracts the BT SAR table from the phone into the android device 293 log path directory. 294 295 Args: 296 ad: android_device object. 297 298 Returns: 299 df : BT SAR table (as pandas DataFrame). 300 """ 301 output_path = os.path.join(ad.device_log_path, self.sar_file_name) 302 ad.adb.pull('{} {}'.format(self.sar_file_path, output_path)) 303 df = pd.read_csv(os.path.join(ad.device_log_path, self.sar_file_name)) 304 self.log.info('BT SAR table read from the phone') 305 return df 306 307 def push_table(self, ad, src_path): 308 """Pushes a BT SAR table to the phone. 309 310 Pushes a BT SAR table to the android device and reboots the device. 311 Also creates a backup file if backup flag is True. 312 313 Args: 314 ad: android_device object. 315 src_path: path to the BT SAR table. 316 """ 317 #Copying the to-be-pushed file for logging 318 if os.path.dirname(src_path) != ad.device_log_path: 319 job.run('cp {} {}'.format(src_path, ad.device_log_path)) 320 321 #Pushing the file provided in the config 322 ad.push_system_file(src_path, self.sar_file_path) 323 self.log.info('BT SAR table pushed') 324 ad.reboot() 325 self.bt_sar_df = self.read_sar_table(self.dut) 326 327 def set_PL10_atten_level(self, ad): 328 """Finds the attenuation level at which the phone is at PL10 329 330 Finds PL10 attenuation level by sweeping the attenuation range. 331 If the power level is not achieved during sweep, 332 returns the max atten level 333 334 Args: 335 ad: android object class 336 Returns: 337 atten : attenuation level when the phone is at PL10 338 """ 339 BT_SAR_ATTEN_STEP = 3 340 341 for atten in range(self.atten_min, self.atten_max, BT_SAR_ATTEN_STEP): 342 self.attenuator.set_atten(atten) 343 # Sleep required for BQR to reflect the change in parameters 344 time.sleep(2) 345 metrics = bt_utils.get_bt_metric(ad) 346 if metrics['pwlv'][ad.serial] == 10: 347 self.log.info('PL10 located at {}'.format(atten + 348 BT_SAR_ATTEN_STEP)) 349 return atten + BT_SAR_ATTEN_STEP 350 351 self.log.warn( 352 "PL10 couldn't be located in the given attenuation range") 353 return atten 354