Funhaus runner code

Bug: b/30896601
Bug: b/30896407
Bug: b/30896220
Bug: b/30895491
Bug: b/30789415
Change-Id: I489f57cd88239a33248e8e3840c4eee7fcee89df
(cherry picked from commit 479c689656fb0cea7b88f53e13dd34548988b513)
diff --git a/acts/tests/google/bt/audio_lab/BtFunhausTest.py b/acts/tests/google/bt/audio_lab/BtFunhausTest.py
new file mode 100644
index 0000000..61be777
--- /dev/null
+++ b/acts/tests/google/bt/audio_lab/BtFunhausTest.py
@@ -0,0 +1,264 @@
+#/usr/bin/env python3.4
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""
+Test script to automate the Bluetooth Audio Funhaus.
+"""
+from acts.keys import Config
+from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+from acts.test_utils.bt.bt_test_utils import bluetooth_enabled_check
+from acts.utils import create_dir
+from acts.utils import exe_cmd
+from acts.utils import sync_device_time
+from queue import Empty
+import json
+import time
+import os
+
+
+class BtFunhausTest(BluetoothBaseTest):
+    music_file_to_play = ""
+    device_fails_to_connect_list = []
+
+    def __init__(self, controllers):
+        BluetoothBaseTest.__init__(self, controllers)
+
+    def on_fail(self, test_name, begin_time):
+        self._collect_bluetooth_manager_dumpsys_logs(self.android_devices)
+        super(BluetoothBaseTest, self).on_fail(test_name, begin_time)
+
+    def setup_class(self):
+        for ad in self.android_devices:
+            sync_device_time(ad)
+            # Disable Bluetooth HCI Snoop Logs for audio tests
+            ad.droid.bluetoothConfigHciSnoopLog(False)
+        if not "bt_config" in self.user_params.keys():
+            self.log.error("Missing mandatory user config \"bt_config\"!")
+            return False
+        bt_config_map_file = self.user_params["bt_config"]
+        return self._setup_bt_config(bt_config_map_file)
+
+    def _setup_bt_config(self, bt_config_map_file):
+        bt_config_map = {}
+        bt_conf_path = "/data/misc/bluedroid/bt_config.conf"
+        if not os.path.isfile(bt_config_map_file):
+            bt_config_map_file = os.path.join(
+                self.user_params[Config.key_config_path], bt_config_map_file)
+            if not os.path.isfile(bt_config_map_file):
+                self.log.error("Unable to load bt_config file {}.".format(
+                    bt_config_map_file))
+                return False
+        try:
+            f = open(bt_config_map_file, 'r')
+            bt_config_map = json.load(f)
+            f.close()
+        except FileNotFoundError:
+            self.log.error("File not found: {}.".format(bt_config_map_file))
+            return False
+        for ad in self.android_devices:
+            serial = ad.serial
+
+            # Verify serial number in bt_config_map
+            if serial not in bt_config_map.keys():
+                self.log.error(
+                    "Missing android device serial {} in bt_config.".format(
+                        serial))
+                return False
+            # Push the bt_config.conf file to Android device
+            config_path = bt_config_map[serial]["config_path"]
+            if not os.path.isfile(config_path):
+                config_path = os.path.join(
+                    self.user_params[Config.key_config_path], config_path)
+                if not os.path.isfile(config_path):
+                    self.log.error("Unable to load bt_config file {}.".format(
+                        config_path))
+                    return False
+            ad.adb.push("{} {}".format(config_path, bt_conf_path))
+
+            # Add music to the Android device
+            music_path_str = "music_path"
+            android_music_path = "/sdcard/Music/"
+            if music_path_str not in self.user_params:
+                self.log.error("Need music for audio testcases...")
+                return False
+
+            music_path = self.user_params[music_path_str]
+            if not os.path.isdir(music_path):
+                music_path = os.path.join(
+                    self.user_params[Config.key_config_path], music_path)
+                if not os.path.isdir(music_path):
+                    self.log.error("Unable to find music directory {}.".format(
+                        music_path))
+                    return False
+            self._add_music_to_primary_android_device(ad, music_path,
+                                                      android_music_path)
+            ad.reboot()
+
+            # Verify Bluetooth is enabled
+            if not bluetooth_enabled_check(ad):
+                self.log.error("Failed to toggle on Bluetooth on device {}".
+                               format(serial))
+                return False
+
+            # Verify Bluetooth device is connected
+            self.log.info(
+                "Waiting up to 10 seconds for device to reconnect...")
+            connected_devices = ad.droid.bluetoothGetConnectedDevices()
+            start_time = time.time()
+            wait_time = 10
+            result = False
+            while time.time() < start_time + wait_time:
+                connected_devices = ad.droid.bluetoothGetConnectedDevices()
+                if len(connected_devices) > 0:
+                    if bt_config_map[serial]["peripheral_info"]["address"] in {
+                            d['address']
+                            for d in connected_devices
+                    }:
+                        result = True
+                        break
+            if not result:
+                self.log.error(
+                    "Failed to connect to peripheral name: {}, address: {}".
+                    format(bt_config_map[serial]["peripheral_info"]["name"],
+                           bt_config_map[serial]["peripheral_info"][
+                               "address"]))
+                self.device_fails_to_connect_list.append("{}:{}".format(
+                    serial, bt_config_map[serial]["peripheral_info"]["name"]))
+        if len(self.device_fails_to_connect_list) == len(self.android_devices):
+            self.log.error("All devices failed to reconnect.")
+            return False
+        return True
+
+    def _add_music_to_primary_android_device(self, ad, music_path,
+                                             android_music_path):
+        for dirname, dirnames, filenames in os.walk(music_path):
+            for filename in filenames:
+                self.music_file_to_play = filename
+                file = os.path.join(dirname, filename)
+                #TODO: Handle file paths with spaces
+                ad.adb.push("{} {}".format(file, android_music_path))
+        return True
+
+    def _collect_bluetooth_manager_dumpsys_logs(self, ads):
+        for ad in ads:
+            serial = ad.serial
+            out_name = "{}_{}".format(serial, "bluetooth_dumpsys.txt")
+            dumpsys_path = ''.join((ad.log_path, "/BluetoothDumpsys"))
+            create_dir(dumpsys_path)
+            cmd = ''.join(
+                ("adb -s ", serial, " shell dumpsys bluetooth_manager > ",
+                 dumpsys_path, "/", out_name))
+            exe_cmd(cmd)
+
+    @BluetoothBaseTest.bt_test_wrap
+    def test_run_bt_audio_12_hours(self):
+        """Test audio quality over 12 hours.
+
+        This test is designed to run Bluetooth audio for 12 hours
+        and collect relavant logs. If all devices disconnect during
+        the test or Bluetooth is off on all devices, then fail the
+        test.
+
+        Steps:
+        1. For each Android device connected run steps 2-5.
+        2. Open and play media file of music pushed to device
+        3. Set media to loop indefintely.
+        4. After 12 hours collect bluetooth_manager dumpsys information
+        5. Stop media player
+
+        Expected Result:
+        Audio plays for 12 hours over Bluetooth
+
+        Returns:
+          Pass if True
+          Fail if False
+
+        TAGS: Classic, A2DP
+        Priority: 1
+        """
+        for ad in self.android_devices:
+            ad.droid.mediaPlayOpen("file:///sdcard/Music/{}".format(
+                self.music_file_to_play))
+            ad.droid.mediaPlaySetLooping(True)
+            self.log.info("Music is now playing on device {}".format(
+                ad.serial))
+
+        sleep_interval = 120
+        twelve_hours_in_seconds = 43200
+        twelve_hours_in_seconds = 500
+        end_time = time.time() + twelve_hours_in_seconds
+        test_result = True
+        bluetooth_off_list = []
+        device_not_connected_list = []
+        while time.time() < end_time:
+            for ad in self.android_devices:
+                serial = ad.serial
+                if (not ad.droid.bluetoothCheckState() and
+                        serial not in bluetooth_off_list):
+                    self.log.error(
+                        "Device {}'s Bluetooth state is off.".format(serial))
+                    bluetooth_off_list.append(serial)
+                if (ad.droid.bluetoothGetConnectedDevices() == 0 and
+                        serial not in device_not_connected_list):
+                    self.log.error(
+                        "Device {} not connected to any Bluetooth devices.".
+                        format(serial))
+                    device_not_connected_list.append(serial)
+                if len(bluetooth_off_list) == len(self.android_devices):
+                    self.log.error(
+                        "Bluetooth off on all Android devices. Ending Test")
+                    return False
+                if len(device_not_connected_list) == len(self.android_devices):
+                    self.log.error(
+                        "Every Android device has no device connected.")
+                    return False
+            time.sleep(sleep_interval)
+
+        self._collect_bluetooth_manager_dumpsys_logs(self.android_devices)
+        for ad in self.android_devices:
+            ad.droid.mediaPlayStopAll()
+        if len(device_not_connected_list) > 0 or len(bluetooth_off_list) > 0:
+            self.log.info("Devices reported as not connected: {}".format(
+                device_not_connected_list))
+            self.log.info("Devices reported with Bluetooth state off: {}".
+                          format(bluetooth_off_list))
+            return False
+        return True
+
+    def test_setup_fail_if_devices_not_connected(self):
+        """Test for devices connected or not during setup.
+
+        This test is designed to fail if the number of devices having
+        connection issues at time of setup is greater than 0. This lets
+        the test runner know of the stability of the testbed.
+
+        Steps:
+        1. Check lenght of self.device_fails_to_connect_list
+
+        Expected Result:
+        No device should be in a disconnected state.
+
+        Returns:
+          Pass if True
+          Fail if False
+
+        TAGS: None
+        Priority: 1
+        """
+        if len(self.device_fails_to_connect_list) > 0:
+            self.log.error("Devices failed to reconnect:\n{}".format(
+                self.device_fails_to_connect_list))
+            return False
+        return True
\ No newline at end of file