#/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.
"""
This test script exercises background scan test scenarios.
"""

from queue import Empty

from acts.test_decorators import test_tracker_info
from acts.test_utils.bt.BluetoothBaseTest import BluetoothBaseTest
from acts.test_utils.bt.BleEnum import BluetoothAdapterState
from acts.test_utils.bt.bt_test_utils import bluetooth_off
from acts.test_utils.bt.bt_test_utils import bluetooth_on
from acts.test_utils.bt.bt_test_utils import cleanup_scanners_and_advertisers
from acts.test_utils.bt.bt_test_utils import generate_ble_advertise_objects
from acts.test_utils.bt.bt_test_utils import generate_ble_scan_objects
from acts.test_utils.bt.bt_test_utils import scan_result


class BleBackgroundScanTest(BluetoothBaseTest):
    default_timeout = 10
    max_scan_instances = 28
    report_delay = 2000
    scan_callbacks = []
    adv_callbacks = []
    active_scan_callback_list = []
    active_adv_callback_list = []

    bluetooth_le_on = "BleStateChangedOn"
    bluetooth_le_off = "BleStateChangedOff"

    def __init__(self, controllers):
        BluetoothBaseTest.__init__(self, controllers)
        self.scn_ad = self.android_devices[0]
        self.adv_ad = self.android_devices[1]

    def setup_test(self):
        if (self.scn_ad.droid.bluetoothGetLeState() ==
                BluetoothAdapterState.STATE_OFF.value):
            self.scn_ad.droid.bluetoothEnableBLE()
            self.scn_ad.ed.pop_event(self.bluetooth_le_on)
        for a in self.android_devices:
            a.ed.clear_all_events()
        return True

    def teardown_test(self):
        cleanup_scanners_and_advertisers(
            self.scn_ad, self.active_adv_callback_list, self.adv_ad,
            self.active_adv_callback_list)
        self.active_adv_callback_list = []
        self.active_scan_callback_list = []

    def _setup_generic_advertisement(self):
        adv_callback, adv_data, adv_settings = generate_ble_advertise_objects(
            self.adv_ad.droid)
        self.adv_ad.droid.bleStartBleAdvertising(adv_callback, adv_data,
                                                 adv_settings)
        self.active_adv_callback_list.append(adv_callback)

    def _verify_no_events_found(self, event_name):
        try:
            self.scn_ad.ed.pop_event(event_name, self.default_timeout)
            self.log.error("Found an event when none was expected.")
            return False
        except Empty:
            self.log.info("No scan result found as expected.")
            return True

    @BluetoothBaseTest.bt_test_wrap
    @test_tracker_info(uuid='4d13c3a8-1805-44ef-a92a-e385540767f1')
    def test_background_scan(self):
        """Test generic background scan.

        Tests LE background scan. The goal is to find scan results even though
        Bluetooth is turned off.

        Steps:
        1. Setup an advertisement on dut1
        2. Enable LE on the Bluetooth Adapter on dut0
        3. Toggle BT off on dut1
        4. Start a LE scan on dut0
        5. Find the advertisement from dut1

        Expected Result:
        Find a advertisement from the scan instance.

        Returns:
          Pass if True
          Fail if False

        TAGS: LE, Advertising, Scanning, Background Scanning
        Priority: 0
        """
        import time
        self._setup_generic_advertisement()
        self.scn_ad.droid.bluetoothToggleState(False)
        try:
            self.scn_ad.ed.pop_event(bluetooth_off, self.default_timeout)
        except Empty:
            self.log.error("Bluetooth Off event not found. Expected {}".format(
                bluetooth_off))
            return False
        self.scn_ad.droid.bluetoothDisableBLE()
        try:
            self.scn_ad.ed.pop_event(bluetooth_off, self.default_timeout)
        except Empty:
            self.log.error("Bluetooth Off event not found. Expected {}".format(
                bluetooth_off))
            return False
        self.scn_ad.droid.bluetoothEnableBLE()
        try:
            self.scn_ad.ed.pop_event(bluetooth_off, self.default_timeout * 2)
        except Empty:
            self.log.error("Bluetooth On event not found. Expected {}".format(
                bluetooth_on))
            return False
        filter_list, scan_settings, scan_callback = generate_ble_scan_objects(
            self.scn_ad.droid)
        self.scn_ad.droid.bleStartBleScan(filter_list, scan_settings,
                                          scan_callback)
        expected_event = scan_result.format(scan_callback)
        try:
            self.scn_ad.ed.pop_event(expected_event, self.default_timeout)
        except Empty:
            self.log.error("Scan Result event not found. Expected {}".format(
                expected_event))
            return False
        return True

    @BluetoothBaseTest.bt_test_wrap
    @test_tracker_info(uuid='9c4577f8-5e06-4034-b977-285956734974')
    def test_background_scan_ble_disabled(self):
        """Test background LE scanning with LE disabled.

        Tests LE background scan. The goal is to find scan results even though
        Bluetooth is turned off.

        Steps:
        1. Setup an advertisement on dut1
        2. Enable LE on the Bluetooth Adapter on dut0
        3. Toggle BT off on dut1
        4. Start a LE scan on dut0
        5. Find the advertisement from dut1

        Expected Result:
        Find a advertisement from the scan instance.

        Returns:
          Pass if True
          Fail if False

        TAGS: LE, Advertising, Scanning, Background Scanning
        Priority: 0
        """
        self._setup_generic_advertisement()
        self.scn_ad.droid.bluetoothEnableBLE()
        self.scn_ad.droid.bluetoothToggleState(False)
        try:
            self.scn_ad.ed.pop_event(bluetooth_off, self.default_timeout)
        except Empty:
            self.log.error("Bluetooth Off event not found. Expected {}".format(
                bluetooth_off))
            return False
        filter_list, scan_settings, scan_callback = generate_ble_scan_objects(
            self.scn_ad.droid)
        try:
            self.scn_ad.droid.bleStartBleScan(filter_list, scan_settings,
                                              scan_callback)
            expected_event = scan_result.format(scan_callback)
            try:
                self.scn_ad.ed.pop_event(expected_event, self.default_timeout)
            except Empty:
                self.log.error("Scan Result event not found. Expected {}".
                               format(expected_event))
                return False
            self.log.info("Was able to start background scan even though ble "
                          "was disabled.")
            return False
        except Exception:
            self.log.info(
                "Was not able to start a background scan as expected.")
        return True

    @BluetoothBaseTest.bt_test_wrap
    @test_tracker_info(uuid='0bdd1764-3dc6-4a82-b041-76e48ed0f424')
    def test_airplane_mode_disables_ble(self):
        """Try to start LE mode in Airplane Mode.

        This test will enable airplane mode, then attempt to start LE scanning
        mode.  This should result in bluetooth still being turned off, LE
        not enabled.

        Steps:
        1. Start LE only mode.
        2. Bluetooth should be in LE ONLY mode
        2. Turn on airplane mode.
        3. Bluetooth should be OFF
        4. Try to start LE only mode.
        5. Bluetooth should stay in OFF mode (LE only start should fail)
        6. Turn off airplane mode.
        7. Bluetooth should be OFF.

        Expected Result:
        No unexpected bluetooth state changes.

        Returns:
          Pass if True
          Fail if False

        TAGS: LE, Airplane
        Priority: 1
        """
        ble_state_error_msg = "Bluetooth LE State not OK {}. Expected {} got {}"
        # Enable BLE always available (effectively enabling BT in location)
        self.scn_ad.adb.shell(
            "shell settings put global ble_scan_always_enabled 1")

        self.scn_ad.droid.bluetoothToggleState(False)
        try:
            self.scn_ad.ed.pop_event(bluetooth_off, self.default_timeout)
        except Empty:
            self.log.error("Bluetooth Off event not found. Expected {}".format(
                bluetooth_off))
            return False

        # Sleep because LE turns off after the bluetooth off event fires
        time.sleep(self.default_timeout)
        state = self.scn_ad.droid.bluetoothGetLeState()
        if state != BluetoothAdapterState.STATE_OFF.value:
            self.log.error(
                ble_state_error_msg.format(
                    "after BT Disable", BluetoothAdapterState.STATE_OFF.value,
                    state))
            return False

        # TODO: BleStateChangedOn got generated as we shut off bluetooth above?
        self.scn_ad.ed.clear_all_events()
        result = self.scn_ad.droid.bluetoothEnableBLE()
        try:
            self.scn_ad.ed.pop_event(self.bluetooth_le_on,
                                     self.default_timeout)
        except Empty:
            self.log.error("Bluetooth LE On event not found. Expected {}".
                           format(self.bluetooth_le_on))
            return False
        state = self.scn_ad.droid.bluetoothGetLeState()
        if state != BluetoothAdapterState.STATE_BLE_ON.value:
            self.log.error(
                ble_state_error_msg.format(
                    "before Airplane Mode OFF",
                    BluetoothAdapterState.STATE_BLE_ON.value, state))
            return False

        self.scn_ad.droid.bluetoothListenForBleStateChange()
        self.scn_ad.droid.connectivityToggleAirplaneMode(True)
        try:
            self.scn_ad.ed.pop_event(self.bluetooth_le_off,
                                     self.default_timeout)
        except Empty:
            self.log.error("Bluetooth LE Off event not found. Expected {}".
                           format(self.bluetooth_le_off))
            return False
        state = self.scn_ad.droid.bluetoothGetLeState()
        if state != BluetoothAdapterState.STATE_OFF.value:
            self.log.error(
                ble_state_error_msg.format(
                    "after Airplane Mode ON",
                    BluetoothAdapterState.STATE_OFF.value, state))
            return False
        result = self.scn_ad.droid.bluetoothEnableBLE()
        if result:
            self.log.error(
                "Bluetooth Enable command succeded when it should have failed (in airplane mode)"
            )
            return False
        state = self.scn_ad.droid.bluetoothGetLeState()
        if state != BluetoothAdapterState.STATE_OFF.value:
            self.log.error(
                "Bluetooth LE State not OK after attempted enable. Expected {} got {}".
                format(BluetoothAdapterState.STATE_OFF.value, state))
            return False
        self.scn_ad.droid.connectivityToggleAirplaneMode(False)
        # Sleep to let Airplane Mode disable propogate through the system
        time.sleep(self.default_timeout)
        state = self.scn_ad.droid.bluetoothGetLeState()
        if state != BluetoothAdapterState.STATE_OFF.value:
            self.log.error(
                ble_state_error_msg.format(
                    "after Airplane Mode OFF",
                    BluetoothAdapterState.STATE_OFF.value, state))
            return False
        return True