1#  Copyright (C) 2023 The Android Open Source Project
2#
3#  Licensed under the Apache License, Version 2.0 (the "License");
4#  you may not use this file except in compliance with the License.
5#  You may obtain a copy of the License at
6#
7#       http://www.apache.org/licenses/LICENSE-2.0
8#
9#  Unless required by applicable law or agreed to in writing, software
10#  distributed under the License is distributed on an "AS IS" BASIS,
11#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12#  See the License for the specific language governing permissions and
13#  limitations under the License.
14
15import logging
16import pprint
17import time
18
19from utilities import constants
20from mobly import asserts
21from mobly.controllers import android_device
22from utilities.media_utils import MediaUtils
23
24# Number of seconds for the target to stay discoverable on Bluetooth.
25DISCOVERABLE_TIME = 60
26TIME_FOR_PROMPT_TO_LOAD = 2
27ALLOW_TEXT = "Allow"
28
29
30class BTUtils:
31    """A utility that provides access to Bluetooth connectivity controls."""
32
33    def __init__(self, discoverer, target):
34        self.discoverer = discoverer
35        self.target = target
36        self.target_adrr = None
37        self.media_utils = MediaUtils(self.target, self.discoverer)
38
39    # Disable Android Auto pop-up on HU
40    def disable_android_auto_popup_on_hu(self):
41        logging.info('Disable Android Auto pop-up on HU with adb')
42        self.media_utils.execute_shell_on_hu_device(constants.DISABLE_ANDROID_AUTO_POP_UP)
43
44    # Skip Assistant pop-up
45    # TODO @vitalidim remove this function after b/314386661 resolved
46    def handle_assistant_pop_up(self):
47        logging.info('Checking for Assistant pop-up on HU')
48        if self.discoverer.mbs.isAssistantImprovementPopUpPresent():
49            logging.info('Assistant pop-up is present on HU')
50            logging.info('Click on <CONTINUE> button on HU')
51            self.discoverer.mbs.skipImprovementCallingAndTextingPopUp()
52            asserts.assert_false(self.discoverer.mbs.isAssistantImprovementPopUpPresent(),
53                                 'Assistant pop-up should be closed')
54        else:
55            logging.info('Assistant pop-up is not present on HU')
56
57    def get_info_from_devices(self, discovered_devices):
58        discovered_names = [device['Name'] for device in discovered_devices]
59        discovered_addresses = [device['Address'] for device in discovered_devices]
60        return discovered_names, discovered_addresses
61
62    def discover_secondary_from_primary(self):
63        target_name = self.target.mbs.btGetName()
64        self.target.log.info('Become discoverable with name "%s" for %ds.',
65                             target_name, DISCOVERABLE_TIME)
66        self.target.mbs.btBecomeDiscoverable(DISCOVERABLE_TIME)
67        self.discoverer.log.info('Looking for Bluetooth devices.')
68        discovered_devices = self.discoverer.mbs.btDiscoverAndGetResults()
69        self.discoverer.log.debug('Found Bluetooth devices: %s',
70                                  pprint.pformat(discovered_devices, indent=2))
71        discovered_names, _ = self.get_info_from_devices(discovered_devices)
72        logging.info('Verifying the target is discovered by the discoverer.')
73        asserts.assert_true(
74            target_name in discovered_names,
75            'Failed to discover the target device %s over Bluetooth.' %
76            target_name)
77
78    def pair_primary_to_secondary(self):
79        """Enable discovery on the target so the discoverer can find it."""
80        # Turn bluetooth on in both machines
81        logging.info('Enabling Bluetooth logs')
82        self.enable_bt_logs()
83        logging.info('Enabling Bluetooth on both devices')
84        self.discoverer.mbs.btEnable()
85        self.target.mbs.btEnable()
86        self.disable_android_auto_popup_on_hu()
87        logging.info('Setting devices to be discoverable')
88        self.target.mbs.btBecomeDiscoverable(DISCOVERABLE_TIME)
89        self.target.mbs.btStartAutoAcceptIncomingPairRequest()
90        target_address = self.target.mbs.btGetAddress()
91        logging.info('Scanning for discoverable devices')
92        # Discovery of target device is tried 5 times.
93        discovered_devices = self.discoverer.mbs.btDiscoverAndGetResults()
94        self.discoverer.mbs.btPairDevice(target_address)
95        logging.info('Allowing time for contacts to sync')
96        time.sleep(constants.SYNC_WAIT_TIME)
97        self.press_allow_on_phone()
98        paired_devices = self.discoverer.mbs.btGetPairedDevices()
99        _, paired_addresses = self.get_info_from_devices(paired_devices)
100        asserts.assert_true(
101            target_address in paired_addresses,
102            'Failed to pair the target device %s over Bluetooth.' %
103            target_address)
104        time.sleep(constants.DEFAULT_WAIT_TIME_FIVE_SECS)
105        self.handle_assistant_pop_up()
106
107    def unpair(self):
108        # unpair Discoverer device from Target
109        logging.info("Unpair Discoverer device from Target")
110        discoverer_address = self.discoverer.mbs.btGetAddress()
111        logging.info(f"Discoverer device address: {discoverer_address}")
112        target_paired_devices = self.target.mbs.btGetPairedDevices()
113        _, target_paired_addresses = self.get_info_from_devices(target_paired_devices)
114        logging.info(f"Paired devices to Target: {target_paired_devices}")
115        if discoverer_address in target_paired_addresses:
116            logging.info(f"Forget Discoverer device <{discoverer_address}> on Target device")
117            self.target.mbs.btUnpairDevice(discoverer_address)
118        else:
119            logging.error("Discoverer device not founded on Target device")
120        # unpair Target device from Discoverer
121        logging.info("Unpair Target device from Discoverer")
122        target_address = self.target.mbs.btGetAddress()
123        logging.info(f"Target device address: {target_address}")
124        discoverer_paired_devices = self.discoverer.mbs.btGetPairedDevices()
125        _, discoverer_paired_addresses = self.get_info_from_devices(
126            discoverer_paired_devices)
127        logging.info(f"Paired devices to Discoverer: {discoverer_paired_devices}")
128        if target_address in discoverer_paired_addresses:
129            logging.info(f"Forget Target device <{target_address}> on Discoverer device")
130            self.discoverer.mbs.btUnpairDevice(target_address)
131        else:
132            logging.error("Target device not founded on Discoverer device")
133
134    def press_allow_on_phone(self):
135        """ Repeatedly presses "Allow" on prompts until no more prompts appear"""
136        logging.info('Attempting to press ALLOW')
137        while (self.target.mbs.hasUIElementWithText(ALLOW_TEXT)):
138            self.target.mbs.clickUIElementWithText(ALLOW_TEXT)
139            logging.info('ALLOW pressed!')
140            time.sleep(TIME_FOR_PROMPT_TO_LOAD)
141
142    def bt_disable(self):
143        """Disable Bluetooth on a device."""
144        self.discoverer.mbs.btUnpairDevice(self.target_adrr)
145        self.discoverer.mbs.btDisable()
146        self.target.mbs.btDisable()
147
148    def click_on_use_bluetooth_toggle(self):
149        logging.info('Click on Use Bluetooth toggle on HU')
150        self.discoverer.mbs.clickOnBluetoothToggle()
151
152    def enable_bt_logs(self):
153        logging.info('Enable bluetooth logs')
154        self.media_utils.execute_shell_on_hu_device(constants.BLUETOOTH_TAG)
155        self.media_utils.execute_shell_on_hu_device(constants.BLUETOOTH_NOOPERABLE)
156        self.media_utils.execute_shell_on_hu_device(constants.BLUETOOTH_BTSNOOP_DEFAULT_MODE)