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