• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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