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 """ 17 Test script to automate the Bluetooth Audio Funhaus. 18 """ 19 from acts.keys import Config 20 from acts.test_utils.bt.BtMetricsBaseTest import BtMetricsBaseTest 21 from acts.test_utils.bt.bt_test_utils import bluetooth_enabled_check 22 from acts.utils import bypass_setup_wizard 23 from acts.utils import create_dir 24 from acts.utils import exe_cmd 25 from acts.utils import sync_device_time 26 import json 27 import time 28 import os 29 30 BT_CONF_PATH = "/data/misc/bluedroid/bt_config.conf" 31 32 33 class BtFunhausBaseTest(BtMetricsBaseTest): 34 """ 35 Base class for Bluetooth A2DP audio tests, this class is in charge of 36 pushing link key to Android device so that it could be paired with remote 37 A2DP device, pushing music to Android device, playing audio, monitoring 38 audio play, and stop playing audio 39 """ 40 music_file_to_play = "" 41 device_fails_to_connect_list = [] 42 43 def __init__(self, controllers): 44 BtMetricsBaseTest.__init__(self, controllers) 45 self.ad = self.android_devices[0] 46 self.dongle = self.relay_devices[0] 47 48 def _pair_devices(self): 49 self.ad.droid.bluetoothStartPairingHelper(False) 50 self.dongle.enter_pairing_mode() 51 52 self.ad.droid.bluetoothBond(self.dongle.mac_address) 53 54 end_time = time.time() + 20 55 self.ad.log.info("Verifying devices are bonded") 56 while time.time() < end_time: 57 bonded_devices = self.ad.droid.bluetoothGetBondedDevices() 58 59 for d in bonded_devices: 60 if d['address'] == self.dongle.mac_address: 61 self.ad.log.info("Successfully bonded to device.") 62 self.log.info("Bonded devices:\n{}".format(bonded_devices)) 63 return True 64 self.ad.log.info("Failed to bond devices.") 65 return False 66 67 def setup_test(self): 68 super(BtFunhausBaseTest, self).setup_test() 69 self.dongle.setup() 70 tries = 5 71 # Since we are not concerned with pairing in this test, try 5 times. 72 while tries > 0: 73 if self._pair_devices(): 74 return True 75 else: 76 tries -= 1 77 return False 78 79 def teardown_test(self): 80 super(BtFunhausBaseTest, self).teardown_test() 81 self.dongle.clean_up() 82 return True 83 84 def on_fail(self, test_name, begin_time): 85 self.dongle.clean_up() 86 self._collect_bluetooth_manager_dumpsys_logs(self.android_devices) 87 super(BtFunhausBaseTest, self).on_fail(test_name, begin_time) 88 89 def setup_class(self): 90 if not super(BtFunhausBaseTest, self).setup_class(): 91 return False 92 for ad in self.android_devices: 93 sync_device_time(ad) 94 # Disable Bluetooth HCI Snoop Logs for audio tests 95 ad.adb.shell("setprop persist.bluetooth.btsnoopenable false") 96 if not bypass_setup_wizard(ad): 97 self.log.debug( 98 "Failed to bypass setup wizard, continuing test.") 99 # Add music to the Android device 100 return self._add_music_to_android_device(ad) 101 102 def _add_music_to_android_device(self, ad): 103 """ 104 Add music to Android device as specified by the test config 105 :param ad: Android device 106 :return: True on success, False on failure 107 """ 108 self.log.info("Pushing music to the Android device.") 109 music_path_str = "bt_music" 110 android_music_path = "/sdcard/Music/" 111 if music_path_str not in self.user_params: 112 self.log.error("Need music for audio testcases...") 113 return False 114 music_path = self.user_params[music_path_str] 115 if type(music_path) is list: 116 self.log.info("Media ready to push as is.") 117 elif not os.path.isdir(music_path): 118 music_path = os.path.join(self.user_params[Config.key_config_path], 119 music_path) 120 if not os.path.isdir(music_path): 121 self.log.error( 122 "Unable to find music directory {}.".format(music_path)) 123 return False 124 if type(music_path) is list: 125 for item in music_path: 126 self.music_file_to_play = item 127 ad.adb.push("{} {}".format(item, android_music_path)) 128 else: 129 for dirname, dirnames, filenames in os.walk(music_path): 130 for filename in filenames: 131 self.music_file_to_play = filename 132 file = os.path.join(dirname, filename) 133 # TODO: Handle file paths with spaces 134 ad.adb.push("{} {}".format(file, android_music_path)) 135 ad.reboot() 136 return True 137 138 def _collect_bluetooth_manager_dumpsys_logs(self, ads): 139 """ 140 Collect "adb shell dumpsys bluetooth_manager" logs 141 :param ads: list of active Android devices 142 :return: None 143 """ 144 for ad in ads: 145 serial = ad.serial 146 out_name = "{}_{}".format(serial, "bluetooth_dumpsys.txt") 147 dumpsys_path = ''.join((ad.log_path, "/BluetoothDumpsys")) 148 create_dir(dumpsys_path) 149 cmd = ''.join( 150 ("adb -s ", serial, " shell dumpsys bluetooth_manager > ", 151 dumpsys_path, "/", out_name)) 152 exe_cmd(cmd) 153 154 def start_playing_music_on_all_devices(self): 155 """ 156 Start playing music 157 :return: None 158 """ 159 self.ad.droid.mediaPlayOpen("file:///sdcard/Music/{}".format( 160 self.music_file_to_play.split("/")[-1])) 161 self.ad.droid.mediaPlaySetLooping(True) 162 self.ad.log.info("Music is now playing.") 163 164 def monitor_music_play_util_deadline(self, end_time, sleep_interval=1): 165 """ 166 Monitor music play on all devices, if a device's Bluetooth adapter is 167 OFF or if a device is not connected to any remote Bluetooth devices, 168 we add them to failure lists bluetooth_off_list and 169 device_not_connected_list respectively 170 :param end_time: The deadline in epoch floating point seconds that we 171 must stop playing 172 :param sleep_interval: How often to monitor, too small we may drain 173 too much resources on Android, too big the deadline might be passed 174 by a maximum of this amount 175 :return: 176 status: False iff all devices are off or disconnected otherwise True 177 bluetooth_off_list: List of ADs that have Bluetooth at OFF state 178 device_not_connected_list: List of ADs with no remote device 179 connected 180 """ 181 device_not_connected_list = [] 182 while time.time() < end_time: 183 if not self.ad.droid.bluetoothCheckState(): 184 self.ad.log.error("Device {}'s Bluetooth state is off.".format( 185 self.ad.serial)) 186 return False 187 if self.ad.droid.bluetoothGetConnectedDevices() == 0: 188 self.ad.log.error( 189 "Bluetooth device not connected. Failing test.") 190 time.sleep(sleep_interval) 191 return True 192 193 def play_music_for_duration(self, duration, sleep_interval=1): 194 """ 195 A convenience method for above methods. It starts run music on all 196 devices, monitors the health of music play and stops playing them when 197 time passes the duration 198 :param duration: Duration in floating point seconds 199 :param sleep_interval: How often to check the health of music play 200 :return: 201 status: False iff all devices are off or disconnected otherwise True 202 bluetooth_off_list: List of ADs that have Bluetooth at OFF state 203 device_not_connected_list: List of ADs with no remote device 204 connected 205 """ 206 start_time = time.time() 207 end_time = start_time + duration 208 self.start_playing_music_on_all_devices() 209 status = self.monitor_music_play_util_deadline(end_time, 210 sleep_interval) 211 self.ad.droid.mediaPlayStopAll() 212 return status 213