# Lint as: python2, python3
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from __future__ import absolute_import

import logging
import time

import common
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros.bluetooth import bluetooth_socket
from autotest_lib.server.cros.bluetooth import bluetooth_adapter_tests

DEVICE_ADDRESS = '01:02:03:04:05:06'
ADDRESS_TYPE = 0

class bluetooth_Health_DefaultStateTest(
        bluetooth_adapter_tests.BluetoothAdapterTests):
    """
    This class implements the default state test
    and all the helper functions it needs.
    """
    version = 1



    def compare_property(self, bluez_property, mgmt_setting, current_settings):
        """ Compare bluez property value and Kernel property

            @param bluez_property : Bluez property to be compared
            @param mgmt_setting   : Bit mask of management setting
            @param current_settings : Current kernel settings
            @return : True if bluez property and the current settings agree """

        cur_kernel_value = 1 if mgmt_setting & current_settings else 0
        return bluez_property == cur_kernel_value

    def default_state_test(self):
        """Test Default state of Bluetooth adapter after power cycling."""

        # Reset the adapter to the powered off state.
        self.test_reset_off_adapter()

        # Kernel default state depends on whether the kernel supports the
        # BR/EDR Allowlist. When this is supported the 'connectable' setting
        # remains unset and instead page scan is managed by the kernel based
        # on whether or not a BR/EDR device is in the allowlist.
        ( commands, events ) = self.read_supported_commands()
        supports_add_device = bluetooth_socket.MGMT_OP_ADD_DEVICE in commands

        # Read the initial state of the adapter. Verify that it is powered down.
        ( address, bluetooth_version, manufacturer_id,
                supported_settings, current_settings, class_of_device,
                name, short_name ) = self.read_info()
        self.log_settings('Initial state', current_settings)

        if current_settings & bluetooth_socket.MGMT_SETTING_POWERED:
            raise error.TestFail('Bluetooth adapter is powered')

        # The other kernel settings (connectable, pairable, etc.) reflect the
        # initial state before the bluetooth daemon adjusts them - we're ok
        # with them being on or off during that brief period.
        #

        # Verify that the Bluetooth Daemon sees that it is also powered down,
        # non-discoverable and not discovering devices.
        bluez_properties = self.get_adapter_properties()

        if bluez_properties['Powered']:
            raise error.TestFail('Bluetooth daemon Powered property does not '
                                 'match kernel while powered off')
        if not self.compare_property(bluez_properties['Discoverable'],
                                     bluetooth_socket.MGMT_SETTING_DISCOVERABLE,
                                     current_settings):
            raise error.TestFail('Bluetooth daemon Discoverable property '
                                 'does not match kernel while powered off')
        if bluez_properties['Discovering']:
            raise error.TestFail('Bluetooth daemon believes adapter is '
                                 'discovering while powered off')

        # Compare with the raw HCI state of the adapter as well, this should
        # be just not "UP", otherwise something deeply screwy is happening.
        flags = self.get_dev_info()[3]
        self.log_flags('Initial state', flags)

        if flags & bluetooth_socket.HCI_UP:
            raise error.TestFail('HCI UP flag does not match kernel while '
                                 'powered off')

        # Power on the adapter, then read the state again. Verify that it is
        # powered up, pairable, but not discoverable.
        self.test_power_on_adapter()
        current_settings = self.read_info()[4]
        self.log_settings("Powered up", current_settings)

        if not current_settings & bluetooth_socket.MGMT_SETTING_POWERED:
            raise error.TestFail('Bluetooth adapter is not powered')

        # If the kernel does not supports the BR/EDR allowlist, the adapter
        # should be generically connectable;
        # if it doesn't, then it depends on previous settings.
        if not supports_add_device:
            if not current_settings & bluetooth_socket.MGMT_SETTING_CONNECTABLE:
                raise error.TestFail('Bluetooth adapter is not connectable '
                                     'though kernel does not support '
                                     'BR/EDR allowlist')

        # Verify that the Bluetooth Daemon sees the same state as the kernel
        # and that it's not discovering.
        bluez_properties = self.get_adapter_properties()

        if not bluez_properties['Powered']:
            raise error.TestFail('Bluetooth daemon Powered property does not '
                                 'match kernel while powered on')
        if not self.compare_property(bluez_properties['Pairable'],
                                     bluetooth_socket.MGMT_SETTING_PAIRABLE,
                                     current_settings):
            raise error.TestFail('Bluetooth daemon Pairable property does not '
                                 'match kernel while powered on')
        if not self.compare_property(bluez_properties['Discoverable'],
                                     bluetooth_socket.MGMT_SETTING_DISCOVERABLE,
                                     current_settings):
            raise error.TestFail('Bluetooth daemon Discoverable property '
                                 'does not match kernel while powered on')
        if bluez_properties['Discovering']:
            raise error.TestFail('Bluetooth daemon believes adapter is '
                                 'discovering while powered on')

        # Compare with the raw HCI state of the adapter while powered up as
        # well.
        flags = self.get_dev_info()[3]
        self.log_flags('Powered up', flags)

        if not flags & bluetooth_socket.HCI_UP:
            raise error.TestFail('HCI UP flag does not match kernel while '
                                 'powered on')
        if not flags & bluetooth_socket.HCI_RUNNING:
            raise error.TestFail('HCI RUNNING flag does not match kernel while '
                                 'powered on')
        if bool(flags & bluetooth_socket.HCI_ISCAN) != \
            bool(bluez_properties['Discoverable']):
            raise error.TestFail('HCI ISCAN flag does not match kernel while '
                                 'powered on')
        if flags & bluetooth_socket.HCI_INQUIRY:
            raise error.TestFail('HCI INQUIRY flag does not match kernel while '
                                 'powered on')

        # If the kernel does not supports the BR/EDR allowlist, the adapter
        # should generically connectable, so should it should be in PSCAN
        # mode. This matches the management API "connectable" setting so far.
        if not supports_add_device:
            if not flags & bluetooth_socket.HCI_PSCAN:
                raise error.TestFail('HCI PSCAN flag not set though kernel'
                                     'does not supports BR/EDR allowlist')

        # Now we can examine the differences. Try adding and removing a device
        # from the kernel BR/EDR allowlist. The management API "connectable"
        # setting should remain off, but we should be able to see the PSCAN
        # flag come and go.
        if supports_add_device:
            # If PSCAN is currently on then device is CONNECTABLE
            # or a previous add device which was not removed.
            # Turn on and off DISCOVERABLE to turn off CONNECTABLE and
            # PSCAN
            if flags & bluetooth_socket.HCI_PSCAN:
                if not (current_settings &
                        bluetooth_socket.MGMT_SETTING_CONNECTABLE):
                    raise error.TestFail('PSCAN on but device not CONNECTABLE')
                logging.debug('Toggle Discoverable to turn off CONNECTABLE')
                self.test_discoverable()
                self.test_nondiscoverable()
                current_settings = self.read_info()[4]
                flags = self.get_dev_info()[3]
                self.log_flags('Discoverability Toggled', flags)
                if flags & bluetooth_socket.HCI_PSCAN:
                    raise error.TestFail('PSCAN on after toggling DISCOVERABLE')

            previous_settings = current_settings
            previous_flags = flags

            self.add_device(DEVICE_ADDRESS, ADDRESS_TYPE, 1)

            # Wait for a few seconds before reading the settings
            time.sleep(3)
            current_settings = self.read_info()[4]
            self.log_settings("After add device",
                                              current_settings)

            flags = self.get_dev_info()[3]
            self.log_flags('After add device', flags)

            if current_settings != previous_settings:
                self.log_settings("previous settings", previous_settings)
                self.log_settings("current settings", current_settings)
                raise error.TestFail(
                    'Bluetooth adapter settings changed after add device')
            if not flags & bluetooth_socket.HCI_PSCAN:
                raise error.TestFail('HCI PSCAN flag not set after add device')

            # Remove the device again, and make sure the PSCAN flag goes away.
            self.remove_device(DEVICE_ADDRESS, ADDRESS_TYPE)

            # PSCAN is still enabled for a few seconds after remove device
            # on older devices. Wait for few second before reading the settigs
            time.sleep(3)
            current_settings = self.read_info()[4]
            self.log_settings("After remove device", current_settings)

            flags = self.get_dev_info()[3]
            self.log_flags('After remove device', flags)

            if current_settings != previous_settings:
                raise error.TestFail(
                    'Bluetooth adapter settings changed after remove device')
            if flags & bluetooth_socket.HCI_PSCAN:
                raise error.TestFail('HCI PSCAN flag set after remove device')

        # Finally power off the adapter again, and verify that the adapter has
        # returned to powered down.
        self.test_power_off_adapter()
        current_settings = self.read_info()[4]
        self.log_settings("After power down", current_settings)

        if current_settings & bluetooth_socket.MGMT_SETTING_POWERED:
            raise error.TestFail('Bluetooth adapter is powered after power off')

        # Verify that the Bluetooth Daemon sees the same state as the kernel.
        bluez_properties = self.get_adapter_properties()

        if bluez_properties['Powered']:
            raise error.TestFail('Bluetooth daemon Powered property does not '
                                 'match kernel after power off')
        if not self.compare_property(bluez_properties['Discoverable'],
                                     bluetooth_socket.MGMT_SETTING_DISCOVERABLE,
                                     current_settings):
            raise error.TestFail('Bluetooth daemon Discoverable property '
                                 'does not match kernel after off')
        if bluez_properties['Discovering']:
            raise error.TestFail('Bluetooth daemon believes adapter is '
                                 'discovering after power off')

        # And one last comparison with the raw HCI state of the adapter.
        flags = self.get_dev_info()[3]
        self.log_flags('After power down', flags)

        if flags & bluetooth_socket.HCI_UP:
            raise error.TestFail('HCI UP flag does not match kernel after '
                                 'power off')
