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