1#/usr/bin/env python3.4 2# 3# Copyright (C) 2016 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 17import random 18import pprint 19import string 20import queue 21import threading 22import time 23from acts import utils 24 25from contextlib2 import suppress 26from subprocess import call 27 28from acts.logger import LoggerProxy 29from acts.test_utils.bt.BleEnum import AdvertiseSettingsAdvertiseMode 30from acts.test_utils.bt.BleEnum import ScanSettingsCallbackType 31from acts.test_utils.bt.BleEnum import ScanSettingsMatchMode 32from acts.test_utils.bt.BleEnum import ScanSettingsMatchNum 33from acts.test_utils.bt.BleEnum import ScanSettingsScanResultType 34from acts.test_utils.bt.BleEnum import ScanSettingsScanMode 35from acts.test_utils.bt.BleEnum import ScanSettingsReportDelaySeconds 36from acts.test_utils.bt.BleEnum import AdvertiseSettingsAdvertiseType 37from acts.test_utils.bt.BleEnum import AdvertiseSettingsAdvertiseTxPower 38from acts.test_utils.bt.BleEnum import ScanSettingsMatchNum 39from acts.test_utils.bt.BleEnum import ScanSettingsScanResultType 40from acts.test_utils.bt.BleEnum import ScanSettingsScanMode 41from acts.test_utils.bt.BtEnum import BluetoothScanModeType 42from acts.test_utils.bt.BtEnum import RfcommUuid 43from acts.utils import exe_cmd 44 45default_timeout = 15 46# bt discovery timeout 47default_discovery_timeout = 3 48log = LoggerProxy() 49 50# Callback strings 51scan_result = "BleScan{}onScanResults" 52scan_failed = "BleScan{}onScanFailed" 53batch_scan_result = "BleScan{}onBatchScanResult" 54adv_fail = "BleAdvertise{}onFailure" 55adv_succ = "BleAdvertise{}onSuccess" 56bluetooth_off = "BluetoothStateChangedOff" 57bluetooth_on = "BluetoothStateChangedOn" 58 59# rfcomm test uuids 60rfcomm_secure_uuid = "fa87c0d0-afac-11de-8a39-0800200c9a66" 61rfcomm_insecure_uuid = "8ce255c0-200a-11e0-ac64-0800200c9a66" 62 63advertisements_to_devices = { 64 "Nexus 4": 0, 65 "Nexus 5": 0, 66 "Nexus 5X": 15, 67 "Nexus 7": 0, 68 "Nexus Player": 1, 69 "Nexus 6": 4, 70 "Nexus 6P": 4, 71 "AOSP on Shamu": 4, 72 "Nexus 9": 4, 73 "Sprout": 10, 74 "Micromax AQ4501": 10, 75 "4560MMX": 10, 76 "G Watch R": 1, 77 "Gear Live": 1, 78 "SmartWatch 3": 1, 79 "Zenwatch": 1, 80 "AOSP on Shamu": 4, 81 "MSM8992 for arm64": 9, 82 "LG Watch Urbane": 1, 83 "Pixel C": 4, 84 "angler": 4, 85 "bullhead": 15, 86} 87 88batch_scan_supported_list = { 89 "Nexus 4": False, 90 "Nexus 5": False, 91 "Nexus 7": False, 92 "Nexus Player": True, 93 "Nexus 6": True, 94 "Nexus 6P": True, 95 "Nexus 5X": True, 96 "AOSP on Shamu": True, 97 "Nexus 9": True, 98 "Sprout": True, 99 "Micromax AQ4501": True, 100 "4560MMX": True, 101 "Pixel C": True, 102 "G Watch R": True, 103 "Gear Live": True, 104 "SmartWatch 3": True, 105 "Zenwatch": True, 106 "AOSP on Shamu": True, 107 "MSM8992 for arm64": True, 108 "LG Watch Urbane": True, 109 "angler": True, 110 "bullhead": True, 111} 112 113 114def generate_ble_scan_objects(droid): 115 filter_list = droid.bleGenFilterList() 116 scan_settings = droid.bleBuildScanSetting() 117 scan_callback = droid.bleGenScanCallback() 118 return filter_list, scan_settings, scan_callback 119 120 121def generate_ble_advertise_objects(droid): 122 advertise_callback = droid.bleGenBleAdvertiseCallback() 123 advertise_data = droid.bleBuildAdvertiseData() 124 advertise_settings = droid.bleBuildAdvertiseSettings() 125 return advertise_callback, advertise_data, advertise_settings 126 127 128def extract_string_from_byte_array(string_list): 129 """Extract the string from array of string list 130 """ 131 start = 1 132 end = len(string_list) - 1 133 extract_string = string_list[start:end] 134 return extract_string 135 136 137def extract_uuidlist_from_record(uuid_string_list): 138 """Extract uuid from Service UUID List 139 """ 140 start = 1 141 end = len(uuid_string_list) - 1 142 uuid_length = 36 143 uuidlist = [] 144 while start < end: 145 uuid = uuid_string_list[start:(start + uuid_length)] 146 start += uuid_length + 1 147 uuidlist.append(uuid) 148 return uuidlist 149 150 151def build_advertise_settings(droid, mode, txpower, type): 152 """Build Advertise Settings 153 """ 154 droid.bleSetAdvertiseSettingsAdvertiseMode(mode) 155 droid.bleSetAdvertiseSettingsTxPowerLevel(txpower) 156 droid.bleSetAdvertiseSettingsIsConnectable(type) 157 settings = droid.bleBuildAdvertiseSettings() 158 return settings 159 160 161def setup_multiple_devices_for_bt_test(android_devices): 162 log.info("Setting up Android Devices") 163 # TODO: Temp fix for an selinux error. 164 for ad in android_devices: 165 ad.adb.shell("setenforce 0") 166 threads = [] 167 try: 168 for a in android_devices: 169 thread = threading.Thread(target=reset_bluetooth, args=([[a]])) 170 threads.append(thread) 171 thread.start() 172 for t in threads: 173 t.join() 174 175 for a in android_devices: 176 d = a.droid 177 setup_result = d.bluetoothSetLocalName(generate_id_by_size(4)) 178 if not setup_result: 179 log.error("Failed to set device name.") 180 return setup_result 181 d.bluetoothDisableBLE() 182 bonded_devices = d.bluetoothGetBondedDevices() 183 for b in bonded_devices: 184 d.bluetoothUnbond(b['address']) 185 for a in android_devices: 186 setup_result = a.droid.bluetoothConfigHciSnoopLog(True) 187 if not setup_result: 188 log.error("Failed to enable Bluetooth Hci Snoop Logging.") 189 return setup_result 190 except Exception as err: 191 log.error("Something went wrong in multi device setup: {}".format(err)) 192 return False 193 return setup_result 194 195 196def reset_bluetooth(android_devices): 197 """Resets bluetooth on the list of android devices passed into the function. 198 :param android_devices: list of android devices 199 :return: bool 200 """ 201 for a in android_devices: 202 droid, ed = a.droid, a.ed 203 log.info("Reset state of bluetooth on device: {}".format( 204 droid.getBuildSerial())) 205 if droid.bluetoothCheckState() is True: 206 droid.bluetoothToggleState(False) 207 expected_bluetooth_off_event_name = bluetooth_off 208 try: 209 ed.pop_event(expected_bluetooth_off_event_name, 210 default_timeout) 211 except Exception: 212 log.error("Failed to toggle Bluetooth off.") 213 return False 214 # temp sleep for b/17723234 215 time.sleep(3) 216 droid.bluetoothToggleState(True) 217 expected_bluetooth_on_event_name = bluetooth_on 218 try: 219 ed.pop_event(expected_bluetooth_on_event_name, default_timeout) 220 except Exception: 221 log.info("Failed to toggle Bluetooth on (no broadcast received).") 222 # Try one more time to poke at the actual state. 223 if droid.bluetoothCheckState() is True: 224 log.info(".. actual state is ON") 225 return True 226 log.info(".. actual state is OFF") 227 return False 228 return True 229 230 231def get_advanced_droid_list(android_devices): 232 droid_list = [] 233 for a in android_devices: 234 d, e = a.droid, a.ed 235 model = d.getBuildModel() 236 max_advertisements = 1 237 batch_scan_supported = True 238 if model in advertisements_to_devices.keys(): 239 max_advertisements = advertisements_to_devices[model] 240 if model in batch_scan_supported_list.keys(): 241 batch_scan_supported = batch_scan_supported_list[model] 242 role = { 243 'droid': d, 244 'ed': e, 245 'max_advertisements': max_advertisements, 246 'batch_scan_supported': batch_scan_supported 247 } 248 droid_list.append(role) 249 return droid_list 250 251 252def generate_id_by_size( 253 size, 254 chars=( 255 string.ascii_lowercase + string.ascii_uppercase + string.digits)): 256 return ''.join(random.choice(chars) for _ in range(size)) 257 258 259def cleanup_scanners_and_advertisers(scn_android_device, scan_callback_list, 260 adv_android_device, adv_callback_list): 261 """ 262 Try to gracefully stop all scanning and advertising instances. 263 """ 264 scan_droid, scan_ed = scn_android_device.droid, scn_android_device.ed 265 adv_droid = adv_android_device.droid 266 try: 267 for scan_callback in scan_callback_list: 268 scan_droid.bleStopBleScan(scan_callback) 269 except Exception as err: 270 log.debug( 271 "Failed to stop LE scan... reseting Bluetooth. Error {}".format( 272 err)) 273 reset_bluetooth([scn_android_device]) 274 try: 275 for adv_callback in adv_callback_list: 276 adv_droid.bleStopBleAdvertising(adv_callback) 277 except Exception as err: 278 log.debug( 279 "Failed to stop LE advertisement... reseting Bluetooth. Error {}".format( 280 err)) 281 reset_bluetooth([adv_android_device]) 282 283 284def get_mac_address_of_generic_advertisement(scan_ad, adv_ad): 285 adv_ad.droid.bleSetAdvertiseDataIncludeDeviceName(True) 286 adv_ad.droid.bleSetAdvertiseSettingsAdvertiseMode( 287 AdvertiseSettingsAdvertiseMode.ADVERTISE_MODE_LOW_LATENCY.value) 288 adv_ad.droid.bleSetAdvertiseSettingsIsConnectable(True) 289 adv_ad.droid.bleSetAdvertiseSettingsTxPowerLevel( 290 AdvertiseSettingsAdvertiseTxPower.ADVERTISE_TX_POWER_HIGH.value) 291 advertise_callback, advertise_data, advertise_settings = ( 292 generate_ble_advertise_objects(adv_ad.droid)) 293 adv_ad.droid.bleStartBleAdvertising(advertise_callback, advertise_data, 294 advertise_settings) 295 adv_ad.ed.pop_event("BleAdvertise{}onSuccess".format(advertise_callback), 296 default_timeout) 297 filter_list = scan_ad.droid.bleGenFilterList() 298 scan_settings = scan_ad.droid.bleBuildScanSetting() 299 scan_callback = scan_ad.droid.bleGenScanCallback() 300 scan_ad.droid.bleSetScanFilterDeviceName( 301 adv_ad.droid.bluetoothGetLocalName()) 302 scan_ad.droid.bleBuildScanFilter(filter_list) 303 scan_ad.droid.bleStartBleScan(filter_list, scan_settings, scan_callback) 304 event = scan_ad.ed.pop_event( 305 "BleScan{}onScanResults".format(scan_callback), default_timeout) 306 mac_address = event['data']['Result']['deviceInfo']['address'] 307 scan_ad.droid.bleStopBleScan(scan_callback) 308 return mac_address, advertise_callback 309 310 311def get_device_local_info(droid): 312 local_info_dict = {} 313 local_info_dict['name'] = droid.bluetoothGetLocalName() 314 local_info_dict['uuids'] = droid.bluetoothGetLocalUuids() 315 return local_info_dict 316 317 318def enable_bluetooth(droid, ed): 319 if droid.bluetoothCheckState() is False: 320 droid.bluetoothToggleState(True) 321 if droid.bluetoothCheckState() is False: 322 return False 323 return True 324 325 326def disable_bluetooth(droid, ed): 327 if droid.bluetoothCheckState() is True: 328 droid.bluetoothToggleState(False) 329 if droid.bluetoothCheckState() is True: 330 return False 331 return True 332 333 334def set_bt_scan_mode(ad, scan_mode_value): 335 droid, ed = ad.droid, ad.ed 336 if scan_mode_value == BluetoothScanModeType.STATE_OFF.value: 337 disable_bluetooth(droid, ed) 338 scan_mode = droid.bluetoothGetScanMode() 339 reset_bluetooth([ad]) 340 if scan_mode != scan_mode_value: 341 return False 342 elif scan_mode_value == BluetoothScanModeType.SCAN_MODE_NONE.value: 343 droid.bluetoothMakeUndiscoverable() 344 scan_mode = droid.bluetoothGetScanMode() 345 if scan_mode != scan_mode_value: 346 return False 347 elif scan_mode_value == BluetoothScanModeType.SCAN_MODE_CONNECTABLE.value: 348 droid.bluetoothMakeUndiscoverable() 349 droid.bluetoothMakeConnectable() 350 scan_mode = droid.bluetoothGetScanMode() 351 if scan_mode != scan_mode_value: 352 return False 353 elif (scan_mode_value == 354 BluetoothScanModeType.SCAN_MODE_CONNECTABLE_DISCOVERABLE.value): 355 droid.bluetoothMakeDiscoverable() 356 scan_mode = droid.bluetoothGetScanMode() 357 if scan_mode != scan_mode_value: 358 return False 359 else: 360 # invalid scan mode 361 return False 362 return True 363 364 365def set_device_name(droid, name): 366 droid.bluetoothSetLocalName(name) 367 time.sleep(2) 368 droid_name = droid.bluetoothGetLocalName() 369 if droid_name != name: 370 return False 371 return True 372 373 374def check_device_supported_profiles(droid): 375 profile_dict = {} 376 profile_dict['hid'] = droid.bluetoothHidIsReady() 377 profile_dict['hsp'] = droid.bluetoothHspIsReady() 378 profile_dict['a2dp'] = droid.bluetoothA2dpIsReady() 379 profile_dict['avrcp'] = droid.bluetoothAvrcpIsReady() 380 return profile_dict 381 382 383def log_energy_info(droids, state): 384 return_string = "{} Energy info collection:\n".format(state) 385 for d in droids: 386 with suppress(Exception): 387 if (d.getBuildModel() != "Nexus 5" or 388 d.getBuildModel() != "Nexus 4"): 389 390 description = ("Device: {}\tEnergyStatus: {}\n".format( 391 d.getBuildSerial(), 392 d.bluetoothGetControllerActivityEnergyInfo(1))) 393 return_string = return_string + description 394 return return_string 395 396 397def pair_pri_to_sec(pri_droid, sec_droid): 398 # Enable discovery on sec_droid so that pri_droid can find it. 399 # The timeout here is based on how much time it would take for two devices 400 # to pair with each other once pri_droid starts seeing devices. 401 log.info( 402 "Bonding device {} to {}".format(pri_droid.bluetoothGetLocalAddress(), 403 sec_droid.bluetoothGetLocalAddress())) 404 sec_droid.bluetoothMakeDiscoverable(default_timeout) 405 target_address = sec_droid.bluetoothGetLocalAddress() 406 log.debug("Starting paring helper on each device") 407 pri_droid.bluetoothStartPairingHelper() 408 sec_droid.bluetoothStartPairingHelper() 409 log.info("Primary device starting discovery and executing bond") 410 result = pri_droid.bluetoothDiscoverAndBond(target_address) 411 # Loop until we have bonded successfully or timeout. 412 end_time = time.time() + default_timeout 413 log.info("Verifying devices are bonded") 414 while time.time() < end_time: 415 bonded_devices = pri_droid.bluetoothGetBondedDevices() 416 bonded = False 417 for d in bonded_devices: 418 if d['address'] == target_address: 419 log.info("Successfully bonded to device") 420 return True 421 time.sleep(1) 422 # Timed out trying to bond. 423 log.debug("Failed to bond devices.") 424 return False 425 426 427def take_btsnoop_logs(android_devices, testcase, testname): 428 for a in android_devices: 429 take_btsnoop_log(a, testcase, testname) 430 431 432def take_btsnoop_log(ad, testcase, test_name): 433 """Grabs the btsnoop_hci log on a device and stores it in the log directory 434 of the test class. 435 436 If you want grab the btsnoop_hci log, call this function with android_device 437 objects in on_fail. Bug report takes a relative long time to take, so use 438 this cautiously. 439 440 Params: 441 test_name: Name of the test case that triggered this bug report. 442 android_device: The android_device instance to take bugreport on. 443 """ 444 test_name = "".join(x for x in test_name if x.isalnum()) 445 with suppress(Exception): 446 serial = ad.droid.getBuildSerial() 447 device_model = ad.droid.getBuildModel() 448 device_model = device_model.replace(" ", "") 449 out_name = ','.join((test_name, device_model, serial)) 450 snoop_path = ad.log_path + "/BluetoothSnoopLogs" 451 utils.create_dir(snoop_path) 452 cmd = ''.join(("adb -s ", serial, " pull /sdcard/btsnoop_hci.log ", 453 snoop_path + '/' + out_name, ".btsnoop_hci.log")) 454 testcase.log.info("Test failed, grabbing the bt_snoop logs on {} {}." 455 .format(device_model, serial)) 456 exe_cmd(cmd) 457 458 459def kill_bluetooth_process(ad): 460 log.info("Killing Bluetooth process.") 461 pid = ad.adb.shell( 462 "ps | grep com.android.bluetooth | awk '{print $2}'").decode('ascii') 463 call(["adb -s " + ad.serial + " shell kill " + pid], 464 shell=True) 465 466 467def rfcomm_connect(ad, device_address): 468 rf_client_ad = ad 469 log.debug("Performing RFCOMM connection to {}".format(device_address)) 470 try: 471 ad.droid.bluetoothRfcommConnect(device_address) 472 except Exception as err: 473 log.error("Failed to connect: {}".format(err)) 474 ad.droid.bluetoothRfcommCloseSocket() 475 return 476 finally: 477 return 478 return 479 480 481def rfcomm_accept(ad): 482 rf_server_ad = ad 483 log.debug("Performing RFCOMM accept") 484 try: 485 ad.droid.bluetoothRfcommAccept(RfcommUuid.DEFAULT_UUID.value, 486 default_timeout) 487 except Exception as err: 488 log.error("Failed to accept: {}".format(err)) 489 ad.droid.bluetoothRfcommCloseSocket() 490 return 491 finally: 492 return 493 return 494 495 496def write_read_verify_data(client_ad, server_ad, msg, binary=False): 497 log.info("Write message.") 498 try: 499 if binary: 500 client_ad.droid.bluetoothRfcommWriteBinary(msg) 501 else: 502 client_ad.droid.bluetoothRfcommWrite(msg) 503 except Exception as err: 504 log.error("Failed to write data: {}".format(err)) 505 return False 506 log.info("Read message.") 507 try: 508 if binary: 509 read_msg = server_ad.droid.bluetoothRfcommReadBinary().rstrip("\r\n") 510 else: 511 read_msg = server_ad.droid.bluetoothRfcommRead() 512 except Exception as err: 513 log.error("Failed to read data: {}".format(err)) 514 return False 515 log.info("Verify message.") 516 if msg != read_msg: 517 log.error("Mismatch! Read: {}, Expected: {}".format( 518 read_msg, msg)) 519 return False 520 return True 521