Merge "[AWARE] Rename configuration variable with "aware" prefix" into oc-mr1-dev
diff --git a/acts/framework/acts/controllers/relay_lib/relay_rig.py b/acts/framework/acts/controllers/relay_lib/relay_rig.py
index 6a167a8..a88099d 100644
--- a/acts/framework/acts/controllers/relay_lib/relay_rig.py
+++ b/acts/framework/acts/controllers/relay_lib/relay_rig.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-#   Copyright 2016 - The Android Open Source Project
+#   Copyright 2017 - 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.
@@ -18,6 +18,7 @@
 from acts.controllers.relay_lib.sain_smart_board import SainSmartBoard
 from acts.controllers.relay_lib.generic_relay_device import GenericRelayDevice
 from acts.controllers.relay_lib.fugu_remote import FuguRemote
+from acts.controllers.relay_lib.sony_xb2_speaker import SonyXB2Speaker
 
 
 class RelayRig:
@@ -48,6 +49,7 @@
     _device_constructors = {
         'GenericRelayDevice': lambda x, rig: GenericRelayDevice(x, rig),
         'FuguRemote': lambda x, rig: FuguRemote(x, rig),
+        'SonyXB2Speaker': lambda x, rig: SonyXB2Speaker(x, rig),
     }
 
     def __init__(self, config):
diff --git a/acts/framework/acts/controllers/relay_lib/sony_xb2_speaker.py b/acts/framework/acts/controllers/relay_lib/sony_xb2_speaker.py
new file mode 100644
index 0000000..2d01eca
--- /dev/null
+++ b/acts/framework/acts/controllers/relay_lib/sony_xb2_speaker.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+#
+#   Copyright 2017 - 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.
+import time
+import enum
+import logging
+from acts.controllers.relay_lib.generic_relay_device import GenericRelayDevice
+from acts.controllers.relay_lib.relay import SynchronizeRelays
+from acts.controllers.relay_lib.errors import RelayConfigError
+from acts.controllers.relay_lib.helpers import validate_key
+
+PAIRING_MODE_WAIT_TIME = 5
+POWER_ON_WAIT_TIME = 2
+POWER_OFF_WAIT_TIME = 6
+MISSING_RELAY_MSG = 'Relay config for Sonxy XB2 "%s" missing relay "%s".'
+
+log = logging
+
+
+class Buttons(enum.Enum):
+    POWER = 'Power'
+    PAIR = 'Pair'
+
+
+class SonyXB2Speaker(GenericRelayDevice):
+    """A Sony XB2 Bluetooth Speaker.
+
+    Wraps the button presses, as well as the special features like pairing.
+    """
+
+    def __init__(self, config, relay_rig):
+        GenericRelayDevice.__init__(self, config, relay_rig)
+
+        self.mac_address = validate_key('mac_address', config, str, 'sony_xb2')
+
+        for button in Buttons:
+            self.ensure_config_contains_relay(button.value)
+
+    def ensure_config_contains_relay(self, relay_name):
+        """Throws an error if the relay does not exist."""
+        if relay_name not in self.relays:
+            raise RelayConfigError(MISSING_RELAY_MSG % (self.name, relay_name))
+
+    def _hold_button(self, button, seconds):
+        self.hold_down(button.value)
+        time.sleep(seconds)
+        self.release(button.value)
+
+    def power_on(self):
+        self._hold_button(Buttons.POWER, POWER_ON_WAIT_TIME)
+
+    def power_off(self):
+        self._hold_button(Buttons.POWER, POWER_OFF_WAIT_TIME)
+
+    def enter_pairing_mode(self):
+        self._hold_button(Buttons.PAIR, PAIRING_MODE_WAIT_TIME)
+
+    def setup(self):
+        """Sets all relays to their default state (off)."""
+        GenericRelayDevice.setup(self)
+
+    def clean_up(self):
+        """Sets all relays to their default state (off)."""
+        GenericRelayDevice.clean_up(self)
diff --git a/acts/tests/google/bt/SonyXB2PairingTest.py b/acts/tests/google/bt/SonyXB2PairingTest.py
new file mode 100644
index 0000000..3bb2ae7
--- /dev/null
+++ b/acts/tests/google/bt/SonyXB2PairingTest.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python
+#
+#   Copyright 2017 - 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 pairing of an Android Device to a Sony XB2 Bluetooth speaker
+"""
+import logging
+import time
+
+from acts.controllers.relay_lib.relay import SynchronizeRelays
+from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
+
+log = logging
+
+
+class SonyXB2PairingTest(BluetoothBaseTest):
+    DISCOVERY_TIME = 5
+
+    def __init__(self, controllers):
+        BluetoothBaseTest.__init__(self, controllers)
+        self.dut = self.android_devices[0]
+        # Do factory reset and then do delay for 3-seconds
+        self.dut.droid.bluetoothFactoryReset()
+        time.sleep(3)
+        self.sony_xb2_speaker = self.relay_devices[0]
+
+    def setup_test(self):
+        super(BluetoothBaseTest, self).setup_test()
+        self.sony_xb2_speaker.setup()
+        self.sony_xb2_speaker.power_on()
+        # Wait for a moment between pushing buttons
+        time.sleep(0.25)
+        self.sony_xb2_speaker.enter_pairing_mode()
+
+    def teardown_test(self):
+        super(BluetoothBaseTest, self).teardown_test()
+        self.sony_xb2_speaker.power_off()
+        self.sony_xb2_speaker.clean_up()
+
+    def _perform_classic_discovery(self, scan_time=DISCOVERY_TIME):
+        self.dut.droid.bluetoothStartDiscovery()
+        time.sleep(scan_time)
+        self.dut.droid.bluetoothCancelDiscovery()
+        return self.dut.droid.bluetoothGetDiscoveredDevices()
+
+    @BluetoothBaseTest.bt_test_wrap
+    def test_speaker_on(self):
+        """Test if the Sony XB2 speaker is powered on.
+
+        Use scanning to determine if the speaker is powered on.
+
+        Steps:
+        1. Put the speaker into pairing mode. (Hold the button)
+        2. Perform a scan on the DUT
+        3. Check the scan list for the device.
+
+        Expected Result:
+        Speaker is found.
+
+        Returns:
+          Pass if True
+          Fail if False
+
+        TAGS: ACTS_Relay
+        Priority: 1
+        """
+
+        for device in self._perform_classic_discovery():
+            if device['address'] == self.sony_xb2_speaker.mac_address:
+                self.dut.log.info("Desired device with MAC address %s found!",
+                                  self.sony_xb2_speaker.mac_address)
+                return True
+        return False
+
+    @BluetoothBaseTest.bt_test_wrap
+    def test_speaker_off(self):
+        """Test if the Sony XB2 speaker is powered off.
+
+        Use scanning to determine if the speaker is powered off.
+
+        Steps:
+        1. Power down the speaker
+        2. Put the speaker into pairing mode. (Hold the button)
+        3. Perform a scan on the DUT
+        4. Check the scan list for the device.
+
+        Expected Result:
+        Speaker is not found.
+
+        Returns:
+          Pass if True
+          Fail if False
+
+        TAGS: ACTS_Relay
+        Priority: 1
+        """
+        # Specific part of the test, turn off the speaker
+        self.sony_xb2_speaker.power_off()
+
+        device_not_found = True
+        for device in self._perform_classic_discovery():
+            if device['address'] == self.sony_xb2_speaker.mac_address:
+                self.dut.log.info("Undesired device with MAC address %s found!",
+                                  self.sony_xb2_speaker.mac_address)
+                device_not_found = False
+
+        # Set the speaker back to the normal for tear_down()
+        self.sony_xb2_speaker.power_on()
+        # Give the relay and speaker some time, before it is turned off.
+        time.sleep(5)
+        return device_not_found
+
+    @BluetoothBaseTest.bt_test_wrap
+    def test_pairing(self):
+        """Test pairing between a phone and Sony XB2 speaker.
+
+        Test the Sony XB2 speaker can be paired to phone.
+
+        Steps:
+        1. Find the MAC address of remote controller from relay config file.
+        2. Start the device paring process.
+        3. Enable remote controller in pairing mode.
+        4. Verify the remote is paired.
+
+        Expected Result:
+        Speaker is paired.
+
+        Returns:
+          Pass if True
+          Fail if False
+
+        TAGS: ACTS_Relay
+        Priority: 1
+        """
+
+        # BT scan activity
+        self._perform_classic_discovery()
+        self.dut.droid.bluetoothDiscoverAndBond(
+            self.sony_xb2_speaker.mac_address)
+
+        end_time = time.time() + 20
+        self.dut.log.info("Verifying devices are bonded")
+        while (time.time() < end_time):
+            bonded_devices = self.dut.droid.bluetoothGetBondedDevices()
+            for d in bonded_devices:
+                if d['address'] == self.sony_xb2_speaker.mac_address:
+                    self.dut.log.info("Successfully bonded to device.")
+                    self.log.info(
+                        "XB2 Bonded devices:\n{}".format(bonded_devices))
+                    return True
+        # Timed out trying to bond.
+        self.dut.log.info("Failed to bond devices.")
+
+        return False
diff --git a/acts/tests/google/bt/power/A2dpPowerTest.py b/acts/tests/google/bt/power/A2dpPowerTest.py
index 5b8d387..56b9850 100644
--- a/acts/tests/google/bt/power/A2dpPowerTest.py
+++ b/acts/tests/google/bt/power/A2dpPowerTest.py
@@ -35,6 +35,7 @@
 from acts.test_utils.bt.PowerBaseTest import PowerBaseTest
 from acts.test_utils.bt.bt_test_utils import bluetooth_enabled_check
 from acts.test_utils.bt.bt_test_utils import disable_bluetooth
+from acts.controllers.relay_lib.sony_xb2_speaker import SonyXB2Speaker
 
 
 def push_file_to_device(ad, file_path, device_path, config_path):
@@ -98,12 +99,8 @@
     # LDAC playback quality constant for the codecs other than LDAC
     LDACBT_NONE = 0
 
-    def setup_class(self):
-
-        self.ad = self.android_devices[0]
-
+    def _pair_by_config(self):
         bt_device_address = self.user_params["bt_device_address"]
-
         # Push the bt_config.conf file to Android device
         # if it is specified in config file
         # so it can pair and connect automatically.
@@ -123,32 +120,12 @@
             if not push_file_to_device(
                     self.ad, bt_config_path, bt_conf_path_dut,
                     self.user_params[Config.key_config_path]):
-                self.log.error("Unable to push file {} to DUT.".format(
-                    bt_config_path))
+                self.log.error(
+                    "Unable to push file {} to DUT.".format(bt_config_path))
 
             self.log.info("Reboot")
             self.ad.reboot()
 
-        # Add music files to the Android device
-        music_path_dut = "/sdcard/Music/"
-        self.cd_quality_music_file = self.user_params["cd_quality_music_file"]
-        self.log.info("Push CD quality music file {}".format(
-            self.cd_quality_music_file))
-        if not push_file_to_device(self.ad, self.cd_quality_music_file,
-                                   music_path_dut,
-                                   self.user_params[Config.key_config_path]):
-            self.log.error("Unable to push file {} to DUT.".format(
-                self.cd_quality_music_file))
-
-        self.hi_res_music_file = self.user_params["hi_res_music_file"]
-        self.log.info("Push Hi Res quality music file {}".format(
-            self.hi_res_music_file))
-        if not push_file_to_device(self.ad, self.hi_res_music_file,
-                                   music_path_dut,
-                                   self.user_params[Config.key_config_path]):
-            self.log.error("Unable to find file {}.".format(
-                self.hi_res_music_file))
-
         if not bluetooth_enabled_check(self.ad):
             self.log.error("Failed to enable Bluetooth on DUT")
             return False
@@ -165,21 +142,96 @@
                 break
 
             try:
-                self.ad.droid.bluetoothConnectBonded(self.user_params[
-                    "bt_device_address"])
+                self.ad.droid.bluetoothConnectBonded(
+                    self.user_params["bt_device_address"])
             except Exception as err:
                 self.log.error(
                     "Failed to connect bonded device. Err: {}".format(err))
 
-        if not result:
+        if result is False:
             self.log.error("No headset is connected")
             return False
+        return True
+
+    def _discover_and_pair(self):
+        self.ad.droid.bluetoothStartDiscovery()
+        time.sleep(5)
+        self.ad.droid.bluetoothCancelDiscovery()
+        for device in self.ad.droid.bluetoothGetDiscoveredDevices():
+            if device['address'] == self.a2dp_speaker.mac_address:
+                self.ad.droid.bluetoothDiscoverAndBond(
+                    self.a2dp_speaker.mac_address)
+                end_time = time.time() + 20
+                self.log.info("Verifying devices are bonded")
+                while (time.time() < end_time):
+                    bonded_devices = self.ad.droid.bluetoothGetBondedDevices()
+                    for d in bonded_devices:
+                        if d['address'] == self.a2dp_speaker.mac_address:
+                            self.log.info("Successfully bonded to device.")
+                            self.log.info("XB2 Bonded devices:\n{}".format(
+                                bonded_devices))
+                            return True
+        return False
+
+    def setup_class(self):
+        self.ad = self.android_devices[0]
+        self.ad.droid.bluetoothFactoryReset()
+        # Factory reset requires a short delay to take effect
+        time.sleep(3)
+
+        # Determine if we have a relay-based device
+        self.a2dp_speaker = None
+        if self.relay_devices[0]:
+            self.a2dp_speaker = self.relay_devices[0]
+            self.a2dp_speaker.setup()
+            # The device may be on, enter pairing mode.
+            self.a2dp_speaker.enter_pairing_mode()
+            if self._discover_and_pair() is False:
+                # The device is probably off, turn it on
+                self.a2dp_speaker.power_on()
+                time.sleep(0.25)
+                self.a2dp_speaker.enter_pairing_mode()
+                if self._discover_and_pair() is False:
+                    self.log.info("Unable to pair to relay based device.")
+                    return False
+            if len(self.ad.droid.bluetoothGetConnectedDevices()) == 0:
+                self.log.info("Not connected to relay based device.")
+                return False
+        else:
+            # No relay-based device used config
+            if self._pair_by_config() is False:
+                return False
+
+        # Add music files to the Android device
+        music_path_dut = "/sdcard/Music/"
+        self.cd_quality_music_file = self.user_params["cd_quality_music_file"]
+        self.log.info(
+            "Push CD quality music file {}".format(self.cd_quality_music_file))
+        if not push_file_to_device(self.ad, self.cd_quality_music_file,
+                                   music_path_dut,
+                                   self.user_params[Config.key_config_path]):
+            self.log.error("Unable to push file {} to DUT.".format(
+                self.cd_quality_music_file))
+
+        self.hi_res_music_file = self.user_params["hi_res_music_file"]
+        self.log.info(
+            "Push Hi Res quality music file {}".format(self.hi_res_music_file))
+        if not push_file_to_device(self.ad, self.hi_res_music_file,
+                                   music_path_dut,
+                                   self.user_params[Config.key_config_path]):
+            self.log.error(
+                "Unable to find file {}.".format(self.hi_res_music_file))
 
         # Then do other power testing setup in the base class
         # including start PMC etc
         super(A2dpPowerTest, self).setup_class()
         return True
 
+    def teardown_class(self):
+        if self.a2dp_speaker is not None:
+            self.a2dp_speaker.power_off()
+            self.a2dp_speaker.clean_up()
+
     def _main_power_test_function_for_codec(self,
                                             codec_type,
                                             sample_rate,
@@ -220,8 +272,8 @@
             # Get the base name of music file
             music_name = os.path.basename(music_file)
             self.music_url = "file:///sdcard/Music/{}".format(music_name)
-            play_time = self.MEASURE_TIME + self.DELAY_MEASURE_TIME
-            +self.DELAY_STOP_TIME
+            play_time = (self.MEASURE_TIME + self.DELAY_MEASURE_TIME +
+                         self.DELAY_STOP_TIME)
             play_msg = "%s --es MusicURL %s --es PlayTime %d" % (
                 self.PMC_BASE_CMD, self.music_url, play_time)
 
@@ -234,7 +286,7 @@
                                                           bits_per_sample)
                 if codec_type == self.CODEC_LDAC:
                     msg = "%s --es LdacPlaybackQuality %d" % (
-                        play_msg, ldac_playback_quality)
+                        codec_msg, ldac_playback_quality)
                 else:
                     msg = codec_msg