Mark De Ruyter | d9c540a | 2018-05-04 11:21:55 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
tturney | 1bdf77d | 2015-12-28 17:46:13 -0800 | [diff] [blame] | 2 | # |
| 3 | # Copyright (C) 2016 The Android Open Source Project |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 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 | |
Ang Li | 5bd83f3 | 2016-05-23 14:39:38 -0700 | [diff] [blame] | 17 | import logging |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 18 | import random |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 19 | import string |
tturney | 50f851d | 2016-07-07 11:07:37 -0700 | [diff] [blame] | 20 | from queue import Empty |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 21 | import threading |
| 22 | import time |
tturney | 03a45ae | 2016-05-24 15:36:05 -0700 | [diff] [blame] | 23 | from acts import utils |
tprotopopov | 2e18bbb | 2018-10-02 10:29:01 -0700 | [diff] [blame] | 24 | import re |
tturney | e3170f0 | 2016-05-19 14:37:00 -0700 | [diff] [blame] | 25 | from subprocess import call |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 26 | |
Aidan Holloway-bidwell | f8f5151 | 2018-11-06 10:42:14 -0800 | [diff] [blame] | 27 | from acts.test_utils.bt.bt_constants import bits_per_samples |
| 28 | from acts.test_utils.bt.bt_constants import channel_modes |
| 29 | from acts.test_utils.bt.bt_constants import codec_types |
| 30 | from acts.test_utils.bt.bt_constants import codec_priorities |
| 31 | from acts.test_utils.bt.bt_constants import sample_rates |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 32 | from acts.test_utils.bt.bt_constants import adv_fail |
| 33 | from acts.test_utils.bt.bt_constants import adv_succ |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 34 | from acts.test_utils.bt.bt_constants import batch_scan_not_supported_list |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 35 | from acts.test_utils.bt.bt_constants import ble_advertise_settings_modes |
| 36 | from acts.test_utils.bt.bt_constants import ble_advertise_settings_tx_powers |
Aidan Holloway-bidwell | f8f5151 | 2018-11-06 10:42:14 -0800 | [diff] [blame] | 37 | from acts.test_utils.bt.bt_constants import bluetooth_a2dp_codec_config_changed |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 38 | from acts.test_utils.bt.bt_constants import bluetooth_off |
| 39 | from acts.test_utils.bt.bt_constants import bluetooth_on |
| 40 | from acts.test_utils.bt.bt_constants import \ |
| 41 | bluetooth_profile_connection_state_changed |
| 42 | from acts.test_utils.bt.bt_constants import bt_default_timeout |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 43 | from acts.test_utils.bt.bt_constants import bt_profile_states |
tturney | b72742f | 2017-08-29 17:46:50 -0700 | [diff] [blame] | 44 | from acts.test_utils.bt.bt_constants import bt_profile_constants |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 45 | from acts.test_utils.bt.bt_constants import bt_rfcomm_uuids |
Timofey Protopopov | a47c881 | 2018-03-28 11:37:31 -0700 | [diff] [blame] | 46 | from acts.test_utils.bt.bt_constants import bluetooth_socket_conn_test_uuid |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 47 | from acts.test_utils.bt.bt_constants import bt_scan_mode_types |
| 48 | from acts.test_utils.bt.bt_constants import btsnoop_last_log_path_on_device |
| 49 | from acts.test_utils.bt.bt_constants import btsnoop_log_path_on_device |
| 50 | from acts.test_utils.bt.bt_constants import default_rfcomm_timeout_ms |
Stanley Tng | 180a8be | 2017-11-29 10:53:33 -0800 | [diff] [blame] | 51 | from acts.test_utils.bt.bt_constants import default_bluetooth_socket_timeout_ms |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 52 | from acts.test_utils.bt.bt_constants import pairing_variant_passkey_confirmation |
| 53 | from acts.test_utils.bt.bt_constants import pan_connect_timeout |
| 54 | from acts.test_utils.bt.bt_constants import small_timeout |
| 55 | from acts.test_utils.bt.bt_constants import scan_result |
Tom Turney | 7b0baa5 | 2018-06-26 11:31:31 -0700 | [diff] [blame] | 56 | from acts.test_utils.bt.bt_constants import sig_uuid_constants |
Hansong Zhang | d78b3b8 | 2017-10-03 10:36:12 -0700 | [diff] [blame] | 57 | from acts.test_utils.bt.bt_constants import hid_id_keyboard |
tturney | 83c28a0 | 2018-03-06 14:30:54 -0800 | [diff] [blame] | 58 | |
tturney | 1e174fd | 2017-06-01 14:42:20 -0700 | [diff] [blame] | 59 | from acts.test_utils.tel.tel_test_utils import toggle_airplane_mode_by_adb |
tturney | b92368a | 2016-09-13 10:43:15 -0700 | [diff] [blame] | 60 | from acts.test_utils.tel.tel_test_utils import verify_http_connection |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 61 | from acts.utils import exe_cmd |
| 62 | |
Ang Li | 5bd83f3 | 2016-05-23 14:39:38 -0700 | [diff] [blame] | 63 | log = logging |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 64 | |
tturney | 57eb93a | 2016-06-21 13:58:06 -0700 | [diff] [blame] | 65 | advertisements_to_devices = {} |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 66 | |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 67 | |
tturney | 951533f | 2016-06-23 11:24:25 -0700 | [diff] [blame] | 68 | class BtTestUtilsError(Exception): |
| 69 | pass |
| 70 | |
tturney | b92368a | 2016-09-13 10:43:15 -0700 | [diff] [blame] | 71 | |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 72 | def _add_android_device_to_dictionary(android_device, profile_list, |
| 73 | selector_dict): |
| 74 | """Adds the AndroidDevice and supported features to the selector dictionary |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 75 | |
| 76 | Args: |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 77 | android_device: The Android device. |
| 78 | profile_list: The list of profiles the Android device supports. |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 79 | """ |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 80 | for profile in profile_list: |
| 81 | if profile in selector_dict and android_device not in selector_dict[profile]: |
| 82 | selector_dict[profile].append(android_device) |
| 83 | else: |
| 84 | selector_dict[profile] = [android_device] |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 85 | |
| 86 | |
tturney | 50f851d | 2016-07-07 11:07:37 -0700 | [diff] [blame] | 87 | def bluetooth_enabled_check(ad): |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 88 | """Checks if the Bluetooth state is enabled, if not it will attempt to |
| 89 | enable it. |
| 90 | |
| 91 | Args: |
| 92 | ad: The Android device list to enable Bluetooth on. |
| 93 | |
| 94 | Returns: |
| 95 | True if successful, false if unsuccessful. |
| 96 | """ |
tturney | 50f851d | 2016-07-07 11:07:37 -0700 | [diff] [blame] | 97 | if not ad.droid.bluetoothCheckState(): |
| 98 | ad.droid.bluetoothToggleState(True) |
| 99 | expected_bluetooth_on_event_name = bluetooth_on |
| 100 | try: |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 101 | ad.ed.pop_event(expected_bluetooth_on_event_name, |
| 102 | bt_default_timeout) |
tturney | 50f851d | 2016-07-07 11:07:37 -0700 | [diff] [blame] | 103 | except Empty: |
tturney | 5da6826 | 2017-05-02 08:57:52 -0700 | [diff] [blame] | 104 | ad.log.info( |
| 105 | "Failed to toggle Bluetooth on(no broadcast received).") |
tturney | 50f851d | 2016-07-07 11:07:37 -0700 | [diff] [blame] | 106 | # Try one more time to poke at the actual state. |
| 107 | if ad.droid.bluetoothCheckState(): |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 108 | ad.log.info(".. actual state is ON") |
tturney | 50f851d | 2016-07-07 11:07:37 -0700 | [diff] [blame] | 109 | return True |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 110 | ad.log.error(".. actual state is OFF") |
tturney | 50f851d | 2016-07-07 11:07:37 -0700 | [diff] [blame] | 111 | return False |
| 112 | return True |
| 113 | |
Tom Turney | 9bce15d | 2018-06-21 13:00:42 -0700 | [diff] [blame] | 114 | |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 115 | def check_device_supported_profiles(droid): |
| 116 | """Checks for Android device supported profiles. |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 117 | |
| 118 | Args: |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 119 | droid: The droid object to query. |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 120 | |
| 121 | Returns: |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 122 | A dictionary of supported profiles. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 123 | """ |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 124 | profile_dict = {} |
| 125 | profile_dict['hid'] = droid.bluetoothHidIsReady() |
| 126 | profile_dict['hsp'] = droid.bluetoothHspIsReady() |
| 127 | profile_dict['a2dp'] = droid.bluetoothA2dpIsReady() |
| 128 | profile_dict['avrcp'] = droid.bluetoothAvrcpIsReady() |
| 129 | profile_dict['a2dp_sink'] = droid.bluetoothA2dpSinkIsReady() |
| 130 | profile_dict['hfp_client'] = droid.bluetoothHfpClientIsReady() |
| 131 | profile_dict['pbap_client'] = droid.bluetoothPbapClientIsReady() |
| 132 | return profile_dict |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 133 | |
| 134 | |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 135 | def cleanup_scanners_and_advertisers(scn_android_device, scn_callback_list, |
tturney | 1ce8dc6 | 2016-02-18 09:55:53 -0800 | [diff] [blame] | 136 | adv_android_device, adv_callback_list): |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 137 | """Try to gracefully stop all scanning and advertising instances. |
| 138 | |
| 139 | Args: |
| 140 | scn_android_device: The Android device that is actively scanning. |
| 141 | scn_callback_list: The scan callback id list that needs to be stopped. |
| 142 | adv_android_device: The Android device that is actively advertising. |
| 143 | adv_callback_list: The advertise callback id list that needs to be |
| 144 | stopped. |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 145 | """ |
tturney | 1ce8dc6 | 2016-02-18 09:55:53 -0800 | [diff] [blame] | 146 | scan_droid, scan_ed = scn_android_device.droid, scn_android_device.ed |
| 147 | adv_droid = adv_android_device.droid |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 148 | try: |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 149 | for scan_callback in scn_callback_list: |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 150 | scan_droid.bleStopBleScan(scan_callback) |
tturney | e3170f0 | 2016-05-19 14:37:00 -0700 | [diff] [blame] | 151 | except Exception as err: |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 152 | scn_android_device.log.debug( |
| 153 | "Failed to stop LE scan... reseting Bluetooth. Error {}".format( |
| 154 | err)) |
tturney | 1ce8dc6 | 2016-02-18 09:55:53 -0800 | [diff] [blame] | 155 | reset_bluetooth([scn_android_device]) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 156 | try: |
| 157 | for adv_callback in adv_callback_list: |
| 158 | adv_droid.bleStopBleAdvertising(adv_callback) |
tturney | e3170f0 | 2016-05-19 14:37:00 -0700 | [diff] [blame] | 159 | except Exception as err: |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 160 | adv_android_device.log.debug( |
tturney | b92368a | 2016-09-13 10:43:15 -0700 | [diff] [blame] | 161 | "Failed to stop LE advertisement... reseting Bluetooth. Error {}". |
Timofey Protopopov | a47c881 | 2018-03-28 11:37:31 -0700 | [diff] [blame] | 162 | format(err)) |
tturney | 1ce8dc6 | 2016-02-18 09:55:53 -0800 | [diff] [blame] | 163 | reset_bluetooth([adv_android_device]) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 164 | |
| 165 | |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 166 | def clear_bonded_devices(ad): |
| 167 | """Clear bonded devices from the input Android device. |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 168 | |
| 169 | Args: |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 170 | ad: the Android device performing the connection. |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 171 | Returns: |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 172 | True if clearing bonded devices was successful, false if unsuccessful. |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 173 | """ |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 174 | bonded_device_list = ad.droid.bluetoothGetBondedDevices() |
| 175 | while bonded_device_list: |
| 176 | device_address = bonded_device_list[0]['address'] |
| 177 | if not ad.droid.bluetoothUnbond(device_address): |
| 178 | log.error("Failed to unbond {} from {}".format( |
| 179 | device_address, ad.serial)) |
| 180 | return False |
| 181 | log.info("Successfully unbonded {} from {}".format( |
| 182 | device_address, ad.serial)) |
| 183 | #TODO: wait for BOND_STATE_CHANGED intent instead of waiting |
| 184 | time.sleep(1) |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 185 | |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 186 | # If device was first connected using LE transport, after bonding it is |
| 187 | # accessible through it's LE address, and through it classic address. |
| 188 | # Unbonding it will unbond two devices representing different |
| 189 | # "addresses". Attempt to unbond such already unbonded devices will |
| 190 | # result in bluetoothUnbond returning false. |
| 191 | bonded_device_list = ad.droid.bluetoothGetBondedDevices() |
Sanket Agarwal | c8fdc1a | 2016-07-06 16:39:34 -0700 | [diff] [blame] | 192 | return True |
| 193 | |
Tom Turney | 9bce15d | 2018-06-21 13:00:42 -0700 | [diff] [blame] | 194 | |
Aidan Holloway-bidwell | 9e44b9d | 2018-11-07 14:33:27 -0800 | [diff] [blame] | 195 | def connect_phone_to_headset(android, headset, timeout=bt_default_timeout, |
| 196 | connection_check_period=10): |
| 197 | """Connects android phone to bluetooth headset. |
| 198 | Headset object must have methods power_on and enter_pairing_mode, |
| 199 | and attribute mac_address. |
| 200 | |
| 201 | Args: |
| 202 | android: AndroidDevice object with SL4A installed. |
| 203 | headset: Object with attribute mac_address and methods power_on and |
| 204 | enter_pairing_mode. |
| 205 | timeout: Seconds to wait for devices to connect. |
| 206 | connection_check_period: how often to check for connection once the |
| 207 | SL4A connect RPC has been sent. |
| 208 | Returns: |
| 209 | connected (bool): True if devices are paired and connected by end of |
| 210 | method. False otherwise. |
| 211 | """ |
| 212 | connected = is_a2dp_src_device_connected(android, headset.mac_address) |
| 213 | log.info('Devices connected before pair attempt: %s' % connected) |
| 214 | start_time = time.time() |
| 215 | # If already connected, skip pair and connect attempt. |
| 216 | while not connected and (time.time() - start_time < timeout): |
| 217 | bonded_info = android.droid.bluetoothA2dpGetConnectedDevices() |
| 218 | if headset.mac_address not in [info["address"] for info in bonded_info]: |
| 219 | # Turn on headset and initiate pairing mode. |
| 220 | headset.enter_pairing_mode() |
| 221 | # Use SL4A to pair and connect with headset. |
| 222 | android.droid.bluetoothDiscoverAndBond(headset.mac_address) |
| 223 | else: # Device is bonded but not connected |
| 224 | android.droid.bluetoothConnectBonded(headset.mac_address) |
| 225 | log.info('Waiting for connection...') |
| 226 | time.sleep(connection_check_period) |
| 227 | # Check for connection. |
| 228 | connected = is_a2dp_src_device_connected(android, headset.mac_address) |
| 229 | log.info('Devices connected after pair attempt: %s' % connected) |
| 230 | return connected |
| 231 | |
| 232 | |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 233 | def connect_pri_to_sec(pri_ad, sec_ad, profiles_set, attempts=2): |
Sanket Agarwal | c96b9cc | 2016-08-18 12:01:04 -0700 | [diff] [blame] | 234 | """Connects pri droid to secondary droid. |
tturney | 57eb93a | 2016-06-21 13:58:06 -0700 | [diff] [blame] | 235 | |
Sanket Agarwal | c96b9cc | 2016-08-18 12:01:04 -0700 | [diff] [blame] | 236 | Args: |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 237 | pri_ad: AndroidDroid initiating connection |
| 238 | sec_ad: AndroidDroid accepting connection |
Sanket Agarwal | c96b9cc | 2016-08-18 12:01:04 -0700 | [diff] [blame] | 239 | profiles_set: Set of profiles to be connected |
| 240 | attempts: Number of attempts to try until failure. |
| 241 | |
| 242 | Returns: |
| 243 | Pass if True |
| 244 | Fail if False |
| 245 | """ |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 246 | device_addr = sec_ad.droid.bluetoothGetLocalAddress() |
| 247 | # Allows extra time for the SDP records to be updated. |
| 248 | time.sleep(2) |
Sanket Agarwal | c96b9cc | 2016-08-18 12:01:04 -0700 | [diff] [blame] | 249 | curr_attempts = 0 |
| 250 | while curr_attempts < attempts: |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 251 | log.info("connect_pri_to_sec curr attempt {} total {}".format( |
| 252 | curr_attempts, attempts)) |
| 253 | if _connect_pri_to_sec(pri_ad, sec_ad, profiles_set): |
| 254 | return True |
| 255 | curr_attempts += 1 |
| 256 | log.error("connect_pri_to_sec failed to connect after {} attempts".format( |
| 257 | attempts)) |
Sanket Agarwal | c96b9cc | 2016-08-18 12:01:04 -0700 | [diff] [blame] | 258 | return False |
| 259 | |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 260 | |
| 261 | def _connect_pri_to_sec(pri_ad, sec_ad, profiles_set): |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 262 | """Connects pri droid to secondary droid. |
| 263 | |
| 264 | Args: |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 265 | pri_ad: AndroidDroid initiating connection. |
| 266 | sec_ad: AndroidDroid accepting connection. |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 267 | profiles_set: Set of profiles to be connected. |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 268 | |
| 269 | Returns: |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 270 | True of connection is successful, false if unsuccessful. |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 271 | """ |
| 272 | # Check if we support all profiles. |
tturney | e1f6c72 | 2017-08-30 07:03:11 -0700 | [diff] [blame] | 273 | supported_profiles = bt_profile_constants.values() |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 274 | for profile in profiles_set: |
| 275 | if profile not in supported_profiles: |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 276 | pri_ad.log.info("Profile {} is not supported list {}".format( |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 277 | profile, supported_profiles)) |
| 278 | return False |
| 279 | |
| 280 | # First check that devices are bonded. |
| 281 | paired = False |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 282 | for paired_device in pri_ad.droid.bluetoothGetBondedDevices(): |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 283 | if paired_device['address'] == \ |
Timofey Protopopov | a47c881 | 2018-03-28 11:37:31 -0700 | [diff] [blame] | 284 | sec_ad.droid.bluetoothGetLocalAddress(): |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 285 | paired = True |
| 286 | break |
| 287 | |
| 288 | if not paired: |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 289 | pri_ad.log.error("Not paired to {}".format(sec_ad.serial)) |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 290 | return False |
| 291 | |
| 292 | # Now try to connect them, the following call will try to initiate all |
| 293 | # connections. |
Stanley Tng | c9a123d | 2017-12-05 17:37:49 -0800 | [diff] [blame] | 294 | pri_ad.droid.bluetoothConnectBonded( |
| 295 | sec_ad.droid.bluetoothGetLocalAddress()) |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 296 | |
tturney | 4606078 | 2016-11-14 16:44:38 -0800 | [diff] [blame] | 297 | end_time = time.time() + 10 |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 298 | profile_connected = set() |
tturney | 4606078 | 2016-11-14 16:44:38 -0800 | [diff] [blame] | 299 | sec_addr = sec_ad.droid.bluetoothGetLocalAddress() |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 300 | pri_ad.log.info("Profiles to be connected {}".format(profiles_set)) |
tturney | 4606078 | 2016-11-14 16:44:38 -0800 | [diff] [blame] | 301 | # First use APIs to check profile connection state |
Stanley Tng | c9a123d | 2017-12-05 17:37:49 -0800 | [diff] [blame] | 302 | while (time.time() < end_time |
| 303 | and not profile_connected.issuperset(profiles_set)): |
| 304 | if (bt_profile_constants['headset_client'] not in profile_connected |
| 305 | and bt_profile_constants['headset_client'] in profiles_set): |
tturney | 4606078 | 2016-11-14 16:44:38 -0800 | [diff] [blame] | 306 | if is_hfp_client_device_connected(pri_ad, sec_addr): |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 307 | profile_connected.add(bt_profile_constants['headset_client']) |
Stanley Tng | c9a123d | 2017-12-05 17:37:49 -0800 | [diff] [blame] | 308 | if (bt_profile_constants['a2dp'] not in profile_connected |
| 309 | and bt_profile_constants['a2dp'] in profiles_set): |
tturney | 4606078 | 2016-11-14 16:44:38 -0800 | [diff] [blame] | 310 | if is_a2dp_src_device_connected(pri_ad, sec_addr): |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 311 | profile_connected.add(bt_profile_constants['a2dp']) |
Stanley Tng | c9a123d | 2017-12-05 17:37:49 -0800 | [diff] [blame] | 312 | if (bt_profile_constants['a2dp_sink'] not in profile_connected |
| 313 | and bt_profile_constants['a2dp_sink'] in profiles_set): |
tturney | 4606078 | 2016-11-14 16:44:38 -0800 | [diff] [blame] | 314 | if is_a2dp_snk_device_connected(pri_ad, sec_addr): |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 315 | profile_connected.add(bt_profile_constants['a2dp_sink']) |
Stanley Tng | c9a123d | 2017-12-05 17:37:49 -0800 | [diff] [blame] | 316 | if (bt_profile_constants['map_mce'] not in profile_connected |
| 317 | and bt_profile_constants['map_mce'] in profiles_set): |
tturney | 4606078 | 2016-11-14 16:44:38 -0800 | [diff] [blame] | 318 | if is_map_mce_device_connected(pri_ad, sec_addr): |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 319 | profile_connected.add(bt_profile_constants['map_mce']) |
Stanley Tng | c9a123d | 2017-12-05 17:37:49 -0800 | [diff] [blame] | 320 | if (bt_profile_constants['map'] not in profile_connected |
| 321 | and bt_profile_constants['map'] in profiles_set): |
tturney | 4606078 | 2016-11-14 16:44:38 -0800 | [diff] [blame] | 322 | if is_map_mse_device_connected(pri_ad, sec_addr): |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 323 | profile_connected.add(bt_profile_constants['map']) |
tturney | 4606078 | 2016-11-14 16:44:38 -0800 | [diff] [blame] | 324 | time.sleep(0.1) |
| 325 | # If APIs fail, try to find the connection broadcast receiver. |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 326 | while not profile_connected.issuperset(profiles_set): |
| 327 | try: |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 328 | profile_event = pri_ad.ed.pop_event( |
tturney | 4606078 | 2016-11-14 16:44:38 -0800 | [diff] [blame] | 329 | bluetooth_profile_connection_state_changed, |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 330 | bt_default_timeout + 10) |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 331 | pri_ad.log.info("Got event {}".format(profile_event)) |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 332 | except Exception: |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 333 | pri_ad.log.error("Did not get {} profiles left {}".format( |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 334 | bluetooth_profile_connection_state_changed, profile_connected)) |
| 335 | return False |
| 336 | |
| 337 | profile = profile_event['data']['profile'] |
| 338 | state = profile_event['data']['state'] |
| 339 | device_addr = profile_event['data']['addr'] |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 340 | if state == bt_profile_states['connected'] and \ |
Timofey Protopopov | a47c881 | 2018-03-28 11:37:31 -0700 | [diff] [blame] | 341 | device_addr == sec_ad.droid.bluetoothGetLocalAddress(): |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 342 | profile_connected.add(profile) |
Stanley Tng | c9a123d | 2017-12-05 17:37:49 -0800 | [diff] [blame] | 343 | pri_ad.log.info( |
| 344 | "Profiles connected until now {}".format(profile_connected)) |
Sanket Agarwal | d09c7d8 | 2016-05-17 20:24:51 -0700 | [diff] [blame] | 345 | # Failure happens inside the while loop. If we came here then we already |
| 346 | # connected. |
| 347 | return True |
Ang Li | 73697b3 | 2015-12-03 00:41:53 +0000 | [diff] [blame] | 348 | |
tturney | 57eb93a | 2016-06-21 13:58:06 -0700 | [diff] [blame] | 349 | |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 350 | def determine_max_advertisements(android_device): |
| 351 | """Determines programatically how many advertisements the Android device |
| 352 | supports. |
| 353 | |
| 354 | Args: |
| 355 | android_device: The Android device to determine max advertisements of. |
| 356 | |
| 357 | Returns: |
| 358 | The maximum advertisement count. |
| 359 | """ |
| 360 | android_device.log.info( |
| 361 | "Determining number of maximum concurrent advertisements...") |
| 362 | advertisement_count = 0 |
| 363 | bt_enabled = False |
| 364 | expected_bluetooth_on_event_name = bluetooth_on |
| 365 | if not android_device.droid.bluetoothCheckState(): |
| 366 | android_device.droid.bluetoothToggleState(True) |
| 367 | try: |
| 368 | android_device.ed.pop_event(expected_bluetooth_on_event_name, |
| 369 | bt_default_timeout) |
| 370 | except Exception: |
| 371 | android_device.log.info( |
| 372 | "Failed to toggle Bluetooth on(no broadcast received).") |
| 373 | # Try one more time to poke at the actual state. |
| 374 | if android_device.droid.bluetoothCheckState() is True: |
| 375 | android_device.log.info(".. actual state is ON") |
| 376 | else: |
| 377 | android_device.log.error( |
| 378 | "Failed to turn Bluetooth on. Setting default advertisements to 1" |
| 379 | ) |
| 380 | advertisement_count = -1 |
| 381 | return advertisement_count |
| 382 | advertise_callback_list = [] |
| 383 | advertise_data = android_device.droid.bleBuildAdvertiseData() |
| 384 | advertise_settings = android_device.droid.bleBuildAdvertiseSettings() |
| 385 | while (True): |
| 386 | advertise_callback = android_device.droid.bleGenBleAdvertiseCallback() |
| 387 | advertise_callback_list.append(advertise_callback) |
| 388 | |
| 389 | android_device.droid.bleStartBleAdvertising( |
| 390 | advertise_callback, advertise_data, advertise_settings) |
| 391 | |
| 392 | regex = "(" + adv_succ.format( |
| 393 | advertise_callback) + "|" + adv_fail.format( |
| 394 | advertise_callback) + ")" |
| 395 | # wait for either success or failure event |
| 396 | evt = android_device.ed.pop_events(regex, bt_default_timeout, |
| 397 | small_timeout) |
| 398 | if evt[0]["name"] == adv_succ.format(advertise_callback): |
| 399 | advertisement_count += 1 |
| 400 | android_device.log.info( |
| 401 | "Advertisement {} started.".format(advertisement_count)) |
| 402 | else: |
| 403 | error = evt[0]["data"]["Error"] |
| 404 | if error == "ADVERTISE_FAILED_TOO_MANY_ADVERTISERS": |
| 405 | android_device.log.info( |
| 406 | "Advertisement failed to start. Reached max " + |
| 407 | "advertisements at {}".format(advertisement_count)) |
| 408 | break |
| 409 | else: |
| 410 | raise BtTestUtilsError( |
| 411 | "Expected ADVERTISE_FAILED_TOO_MANY_ADVERTISERS," + |
| 412 | " but received bad error code {}".format(error)) |
| 413 | try: |
| 414 | for adv in advertise_callback_list: |
| 415 | android_device.droid.bleStopBleAdvertising(adv) |
| 416 | except Exception: |
| 417 | android_device.log.error( |
| 418 | "Failed to stop advertisingment, resetting Bluetooth.") |
| 419 | reset_bluetooth([android_device]) |
| 420 | return advertisement_count |
| 421 | |
| 422 | |
| 423 | def disable_bluetooth(droid): |
| 424 | """Disable Bluetooth on input Droid object. |
| 425 | |
| 426 | Args: |
| 427 | droid: The droid object to disable Bluetooth on. |
| 428 | |
| 429 | Returns: |
| 430 | True if successful, false if unsuccessful. |
| 431 | """ |
| 432 | if droid.bluetoothCheckState() is True: |
| 433 | droid.bluetoothToggleState(False) |
| 434 | if droid.bluetoothCheckState() is True: |
| 435 | log.error("Failed to toggle Bluetooth off.") |
| 436 | return False |
| 437 | return True |
| 438 | |
| 439 | |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 440 | def disconnect_pri_from_sec(pri_ad, sec_ad, profiles_list): |
Ram Periathiruvadi | e3f9a70 | 2016-07-26 17:41:59 -0700 | [diff] [blame] | 441 | """ |
| 442 | Disconnect primary from secondary on a specific set of profiles |
| 443 | Args: |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 444 | pri_ad - Primary android_device initiating disconnection |
| 445 | sec_ad - Secondary android droid (sl4a interface to keep the |
| 446 | method signature the same connect_pri_to_sec above) |
Ram Periathiruvadi | e3f9a70 | 2016-07-26 17:41:59 -0700 | [diff] [blame] | 447 | profiles_list - List of profiles we want to disconnect from |
| 448 | |
| 449 | Returns: |
| 450 | True on Success |
| 451 | False on Failure |
| 452 | """ |
| 453 | # Sanity check to see if all the profiles in the given set is supported |
tturney | ca42e2e | 2017-08-30 10:22:54 -0700 | [diff] [blame] | 454 | supported_profiles = bt_profile_constants.values() |
Ram Periathiruvadi | e3f9a70 | 2016-07-26 17:41:59 -0700 | [diff] [blame] | 455 | for profile in profiles_list: |
| 456 | if profile not in supported_profiles: |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 457 | pri_ad.log.info("Profile {} is not in supported list {}".format( |
Ram Periathiruvadi | e3f9a70 | 2016-07-26 17:41:59 -0700 | [diff] [blame] | 458 | profile, supported_profiles)) |
| 459 | return False |
| 460 | |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 461 | pri_ad.log.info(pri_ad.droid.bluetoothGetBondedDevices()) |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 462 | # Disconnecting on a already disconnected profile is a nop, |
| 463 | # so not checking for the connection state |
| 464 | try: |
| 465 | pri_ad.droid.bluetoothDisconnectConnectedProfile( |
| 466 | sec_ad.droid.bluetoothGetLocalAddress(), profiles_list) |
| 467 | except Exception as err: |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 468 | pri_ad.log.error( |
| 469 | "Exception while trying to disconnect profile(s) {}: {}".format( |
| 470 | profiles_list, err)) |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 471 | return False |
| 472 | |
Ram Periathiruvadi | e3f9a70 | 2016-07-26 17:41:59 -0700 | [diff] [blame] | 473 | profile_disconnected = set() |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 474 | pri_ad.log.info("Disconnecting from profiles: {}".format(profiles_list)) |
Ram Periathiruvadi | e3f9a70 | 2016-07-26 17:41:59 -0700 | [diff] [blame] | 475 | |
| 476 | while not profile_disconnected.issuperset(profiles_list): |
| 477 | try: |
Phillip Walker | 827112a | 2016-09-08 16:27:19 -0700 | [diff] [blame] | 478 | profile_event = pri_ad.ed.pop_event( |
Timofey Protopopov | a47c881 | 2018-03-28 11:37:31 -0700 | [diff] [blame] | 479 | bluetooth_profile_connection_state_changed, bt_default_timeout) |
tturney | 34ee54d | 2016-11-16 15:29:02 -0800 | [diff] [blame] | 480 | pri_ad.log.info("Got event {}".format(profile_event)) |
Timofey Protopopov | a47c881 | 2018-03-28 11:37:31 -0700 | [diff] [blame] | 481 | except Exception as e: |
Tom Turney | 9bce15d | 2018-06-21 13:00:42 -0700 | [diff] [blame] | 482 | pri_ad.log.error( |
| 483 | "Did not disconnect from Profiles. Reason {}".format(e)) |
Ram Periathiruvadi | e3f9a70 | 2016-07-26 17:41:59 -0700 | [diff] [blame] | 484 | return False |
| 485 | |
| 486 | profile = profile_event['data']['profile'] |
| 487 | state = profile_event['data']['state'] |
| 488 | device_addr = profile_event['data']['addr'] |
| 489 | |
tturney | ec1b8f5 | 2017-07-26 07:35:06 -0700 | [diff] [blame] | 490 | if state == bt_profile_states['disconnected'] and \ |
Timofey Protopopov | a47c881 | 2018-03-28 11:37:31 -0700 | [diff] [blame] | 491 | device_addr == sec_ad.droid.bluetoothGetLocalAddress(): |
Ram Periathiruvadi | e3f9a70 | 2016-07-26 17:41:59 -0700 | [diff] [blame] | 492 | profile_disconnected.add(profile) |
Stanley Tng | c9a123d | 2017-12-05 17:37:49 -0800 | [diff] [blame] | 493 | pri_ad.log.info( |
| 494 | "Profiles disconnected so far {}".format(profile_disconnected)) |
Ram Periathiruvadi | e3f9a70 | 2016-07-26 17:41:59 -0700 | [diff] [blame] | 495 | |
| 496 | return True |
| 497 | |
| 498 | |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 499 | def enable_bluetooth(droid, ed): |
| 500 | if droid.bluetoothCheckState() is True: |
| 501 | return True |
tturney | ed24997 | 2016-09-13 11:07:49 -0700 | [diff] [blame] | 502 | |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 503 | droid.bluetoothToggleState(True) |
| 504 | expected_bluetooth_on_event_name = bluetooth_on |
tturney | 5d7a1fc | 2017-06-01 15:12:12 -0700 | [diff] [blame] | 505 | try: |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 506 | ed.pop_event(expected_bluetooth_on_event_name, bt_default_timeout) |
| 507 | except Exception: |
| 508 | log.info("Failed to toggle Bluetooth on (no broadcast received)") |
| 509 | if droid.bluetoothCheckState() is True: |
| 510 | log.info(".. actual state is ON") |
Timofey Protopopov | 2651b74 | 2018-04-09 16:43:49 -0700 | [diff] [blame] | 511 | return True |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 512 | log.info(".. actual state is OFF") |
| 513 | return False |
| 514 | |
| 515 | return True |
| 516 | |
| 517 | |
| 518 | def factory_reset_bluetooth(android_devices): |
| 519 | """Clears Bluetooth stack of input Android device list. |
| 520 | |
| 521 | Args: |
| 522 | android_devices: The Android device list to reset Bluetooth |
| 523 | |
| 524 | Returns: |
| 525 | True if successful, false if unsuccessful. |
| 526 | """ |
| 527 | for a in android_devices: |
| 528 | droid, ed = a.droid, a.ed |
| 529 | a.log.info("Reset state of bluetooth on device.") |
| 530 | if not bluetooth_enabled_check(a): |
| 531 | return False |
| 532 | # TODO: remove device unbond b/79418045 |
| 533 | # Temporary solution to ensure all devices are unbonded |
| 534 | bonded_devices = droid.bluetoothGetBondedDevices() |
| 535 | for b in bonded_devices: |
| 536 | a.log.info("Removing bond for device {}".format(b['address'])) |
| 537 | droid.bluetoothUnbond(b['address']) |
| 538 | |
| 539 | droid.bluetoothFactoryReset() |
| 540 | wait_for_bluetooth_manager_state(droid) |
| 541 | if not enable_bluetooth(droid, ed): |
| 542 | return False |
| 543 | return True |
| 544 | |
| 545 | |
| 546 | def generate_ble_advertise_objects(droid): |
| 547 | """Generate generic LE advertise objects. |
| 548 | |
| 549 | Args: |
| 550 | droid: The droid object to generate advertise LE objects from. |
| 551 | |
| 552 | Returns: |
| 553 | advertise_callback: The generated advertise callback id. |
| 554 | advertise_data: The generated advertise data id. |
| 555 | advertise_settings: The generated advertise settings id. |
| 556 | """ |
| 557 | advertise_callback = droid.bleGenBleAdvertiseCallback() |
| 558 | advertise_data = droid.bleBuildAdvertiseData() |
| 559 | advertise_settings = droid.bleBuildAdvertiseSettings() |
| 560 | return advertise_callback, advertise_data, advertise_settings |
| 561 | |
| 562 | |
| 563 | def generate_ble_scan_objects(droid): |
| 564 | """Generate generic LE scan objects. |
| 565 | |
| 566 | Args: |
| 567 | droid: The droid object to generate LE scan objects from. |
| 568 | |
| 569 | Returns: |
| 570 | filter_list: The generated scan filter list id. |
| 571 | scan_settings: The generated scan settings id. |
| 572 | scan_callback: The generated scan callback id. |
| 573 | """ |
| 574 | filter_list = droid.bleGenFilterList() |
| 575 | scan_settings = droid.bleBuildScanSetting() |
| 576 | scan_callback = droid.bleGenScanCallback() |
| 577 | return filter_list, scan_settings, scan_callback |
| 578 | |
| 579 | |
| 580 | def generate_id_by_size( |
| 581 | size, |
| 582 | chars=( |
| 583 | string.ascii_lowercase + string.ascii_uppercase + string.digits)): |
| 584 | """Generate random ascii characters of input size and input char types |
| 585 | |
| 586 | Args: |
| 587 | size: Input size of string. |
| 588 | chars: (Optional) Chars to use in generating a random string. |
| 589 | |
| 590 | Returns: |
| 591 | String of random input chars at the input size. |
| 592 | """ |
| 593 | return ''.join(random.choice(chars) for _ in range(size)) |
| 594 | |
| 595 | |
| 596 | def get_advanced_droid_list(android_devices): |
| 597 | """Add max_advertisement and batch_scan_supported attributes to input |
| 598 | Android devices |
| 599 | |
| 600 | This will programatically determine maximum LE advertisements of each |
| 601 | input Android device. |
| 602 | |
| 603 | Args: |
| 604 | android_devices: The Android devices to setup. |
| 605 | |
| 606 | Returns: |
| 607 | List of Android devices with new attribtues. |
| 608 | """ |
| 609 | droid_list = [] |
| 610 | for a in android_devices: |
| 611 | d, e = a.droid, a.ed |
| 612 | model = d.getBuildModel() |
| 613 | max_advertisements = 1 |
| 614 | batch_scan_supported = True |
| 615 | if model in advertisements_to_devices.keys(): |
| 616 | max_advertisements = advertisements_to_devices[model] |
| 617 | else: |
| 618 | max_advertisements = determine_max_advertisements(a) |
| 619 | max_tries = 3 |
| 620 | # Retry to calculate max advertisements |
| 621 | while max_advertisements == -1 and max_tries > 0: |
| 622 | a.log.info( |
| 623 | "Attempts left to determine max advertisements: {}".format( |
| 624 | max_tries)) |
| 625 | max_advertisements = determine_max_advertisements(a) |
| 626 | max_tries -= 1 |
| 627 | advertisements_to_devices[model] = max_advertisements |
| 628 | if model in batch_scan_not_supported_list: |
| 629 | batch_scan_supported = False |
| 630 | role = { |
| 631 | 'droid': d, |
| 632 | 'ed': e, |
| 633 | 'max_advertisements': max_advertisements, |
| 634 | 'batch_scan_supported': batch_scan_supported |
| 635 | } |
| 636 | droid_list.append(role) |
| 637 | return droid_list |
| 638 | |
| 639 | |
| 640 | def get_bluetooth_crash_count(android_device): |
| 641 | out = android_device.adb.shell("dumpsys bluetooth_manager") |
| 642 | return int(re.search("crashed(.*\d)", out).group(1)) |
Tom Turney | f5b4214 | 2018-06-22 14:01:27 -0700 | [diff] [blame] | 643 | |
| 644 | |
Tom Turney | 9bce15d | 2018-06-21 13:00:42 -0700 | [diff] [blame] | 645 | def get_device_selector_dictionary(android_device_list): |
| 646 | """Create a dictionary of Bluetooth features vs Android devices. |
| 647 | |
| 648 | Args: |
| 649 | android_device_list: The list of Android devices. |
| 650 | Returns: |
| 651 | A dictionary of profiles/features to Android devices. |
| 652 | """ |
| 653 | selector_dict = {} |
| 654 | for ad in android_device_list: |
| 655 | uuids = ad.droid.bluetoothGetLocalUuids() |
| 656 | |
| 657 | for profile, uuid_const in sig_uuid_constants.items(): |
| 658 | uuid_check = sig_uuid_constants['BASE_UUID'].format( |
| 659 | uuid_const).lower() |
Aidan Holloway-bidwell | 9e44b9d | 2018-11-07 14:33:27 -0800 | [diff] [blame] | 660 | if uuids and uuid_check in uuids: |
Tom Turney | 9bce15d | 2018-06-21 13:00:42 -0700 | [diff] [blame] | 661 | if profile in selector_dict: |
| 662 | selector_dict[profile].append(ad) |
| 663 | else: |
| 664 | selector_dict[profile] = [ad] |
| 665 | |
| 666 | # Various services may not be active during BT startup. |
| 667 | # If the device can be identified through adb shell pm list features |
| 668 | # then try to add them to the appropriate profiles / features. |
| 669 | |
| 670 | # Android TV. |
| 671 | if "feature:com.google.android.tv.installed" in ad.features: |
| 672 | ad.log.info("Android TV device found.") |
| 673 | supported_profiles = ['AudioSink'] |
| 674 | _add_android_device_to_dictionary(ad, supported_profiles, |
| 675 | selector_dict) |
| 676 | |
| 677 | # Android Auto |
| 678 | elif "feature:android.hardware.type.automotive" in ad.features: |
| 679 | ad.log.info("Android Auto device found.") |
| 680 | # Add: AudioSink , A/V_RemoteControl, |
| 681 | supported_profiles = [ |
| 682 | 'AudioSink', 'A/V_RemoteControl', 'Message Notification Server' |
| 683 | ] |
| 684 | _add_android_device_to_dictionary(ad, supported_profiles, |
| 685 | selector_dict) |
| 686 | # Android Wear |
| 687 | elif "feature:android.hardware.type.watch" in ad.features: |
| 688 | ad.log.info("Android Wear device found.") |
| 689 | supported_profiles = [] |
| 690 | _add_android_device_to_dictionary(ad, supported_profiles, |
| 691 | selector_dict) |
| 692 | # Android Phone |
| 693 | elif "feature:android.hardware.telephony" in ad.features: |
| 694 | ad.log.info("Android Phone device found.") |
| 695 | # Add: AudioSink |
| 696 | supported_profiles = [ |
| 697 | 'AudioSource', 'A/V_RemoteControlTarget', |
| 698 | 'Message Access Server' |
| 699 | ] |
| 700 | _add_android_device_to_dictionary(ad, supported_profiles, |
| 701 | selector_dict) |
| 702 | return selector_dict |
| 703 | |
| 704 | |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 705 | def get_mac_address_of_generic_advertisement(scan_ad, adv_ad): |
| 706 | """Start generic advertisement and get it's mac address by LE scanning. |
Tom Turney | 9bce15d | 2018-06-21 13:00:42 -0700 | [diff] [blame] | 707 | |
| 708 | Args: |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 709 | scan_ad: The Android device to use as the scanner. |
| 710 | adv_ad: The Android device to use as the advertiser. |
| 711 | |
| 712 | Returns: |
| 713 | mac_address: The mac address of the advertisement. |
| 714 | advertise_callback: The advertise callback id of the active |
| 715 | advertisement. |
Tom Turney | 9bce15d | 2018-06-21 13:00:42 -0700 | [diff] [blame] | 716 | """ |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 717 | adv_ad.droid.bleSetAdvertiseDataIncludeDeviceName(True) |
| 718 | adv_ad.droid.bleSetAdvertiseSettingsAdvertiseMode( |
| 719 | ble_advertise_settings_modes['low_latency']) |
| 720 | adv_ad.droid.bleSetAdvertiseSettingsIsConnectable(True) |
| 721 | adv_ad.droid.bleSetAdvertiseSettingsTxPowerLevel( |
| 722 | ble_advertise_settings_tx_powers['high']) |
| 723 | advertise_callback, advertise_data, advertise_settings = ( |
| 724 | generate_ble_advertise_objects(adv_ad.droid)) |
| 725 | adv_ad.droid.bleStartBleAdvertising(advertise_callback, advertise_data, |
| 726 | advertise_settings) |
| 727 | try: |
| 728 | adv_ad.ed.pop_event( |
| 729 | adv_succ.format(advertise_callback), bt_default_timeout) |
| 730 | except Empty as err: |
| 731 | raise BtTestUtilsError( |
| 732 | "Advertiser did not start successfully {}".format(err)) |
| 733 | filter_list = scan_ad.droid.bleGenFilterList() |
| 734 | scan_settings = scan_ad.droid.bleBuildScanSetting() |
| 735 | scan_callback = scan_ad.droid.bleGenScanCallback() |
| 736 | scan_ad.droid.bleSetScanFilterDeviceName( |
| 737 | adv_ad.droid.bluetoothGetLocalName()) |
| 738 | scan_ad.droid.bleBuildScanFilter(filter_list) |
| 739 | scan_ad.droid.bleStartBleScan(filter_list, scan_settings, scan_callback) |
| 740 | try: |
| 741 | event = scan_ad.ed.pop_event( |
| 742 | "BleScan{}onScanResults".format(scan_callback), bt_default_timeout) |
| 743 | except Empty as err: |
| 744 | raise BtTestUtilsError( |
| 745 | "Scanner did not find advertisement {}".format(err)) |
| 746 | mac_address = event['data']['Result']['deviceInfo']['address'] |
| 747 | return mac_address, advertise_callback, scan_callback |
| 748 | |
| 749 | |
| 750 | def hid_device_send_key_data_report(host_id, device_ad, key, interval=1): |
| 751 | """Send a HID report simulating a 1-second keyboard press from host_ad to |
| 752 | device_ad |
| 753 | |
| 754 | Args: |
| 755 | host_id: the Bluetooth MAC address or name of the HID host |
| 756 | device_ad: HID device |
| 757 | key: the key we want to send |
| 758 | interval: the interval between key press and key release |
| 759 | """ |
| 760 | device_ad.droid.bluetoothHidDeviceSendReport(host_id, hid_id_keyboard, |
| 761 | hid_keyboard_report(key)) |
| 762 | time.sleep(interval) |
| 763 | device_ad.droid.bluetoothHidDeviceSendReport(host_id, hid_id_keyboard, |
| 764 | hid_keyboard_report("00")) |
| 765 | |
| 766 | |
| 767 | def hid_keyboard_report(key, modifier="00"): |
| 768 | """Get the HID keyboard report for the given key |
| 769 | |
| 770 | Args: |
| 771 | key: the key we want |
| 772 | modifier: HID keyboard modifier bytes |
| 773 | Returns: |
| 774 | The byte array for the HID report. |
| 775 | """ |
| 776 | return str( |
| 777 | bytearray.fromhex(" ".join( |
| 778 | [modifier, "00", key, "00", "00", "00", "00", "00"])), "utf-8") |
| 779 | |
| 780 | |
| 781 | def is_a2dp_connected(sink, source): |
| 782 | """ |
| 783 | Convenience Function to see if the 2 devices are connected on |
| 784 | A2dp. |
| 785 | Args: |
| 786 | sink: Audio Sink |
| 787 | source: Audio Source |
| 788 | Returns: |
| 789 | True if Connected |
| 790 | False if Not connected |
| 791 | """ |
| 792 | |
| 793 | devices = sink.droid.bluetoothA2dpSinkGetConnectedDevices() |
| 794 | for device in devices: |
| 795 | sink.log.info("A2dp Connected device {}".format(device["name"])) |
| 796 | if (device["address"] == source.droid.bluetoothGetLocalAddress()): |
| 797 | return True |
| 798 | return False |
| 799 | |
| 800 | |
| 801 | def is_a2dp_snk_device_connected(ad, addr): |
| 802 | """Determines if an AndroidDevice has A2DP snk connectivity to input address |
| 803 | |
| 804 | Args: |
| 805 | ad: the Android device |
| 806 | addr: the address that's expected |
| 807 | Returns: |
| 808 | True if connection was successful, false if unsuccessful. |
| 809 | """ |
| 810 | devices = ad.droid.bluetoothA2dpSinkGetConnectedDevices() |
| 811 | ad.log.info("Connected A2DP Sink devices: {}".format(devices)) |
| 812 | if addr in {d['address'] for d in devices}: |
| 813 | return True |
| 814 | return False |
| 815 | |
| 816 | |
| 817 | def is_a2dp_src_device_connected(ad, addr): |
| 818 | """Determines if an AndroidDevice has A2DP connectivity to input address |
| 819 | |
| 820 | Args: |
| 821 | ad: the Android device |
| 822 | addr: the address that's expected |
| 823 | Returns: |
| 824 | True if connection was successful, false if unsuccessful. |
| 825 | """ |
| 826 | devices = ad.droid.bluetoothA2dpGetConnectedDevices() |
| 827 | ad.log.info("Connected A2DP Source devices: {}".format(devices)) |
| 828 | if addr in {d['address'] for d in devices}: |
| 829 | return True |
| 830 | return False |
| 831 | |
| 832 | |
| 833 | def is_hfp_client_device_connected(ad, addr): |
| 834 | """Determines if an AndroidDevice has HFP connectivity to input address |
| 835 | |
| 836 | Args: |
| 837 | ad: the Android device |
| 838 | addr: the address that's expected |
| 839 | Returns: |
| 840 | True if connection was successful, false if unsuccessful. |
| 841 | """ |
| 842 | devices = ad.droid.bluetoothHfpClientGetConnectedDevices() |
| 843 | ad.log.info("Connected HFP Client devices: {}".format(devices)) |
| 844 | if addr in {d['address'] for d in devices}: |
| 845 | return True |
| 846 | return False |
| 847 | |
| 848 | |
| 849 | def is_map_mce_device_connected(ad, addr): |
| 850 | """Determines if an AndroidDevice has MAP MCE connectivity to input address |
| 851 | |
| 852 | Args: |
| 853 | ad: the Android device |
| 854 | addr: the address that's expected |
| 855 | Returns: |
| 856 | True if connection was successful, false if unsuccessful. |
| 857 | """ |
| 858 | devices = ad.droid.bluetoothMapClientGetConnectedDevices() |
| 859 | ad.log.info("Connected MAP MCE devices: {}".format(devices)) |
| 860 | if addr in {d['address'] for d in devices}: |
| 861 | return True |
| 862 | return False |
| 863 | |
| 864 | |
| 865 | def is_map_mse_device_connected(ad, addr): |
| 866 | """Determines if an AndroidDevice has MAP MSE connectivity to input address |
| 867 | |
| 868 | Args: |
| 869 | ad: the Android device |
| 870 | addr: the address that's expected |
| 871 | Returns: |
| 872 | True if connection was successful, false if unsuccessful. |
| 873 | """ |
| 874 | devices = ad.droid.bluetoothMapGetConnectedDevices() |
| 875 | ad.log.info("Connected MAP MSE devices: {}".format(devices)) |
| 876 | if addr in {d['address'] for d in devices}: |
| 877 | return True |
| 878 | return False |
| 879 | |
| 880 | |
| 881 | def kill_bluetooth_process(ad): |
| 882 | """Kill Bluetooth process on Android device. |
| 883 | |
| 884 | Args: |
| 885 | ad: Android device to kill BT process on. |
| 886 | """ |
| 887 | ad.log.info("Killing Bluetooth process.") |
| 888 | pid = ad.adb.shell( |
| 889 | "ps | grep com.android.bluetooth | awk '{print $2}'").decode('ascii') |
| 890 | call(["adb -s " + ad.serial + " shell kill " + pid], shell=True) |
| 891 | |
| 892 | |
| 893 | def log_energy_info(android_devices, state): |
| 894 | """Logs energy info of input Android devices. |
| 895 | |
| 896 | Args: |
| 897 | android_devices: input Android device list to log energy info from. |
| 898 | state: the input state to log. Usually 'Start' or 'Stop' for logging. |
| 899 | |
| 900 | Returns: |
| 901 | A logging string of the Bluetooth energy info reported. |
| 902 | """ |
| 903 | return_string = "{} Energy info collection:\n".format(state) |
| 904 | # Bug: b/31966929 |
| 905 | return return_string |
| 906 | |
| 907 | |
| 908 | def orchestrate_and_verify_pan_connection(pan_dut, panu_dut): |
| 909 | """Setups up a PAN conenction between two android devices. |
| 910 | |
| 911 | Args: |
| 912 | pan_dut: the Android device providing tethering services |
| 913 | panu_dut: the Android device using the internet connection from the |
| 914 | pan_dut |
| 915 | Returns: |
| 916 | True if PAN connection and verification is successful, |
| 917 | false if unsuccessful. |
| 918 | """ |
| 919 | if not toggle_airplane_mode_by_adb(log, panu_dut, True): |
| 920 | panu_dut.log.error("Failed to toggle airplane mode on") |
| 921 | return False |
| 922 | if not toggle_airplane_mode_by_adb(log, panu_dut, False): |
| 923 | pan_dut.log.error("Failed to toggle airplane mode off") |
| 924 | return False |
| 925 | pan_dut.droid.bluetoothStartConnectionStateChangeMonitor("") |
| 926 | panu_dut.droid.bluetoothStartConnectionStateChangeMonitor("") |
| 927 | if not bluetooth_enabled_check(panu_dut): |
| 928 | return False |
| 929 | if not bluetooth_enabled_check(pan_dut): |
| 930 | return False |
| 931 | pan_dut.droid.bluetoothPanSetBluetoothTethering(True) |
| 932 | if not (pair_pri_to_sec(pan_dut, panu_dut)): |
| 933 | return False |
| 934 | if not pan_dut.droid.bluetoothPanIsTetheringOn(): |
| 935 | pan_dut.log.error("Failed to enable Bluetooth tethering.") |
| 936 | return False |
| 937 | # Magic sleep needed to give the stack time in between bonding and |
| 938 | # connecting the PAN profile. |
| 939 | time.sleep(pan_connect_timeout) |
| 940 | panu_dut.droid.bluetoothConnectBonded( |
| 941 | pan_dut.droid.bluetoothGetLocalAddress()) |
| 942 | if not verify_http_connection(log, panu_dut): |
| 943 | panu_dut.log.error("Can't verify http connection on PANU device.") |
| 944 | if not verify_http_connection(log, pan_dut): |
| 945 | pan_dut.log.info( |
| 946 | "Can't verify http connection on PAN service device") |
| 947 | return False |
| 948 | return True |
| 949 | |
| 950 | |
| 951 | def orchestrate_bluetooth_socket_connection( |
| 952 | client_ad, |
| 953 | server_ad, |
| 954 | accept_timeout_ms=default_bluetooth_socket_timeout_ms, |
| 955 | uuid=None): |
| 956 | """Sets up the Bluetooth Socket connection between two Android devices. |
| 957 | |
| 958 | Args: |
| 959 | client_ad: the Android device performing the connection. |
| 960 | server_ad: the Android device accepting the connection. |
| 961 | Returns: |
| 962 | True if connection was successful, false if unsuccessful. |
| 963 | """ |
| 964 | server_ad.droid.bluetoothStartPairingHelper() |
| 965 | client_ad.droid.bluetoothStartPairingHelper() |
| 966 | |
| 967 | server_ad.droid.bluetoothSocketConnBeginAcceptThreadUuid( |
| 968 | (bluetooth_socket_conn_test_uuid |
| 969 | if uuid is None else uuid), accept_timeout_ms) |
| 970 | client_ad.droid.bluetoothSocketConnBeginConnectThreadUuid( |
| 971 | server_ad.droid.bluetoothGetLocalAddress(), |
| 972 | (bluetooth_socket_conn_test_uuid if uuid is None else uuid)) |
| 973 | |
| 974 | end_time = time.time() + bt_default_timeout |
| 975 | result = False |
| 976 | test_result = True |
| 977 | while time.time() < end_time: |
| 978 | if len(client_ad.droid.bluetoothSocketConnActiveConnections()) > 0: |
| 979 | test_result = True |
| 980 | client_ad.log.info("Bluetooth socket Client Connection Active") |
| 981 | break |
Tom Turney | 9bce15d | 2018-06-21 13:00:42 -0700 | [diff] [blame] | 982 | else: |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 983 | test_result = False |
| 984 | time.sleep(1) |
| 985 | if not test_result: |
| 986 | client_ad.log.error( |
| 987 | "Failed to establish a Bluetooth socket connection") |
| 988 | return False |
| 989 | return True |
tprotopopov | 2e18bbb | 2018-10-02 10:29:01 -0700 | [diff] [blame] | 990 | |
Aidan Holloway-bidwell | f8f5151 | 2018-11-06 10:42:14 -0800 | [diff] [blame] | 991 | |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 992 | def orchestrate_rfcomm_connection(client_ad, |
| 993 | server_ad, |
| 994 | accept_timeout_ms=default_rfcomm_timeout_ms, |
| 995 | uuid=None): |
| 996 | """Sets up the RFCOMM connection between two Android devices. |
| 997 | |
| 998 | Args: |
| 999 | client_ad: the Android device performing the connection. |
| 1000 | server_ad: the Android device accepting the connection. |
| 1001 | Returns: |
| 1002 | True if connection was successful, false if unsuccessful. |
| 1003 | """ |
| 1004 | result = orchestrate_bluetooth_socket_connection( |
| 1005 | client_ad, server_ad, accept_timeout_ms, |
| 1006 | (bt_rfcomm_uuids['default_uuid'] if uuid is None else uuid)) |
| 1007 | |
| 1008 | return result |
| 1009 | |
| 1010 | |
| 1011 | def pair_pri_to_sec(pri_ad, sec_ad, attempts=2, auto_confirm=True): |
| 1012 | """Pairs pri droid to secondary droid. |
| 1013 | |
| 1014 | Args: |
| 1015 | pri_ad: Android device initiating connection |
| 1016 | sec_ad: Android device accepting connection |
| 1017 | attempts: Number of attempts to try until failure. |
| 1018 | auto_confirm: Auto confirm passkey match for both devices |
| 1019 | |
| 1020 | Returns: |
| 1021 | Pass if True |
| 1022 | Fail if False |
| 1023 | """ |
| 1024 | pri_ad.droid.bluetoothStartConnectionStateChangeMonitor( |
| 1025 | sec_ad.droid.bluetoothGetLocalAddress()) |
| 1026 | curr_attempts = 0 |
| 1027 | while curr_attempts < attempts: |
| 1028 | if _pair_pri_to_sec(pri_ad, sec_ad, auto_confirm): |
| 1029 | return True |
| 1030 | # Wait 2 seconds before unbound |
| 1031 | time.sleep(2) |
| 1032 | if not clear_bonded_devices(pri_ad): |
| 1033 | log.error("Failed to clear bond for primary device at attempt {}" |
| 1034 | .format(str(curr_attempts))) |
| 1035 | return False |
| 1036 | if not clear_bonded_devices(sec_ad): |
| 1037 | log.error("Failed to clear bond for secondary device at attempt {}" |
| 1038 | .format(str(curr_attempts))) |
| 1039 | return False |
| 1040 | # Wait 2 seconds after unbound |
| 1041 | time.sleep(2) |
| 1042 | curr_attempts += 1 |
| 1043 | log.error("pair_pri_to_sec failed to connect after {} attempts".format( |
| 1044 | str(attempts))) |
| 1045 | return False |
| 1046 | |
| 1047 | |
| 1048 | def _pair_pri_to_sec(pri_ad, sec_ad, auto_confirm): |
| 1049 | # Enable discovery on sec_ad so that pri_ad can find it. |
| 1050 | # The timeout here is based on how much time it would take for two devices |
| 1051 | # to pair with each other once pri_ad starts seeing devices. |
| 1052 | pri_droid = pri_ad.droid |
| 1053 | sec_droid = sec_ad.droid |
| 1054 | pri_ad.ed.clear_all_events() |
| 1055 | sec_ad.ed.clear_all_events() |
| 1056 | log.info("Bonding device {} to {}".format( |
| 1057 | pri_droid.bluetoothGetLocalAddress(), |
| 1058 | sec_droid.bluetoothGetLocalAddress())) |
| 1059 | sec_droid.bluetoothMakeDiscoverable(bt_default_timeout) |
| 1060 | target_address = sec_droid.bluetoothGetLocalAddress() |
| 1061 | log.debug("Starting paring helper on each device") |
| 1062 | pri_droid.bluetoothStartPairingHelper(auto_confirm) |
| 1063 | sec_droid.bluetoothStartPairingHelper(auto_confirm) |
| 1064 | pri_ad.log.info("Primary device starting discovery and executing bond") |
| 1065 | result = pri_droid.bluetoothDiscoverAndBond(target_address) |
| 1066 | if not auto_confirm: |
| 1067 | if not _wait_for_passkey_match(pri_ad, sec_ad): |
| 1068 | return False |
| 1069 | # Loop until we have bonded successfully or timeout. |
| 1070 | end_time = time.time() + bt_default_timeout |
| 1071 | pri_ad.log.info("Verifying devices are bonded") |
| 1072 | while time.time() < end_time: |
| 1073 | bonded_devices = pri_droid.bluetoothGetBondedDevices() |
| 1074 | bonded = False |
| 1075 | for d in bonded_devices: |
| 1076 | if d['address'] == target_address: |
| 1077 | pri_ad.log.info("Successfully bonded to device") |
| 1078 | return True |
| 1079 | time.sleep(0.1) |
| 1080 | # Timed out trying to bond. |
| 1081 | pri_ad.log.info("Failed to bond devices.") |
| 1082 | return False |
| 1083 | |
| 1084 | |
| 1085 | def reset_bluetooth(android_devices): |
| 1086 | """Resets Bluetooth state of input Android device list. |
| 1087 | |
| 1088 | Args: |
| 1089 | android_devices: The Android device list to reset Bluetooth state on. |
| 1090 | |
| 1091 | Returns: |
| 1092 | True if successful, false if unsuccessful. |
| 1093 | """ |
| 1094 | for a in android_devices: |
| 1095 | droid, ed = a.droid, a.ed |
| 1096 | a.log.info("Reset state of bluetooth on device.") |
| 1097 | if droid.bluetoothCheckState() is True: |
| 1098 | droid.bluetoothToggleState(False) |
| 1099 | expected_bluetooth_off_event_name = bluetooth_off |
| 1100 | try: |
| 1101 | ed.pop_event(expected_bluetooth_off_event_name, |
| 1102 | bt_default_timeout) |
| 1103 | except Exception: |
| 1104 | a.log.error("Failed to toggle Bluetooth off.") |
| 1105 | return False |
| 1106 | # temp sleep for b/17723234 |
| 1107 | time.sleep(3) |
| 1108 | if not bluetooth_enabled_check(a): |
| 1109 | return False |
| 1110 | return True |
| 1111 | |
| 1112 | |
| 1113 | def scan_and_verify_n_advertisements(scn_ad, max_advertisements): |
| 1114 | """Verify that input number of advertisements can be found from the scanning |
| 1115 | Android device. |
| 1116 | |
| 1117 | Args: |
| 1118 | scn_ad: The Android device to start LE scanning on. |
| 1119 | max_advertisements: The number of advertisements the scanner expects to |
| 1120 | find. |
| 1121 | |
| 1122 | Returns: |
| 1123 | True if successful, false if unsuccessful. |
| 1124 | """ |
| 1125 | test_result = False |
| 1126 | address_list = [] |
| 1127 | filter_list = scn_ad.droid.bleGenFilterList() |
| 1128 | scn_ad.droid.bleBuildScanFilter(filter_list) |
| 1129 | scan_settings = scn_ad.droid.bleBuildScanSetting() |
| 1130 | scan_callback = scn_ad.droid.bleGenScanCallback() |
| 1131 | scn_ad.droid.bleStartBleScan(filter_list, scan_settings, scan_callback) |
| 1132 | start_time = time.time() |
| 1133 | while (start_time + bt_default_timeout) > time.time(): |
| 1134 | event = None |
| 1135 | try: |
| 1136 | event = scn_ad.ed.pop_event( |
| 1137 | scan_result.format(scan_callback), bt_default_timeout) |
| 1138 | except Empty as error: |
| 1139 | raise BtTestUtilsError( |
| 1140 | "Failed to find scan event: {}".format(error)) |
| 1141 | address = event['data']['Result']['deviceInfo']['address'] |
| 1142 | if address not in address_list: |
| 1143 | address_list.append(address) |
| 1144 | if len(address_list) == max_advertisements: |
| 1145 | test_result = True |
| 1146 | break |
| 1147 | scn_ad.droid.bleStopBleScan(scan_callback) |
| 1148 | return test_result |
Aidan Holloway-bidwell | f8f5151 | 2018-11-06 10:42:14 -0800 | [diff] [blame] | 1149 | |
| 1150 | |
| 1151 | def set_bluetooth_codec( |
| 1152 | android_device, |
| 1153 | codec_type, |
| 1154 | sample_rate, |
| 1155 | bits_per_sample, |
| 1156 | channel_mode, |
| 1157 | codec_specific_1=0): |
| 1158 | """Sets the A2DP codec configuration on the AndroidDevice. |
| 1159 | |
| 1160 | Args: |
| 1161 | android_device (acts.controllers.android_device.AndroidDevice): the |
| 1162 | android device for which to switch the codec. |
| 1163 | codec_type (str): the desired codec type. Must be a key in |
| 1164 | bt_constants.codec_types. |
| 1165 | sample_rate (str): the desired sample rate. Must be a key in |
| 1166 | bt_constants.sample_rates. |
| 1167 | bits_per_sample (str): the desired bits per sample. Must be a key in |
| 1168 | bt_constants.bits_per_samples. |
| 1169 | channel_mode (str): the desired channel mode. Must be a key in |
| 1170 | bt_constants.channel_modes. |
| 1171 | codec_specific_1 (int): the desired bit rate (quality) for LDAC codec. |
| 1172 | Returns: |
| 1173 | bool: True if the codec config was successfully changed to the desired |
| 1174 | values. Else False. |
| 1175 | """ |
| 1176 | message = ( |
| 1177 | "Set Android Device A2DP Bluetooth codec configuration:\n" |
| 1178 | "\tCodec: {codec_type}\n" |
| 1179 | "\tSample Rate: {sample_rate}\n" |
| 1180 | "\tBits per Sample: {bits_per_sample}\n" |
| 1181 | "\tChannel Mode: {channel_mode}".format( |
| 1182 | codec_type=codec_type, |
| 1183 | sample_rate=sample_rate, |
| 1184 | bits_per_sample=bits_per_sample, |
| 1185 | channel_mode=channel_mode |
| 1186 | ) |
| 1187 | ) |
| 1188 | android_device.log.info(message) |
| 1189 | |
| 1190 | # Send SL4A command |
| 1191 | droid, ed = android_device.droid, android_device.ed |
| 1192 | if not droid.bluetoothA2dpSetCodecConfigPreference( |
| 1193 | codec_types[codec_type], |
| 1194 | sample_rates[str(sample_rate)], |
| 1195 | bits_per_samples[str(bits_per_sample)], |
| 1196 | channel_modes[channel_mode], |
| 1197 | codec_specific_1 |
| 1198 | ): |
Aidan Holloway-bidwell | 477807a | 2019-02-01 15:29:00 -0800 | [diff] [blame] | 1199 | android_device.log.warning("SL4A command returned False. Codec was not " |
| 1200 | "changed.") |
| 1201 | else: |
| 1202 | try: |
| 1203 | ed.pop_event(bluetooth_a2dp_codec_config_changed, |
| 1204 | bt_default_timeout) |
| 1205 | except Exception: |
| 1206 | android_device.log.warning("SL4A event not registered. Codec " |
| 1207 | "may not have been changed.") |
Aidan Holloway-bidwell | f8f5151 | 2018-11-06 10:42:14 -0800 | [diff] [blame] | 1208 | |
| 1209 | # Validate codec value through ADB |
Aidan Holloway-bidwell | 477807a | 2019-02-01 15:29:00 -0800 | [diff] [blame] | 1210 | # TODO (aidanhb): validate codec more robustly using SL4A |
Aidan Holloway-bidwell | f8f5151 | 2018-11-06 10:42:14 -0800 | [diff] [blame] | 1211 | command = "dumpsys bluetooth_manager | grep -i 'current codec'" |
| 1212 | out = android_device.adb.shell(command) |
| 1213 | split_out = out.split(": ") |
| 1214 | if len(split_out) != 2: |
| 1215 | android_device.log.warning("Could not verify codec config change " |
| 1216 | "through ADB.") |
Aidan Holloway-bidwell | 9e44b9d | 2018-11-07 14:33:27 -0800 | [diff] [blame] | 1217 | elif split_out[1].strip().upper() != codec_type: |
Aidan Holloway-bidwell | f8f5151 | 2018-11-06 10:42:14 -0800 | [diff] [blame] | 1218 | android_device.log.error( |
| 1219 | "Codec config was not changed.\n" |
| 1220 | "\tExpected codec: {exp}\n" |
| 1221 | "\tActual codec: {act}".format( |
| 1222 | exp=codec_type, |
| 1223 | act=split_out[1].strip() |
| 1224 | ) |
| 1225 | ) |
| 1226 | return False |
| 1227 | android_device.log.info("Bluetooth codec successfully changed.") |
| 1228 | return True |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 1229 | |
| 1230 | |
| 1231 | def set_bt_scan_mode(ad, scan_mode_value): |
| 1232 | """Set Android device's Bluetooth scan mode. |
| 1233 | |
| 1234 | Args: |
| 1235 | ad: The Android device to set the scan mode on. |
| 1236 | scan_mode_value: The value to set the scan mode to. |
| 1237 | |
| 1238 | Returns: |
| 1239 | True if successful, false if unsuccessful. |
| 1240 | """ |
| 1241 | droid, ed = ad.droid, ad.ed |
| 1242 | if scan_mode_value == bt_scan_mode_types['state_off']: |
| 1243 | disable_bluetooth(droid) |
| 1244 | scan_mode = droid.bluetoothGetScanMode() |
| 1245 | reset_bluetooth([ad]) |
| 1246 | if scan_mode != scan_mode_value: |
| 1247 | return False |
| 1248 | elif scan_mode_value == bt_scan_mode_types['none']: |
| 1249 | droid.bluetoothMakeUndiscoverable() |
| 1250 | scan_mode = droid.bluetoothGetScanMode() |
| 1251 | if scan_mode != scan_mode_value: |
| 1252 | return False |
| 1253 | elif scan_mode_value == bt_scan_mode_types['connectable']: |
| 1254 | droid.bluetoothMakeUndiscoverable() |
| 1255 | droid.bluetoothMakeConnectable() |
| 1256 | scan_mode = droid.bluetoothGetScanMode() |
| 1257 | if scan_mode != scan_mode_value: |
| 1258 | return False |
| 1259 | elif (scan_mode_value == bt_scan_mode_types['connectable_discoverable']): |
| 1260 | droid.bluetoothMakeDiscoverable() |
| 1261 | scan_mode = droid.bluetoothGetScanMode() |
| 1262 | if scan_mode != scan_mode_value: |
| 1263 | return False |
| 1264 | else: |
| 1265 | # invalid scan mode |
| 1266 | return False |
| 1267 | return True |
| 1268 | |
| 1269 | |
| 1270 | def set_device_name(droid, name): |
| 1271 | """Set and check Bluetooth local name on input droid object. |
| 1272 | |
| 1273 | Args: |
| 1274 | droid: Droid object to set local name on. |
| 1275 | name: the Bluetooth local name to set. |
| 1276 | |
| 1277 | Returns: |
| 1278 | True if successful, false if unsuccessful. |
| 1279 | """ |
| 1280 | droid.bluetoothSetLocalName(name) |
| 1281 | time.sleep(2) |
| 1282 | droid_name = droid.bluetoothGetLocalName() |
| 1283 | if droid_name != name: |
| 1284 | return False |
| 1285 | return True |
| 1286 | |
| 1287 | |
| 1288 | def set_profile_priority(host_ad, client_ad, profiles, priority): |
| 1289 | """Sets the priority of said profile(s) on host_ad for client_ad""" |
| 1290 | for profile in profiles: |
| 1291 | host_ad.log.info("Profile {} on {} for {} set to priority {}".format( |
| 1292 | profile, host_ad.droid.bluetoothGetLocalName(), |
| 1293 | client_ad.droid.bluetoothGetLocalAddress(), priority.value)) |
| 1294 | if bt_profile_constants['a2dp_sink'] == profile: |
| 1295 | host_ad.droid.bluetoothA2dpSinkSetPriority( |
| 1296 | client_ad.droid.bluetoothGetLocalAddress(), priority.value) |
| 1297 | elif bt_profile_constants['headset_client'] == profile: |
| 1298 | host_ad.droid.bluetoothHfpClientSetPriority( |
| 1299 | client_ad.droid.bluetoothGetLocalAddress(), priority.value) |
| 1300 | elif bt_profile_constants['pbap_client'] == profile: |
| 1301 | host_ad.droid.bluetoothPbapClientSetPriority( |
| 1302 | client_ad.droid.bluetoothGetLocalAddress(), priority.value) |
| 1303 | else: |
| 1304 | host_ad.log.error( |
| 1305 | "Profile {} not yet supported for priority settings".format( |
| 1306 | profile)) |
| 1307 | |
| 1308 | |
| 1309 | def setup_multiple_devices_for_bt_test(android_devices): |
| 1310 | """A common setup routine for Bluetooth on input Android device list. |
| 1311 | |
| 1312 | Things this function sets up: |
| 1313 | 1. Resets Bluetooth |
| 1314 | 2. Set Bluetooth local name to random string of size 4 |
| 1315 | 3. Disable BLE background scanning. |
| 1316 | 4. Enable Bluetooth snoop logging. |
| 1317 | |
| 1318 | Args: |
| 1319 | android_devices: Android device list to setup Bluetooth on. |
| 1320 | |
| 1321 | Returns: |
| 1322 | True if successful, false if unsuccessful. |
| 1323 | """ |
| 1324 | log.info("Setting up Android Devices") |
| 1325 | # TODO: Temp fix for an selinux error. |
| 1326 | for ad in android_devices: |
| 1327 | ad.adb.shell("setenforce 0") |
| 1328 | threads = [] |
| 1329 | try: |
| 1330 | for a in android_devices: |
| 1331 | thread = threading.Thread( |
| 1332 | target=factory_reset_bluetooth, args=([[a]])) |
| 1333 | threads.append(thread) |
| 1334 | thread.start() |
| 1335 | for t in threads: |
| 1336 | t.join() |
| 1337 | |
| 1338 | for a in android_devices: |
| 1339 | d = a.droid |
| 1340 | # TODO: Create specific RPC command to instantiate |
| 1341 | # BluetoothConnectionFacade. This is just a workaround. |
| 1342 | d.bluetoothStartConnectionStateChangeMonitor("") |
| 1343 | setup_result = d.bluetoothSetLocalName(generate_id_by_size(4)) |
| 1344 | if not setup_result: |
| 1345 | a.log.error("Failed to set device name.") |
| 1346 | return setup_result |
| 1347 | d.bluetoothDisableBLE() |
Ted Wang | e4c7072 | 2019-02-21 16:06:53 +0800 | [diff] [blame^] | 1348 | utils.set_location_service(a, True) |
Bill Leung | 46b6935 | 2018-11-09 17:28:39 -0800 | [diff] [blame] | 1349 | bonded_devices = d.bluetoothGetBondedDevices() |
| 1350 | for b in bonded_devices: |
| 1351 | a.log.info("Removing bond for device {}".format(b['address'])) |
| 1352 | d.bluetoothUnbond(b['address']) |
| 1353 | for a in android_devices: |
| 1354 | a.adb.shell("setprop persist.bluetooth.btsnoopenable true") |
| 1355 | getprop_result = bool( |
| 1356 | a.adb.shell("getprop persist.bluetooth.btsnoopenable")) |
| 1357 | if not getprop_result: |
| 1358 | a.log.warning("Failed to enable Bluetooth Hci Snoop Logging.") |
| 1359 | except Exception as err: |
| 1360 | log.error("Something went wrong in multi device setup: {}".format(err)) |
| 1361 | return False |
| 1362 | return setup_result |
| 1363 | |
| 1364 | |
| 1365 | def setup_n_advertisements(adv_ad, num_advertisements): |
| 1366 | """Setup input number of advertisements on input Android device. |
| 1367 | |
| 1368 | Args: |
| 1369 | adv_ad: The Android device to start LE advertisements on. |
| 1370 | num_advertisements: The number of advertisements to start. |
| 1371 | |
| 1372 | Returns: |
| 1373 | advertise_callback_list: List of advertisement callback ids. |
| 1374 | """ |
| 1375 | adv_ad.droid.bleSetAdvertiseSettingsAdvertiseMode( |
| 1376 | ble_advertise_settings_modes['low_latency']) |
| 1377 | advertise_data = adv_ad.droid.bleBuildAdvertiseData() |
| 1378 | advertise_settings = adv_ad.droid.bleBuildAdvertiseSettings() |
| 1379 | advertise_callback_list = [] |
| 1380 | for i in range(num_advertisements): |
| 1381 | advertise_callback = adv_ad.droid.bleGenBleAdvertiseCallback() |
| 1382 | advertise_callback_list.append(advertise_callback) |
| 1383 | adv_ad.droid.bleStartBleAdvertising(advertise_callback, advertise_data, |
| 1384 | advertise_settings) |
| 1385 | try: |
| 1386 | adv_ad.ed.pop_event( |
| 1387 | adv_succ.format(advertise_callback), bt_default_timeout) |
| 1388 | adv_ad.log.info("Advertisement {} started.".format(i + 1)) |
| 1389 | except Empty as error: |
| 1390 | adv_ad.log.error("Advertisement {} failed to start.".format(i + 1)) |
| 1391 | raise BtTestUtilsError( |
| 1392 | "Test failed with Empty error: {}".format(error)) |
| 1393 | return advertise_callback_list |
| 1394 | |
| 1395 | |
| 1396 | def take_btsnoop_log(ad, testcase, testname): |
| 1397 | """Grabs the btsnoop_hci log on a device and stores it in the log directory |
| 1398 | of the test class. |
| 1399 | |
| 1400 | If you want grab the btsnoop_hci log, call this function with android_device |
| 1401 | objects in on_fail. Bug report takes a relative long time to take, so use |
| 1402 | this cautiously. |
| 1403 | |
| 1404 | Args: |
| 1405 | ad: The android_device instance to take bugreport on. |
| 1406 | testcase: Name of the test calss that triggered this snoop log. |
| 1407 | testname: Name of the test case that triggered this bug report. |
| 1408 | """ |
| 1409 | testname = "".join(x for x in testname if x.isalnum()) |
| 1410 | serial = ad.serial |
| 1411 | device_model = ad.droid.getBuildModel() |
| 1412 | device_model = device_model.replace(" ", "") |
| 1413 | out_name = ','.join((testname, device_model, serial)) |
| 1414 | snoop_path = ad.log_path + "/BluetoothSnoopLogs" |
| 1415 | utils.create_dir(snoop_path) |
| 1416 | cmd = ''.join(("adb -s ", serial, " pull ", btsnoop_log_path_on_device, |
| 1417 | " ", snoop_path + '/' + out_name, ".btsnoop_hci.log")) |
| 1418 | exe_cmd(cmd) |
| 1419 | try: |
| 1420 | cmd = ''.join( |
| 1421 | ("adb -s ", serial, " pull ", btsnoop_last_log_path_on_device, " ", |
| 1422 | snoop_path + '/' + out_name, ".btsnoop_hci.log.last")) |
| 1423 | exe_cmd(cmd) |
| 1424 | except Exception as err: |
| 1425 | testcase.log.info( |
| 1426 | "File does not exist {}".format(btsnoop_last_log_path_on_device)) |
| 1427 | |
| 1428 | |
| 1429 | def take_btsnoop_logs(android_devices, testcase, testname): |
| 1430 | """Pull btsnoop logs from an input list of android devices. |
| 1431 | |
| 1432 | Args: |
| 1433 | android_devices: the list of Android devices to pull btsnoop logs from. |
| 1434 | testcase: Name of the test calss that triggered this snoop log. |
| 1435 | testname: Name of the test case that triggered this bug report. |
| 1436 | """ |
| 1437 | for a in android_devices: |
| 1438 | take_btsnoop_log(a, testcase, testname) |
| 1439 | |
| 1440 | |
| 1441 | def teardown_n_advertisements(adv_ad, num_advertisements, |
| 1442 | advertise_callback_list): |
| 1443 | """Stop input number of advertisements on input Android device. |
| 1444 | |
| 1445 | Args: |
| 1446 | adv_ad: The Android device to stop LE advertisements on. |
| 1447 | num_advertisements: The number of advertisements to stop. |
| 1448 | advertise_callback_list: The list of advertisement callbacks to stop. |
| 1449 | |
| 1450 | Returns: |
| 1451 | True if successful, false if unsuccessful. |
| 1452 | """ |
| 1453 | for n in range(num_advertisements): |
| 1454 | adv_ad.droid.bleStopBleAdvertising(advertise_callback_list[n]) |
| 1455 | return True |
| 1456 | |
| 1457 | |
| 1458 | def verify_server_and_client_connected(client_ad, server_ad, log=True): |
| 1459 | """Verify that input server and client Android devices are connected. |
| 1460 | |
| 1461 | This code is under the assumption that there will only be |
| 1462 | a single connection. |
| 1463 | |
| 1464 | Args: |
| 1465 | client_ad: the Android device to check number of active connections. |
| 1466 | server_ad: the Android device to check number of active connections. |
| 1467 | |
| 1468 | Returns: |
| 1469 | True both server and client have at least 1 active connection, |
| 1470 | false if unsuccessful. |
| 1471 | """ |
| 1472 | test_result = True |
| 1473 | if len(server_ad.droid.bluetoothSocketConnActiveConnections()) == 0: |
| 1474 | if log: |
| 1475 | server_ad.log.error("No socket connections found on server.") |
| 1476 | test_result = False |
| 1477 | if len(client_ad.droid.bluetoothSocketConnActiveConnections()) == 0: |
| 1478 | if log: |
| 1479 | client_ad.log.error("No socket connections found on client.") |
| 1480 | test_result = False |
| 1481 | return test_result |
| 1482 | |
| 1483 | |
| 1484 | def wait_for_bluetooth_manager_state(droid, |
| 1485 | state=None, |
| 1486 | timeout=10, |
| 1487 | threshold=5): |
| 1488 | """ Waits for BlueTooth normalized state or normalized explicit state |
| 1489 | args: |
| 1490 | droid: droid device object |
| 1491 | state: expected BlueTooth state |
| 1492 | timeout: max timeout threshold |
| 1493 | threshold: list len of bt state |
| 1494 | Returns: |
| 1495 | True if successful, false if unsuccessful. |
| 1496 | """ |
| 1497 | all_states = [] |
| 1498 | get_state = lambda: droid.bluetoothGetLeState() |
| 1499 | start_time = time.time() |
| 1500 | while time.time() < start_time + timeout: |
| 1501 | all_states.append(get_state()) |
| 1502 | if len(all_states) >= threshold: |
| 1503 | # for any normalized state |
| 1504 | if state is None: |
| 1505 | if len(set(all_states[-threshold:])) == 1: |
| 1506 | log.info("State normalized {}".format( |
| 1507 | set(all_states[-threshold:]))) |
| 1508 | return True |
| 1509 | else: |
| 1510 | # explicit check against normalized state |
| 1511 | if set([state]).issubset(all_states[-threshold:]): |
| 1512 | return True |
| 1513 | time.sleep(0.5) |
| 1514 | log.error( |
| 1515 | "Bluetooth state fails to normalize" if state is None else |
| 1516 | "Failed to match bluetooth state, current state {} expected state {}". |
| 1517 | format(get_state(), state)) |
| 1518 | return False |
| 1519 | |
| 1520 | |
| 1521 | def _wait_for_passkey_match(pri_ad, sec_ad): |
| 1522 | pri_pin, sec_pin = -1, 1 |
| 1523 | pri_variant, sec_variant = -1, 1 |
| 1524 | pri_pairing_req, sec_pairing_req = None, None |
| 1525 | try: |
| 1526 | pri_pairing_req = pri_ad.ed.pop_event( |
| 1527 | event_name="BluetoothActionPairingRequest", |
| 1528 | timeout=bt_default_timeout) |
| 1529 | pri_variant = pri_pairing_req["data"]["PairingVariant"] |
| 1530 | pri_pin = pri_pairing_req["data"]["Pin"] |
| 1531 | pri_ad.log.info("Primary device received Pin: {}, Variant: {}".format( |
| 1532 | pri_pin, pri_variant)) |
| 1533 | sec_pairing_req = sec_ad.ed.pop_event( |
| 1534 | event_name="BluetoothActionPairingRequest", |
| 1535 | timeout=bt_default_timeout) |
| 1536 | sec_variant = sec_pairing_req["data"]["PairingVariant"] |
| 1537 | sec_pin = sec_pairing_req["data"]["Pin"] |
| 1538 | sec_ad.log.info("Secondary device received Pin: {}, Variant: {}" |
| 1539 | .format(sec_pin, sec_variant)) |
| 1540 | except Empty as err: |
| 1541 | log.error("Wait for pin error: {}".format(err)) |
| 1542 | log.error("Pairing request state, Primary: {}, Secondary: {}".format( |
| 1543 | pri_pairing_req, sec_pairing_req)) |
| 1544 | return False |
| 1545 | if pri_variant == sec_variant == pairing_variant_passkey_confirmation: |
| 1546 | confirmation = pri_pin == sec_pin |
| 1547 | if confirmation: |
| 1548 | log.info("Pairing code matched, accepting connection") |
| 1549 | else: |
| 1550 | log.info("Pairing code mismatched, rejecting connection") |
| 1551 | pri_ad.droid.eventPost("BluetoothActionPairingRequestUserConfirm", |
| 1552 | str(confirmation)) |
| 1553 | sec_ad.droid.eventPost("BluetoothActionPairingRequestUserConfirm", |
| 1554 | str(confirmation)) |
| 1555 | if not confirmation: |
| 1556 | return False |
| 1557 | elif pri_variant != sec_variant: |
| 1558 | log.error("Pairing variant mismatched, abort connection") |
| 1559 | return False |
| 1560 | return True |
| 1561 | |
| 1562 | |
| 1563 | def write_read_verify_data(client_ad, server_ad, msg, binary=False): |
| 1564 | """Verify that the client wrote data to the server Android device correctly. |
| 1565 | |
| 1566 | Args: |
| 1567 | client_ad: the Android device to perform the write. |
| 1568 | server_ad: the Android device to read the data written. |
| 1569 | msg: the message to write. |
| 1570 | binary: if the msg arg is binary or not. |
| 1571 | |
| 1572 | Returns: |
| 1573 | True if the data written matches the data read, false if not. |
| 1574 | """ |
| 1575 | client_ad.log.info("Write message.") |
| 1576 | try: |
| 1577 | if binary: |
| 1578 | client_ad.droid.bluetoothSocketConnWriteBinary(msg) |
| 1579 | else: |
| 1580 | client_ad.droid.bluetoothSocketConnWrite(msg) |
| 1581 | except Exception as err: |
| 1582 | client_ad.log.error("Failed to write data: {}".format(err)) |
| 1583 | return False |
| 1584 | server_ad.log.info("Read message.") |
| 1585 | try: |
| 1586 | if binary: |
| 1587 | read_msg = server_ad.droid.bluetoothSocketConnReadBinary().rstrip( |
| 1588 | "\r\n") |
| 1589 | else: |
| 1590 | read_msg = server_ad.droid.bluetoothSocketConnRead() |
| 1591 | except Exception as err: |
| 1592 | server_ad.log.error("Failed to read data: {}".format(err)) |
| 1593 | return False |
| 1594 | log.info("Verify message.") |
| 1595 | if msg != read_msg: |
| 1596 | log.error("Mismatch! Read: {}, Expected: {}".format(read_msg, msg)) |
| 1597 | return False |
| 1598 | return True |
| 1599 | |