1# Copyright 2023 Google LLC
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#     https://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"""All functions relative to the bluetooth procedure."""
15
16import logging
17import threading
18import traceback
19
20from floss.pandora.floss import adapter_client
21from floss.pandora.floss import advertising_client
22from floss.pandora.floss import floss_enums
23from floss.pandora.floss import gatt_client
24from floss.pandora.floss import gatt_server
25from floss.pandora.floss import manager_client
26from floss.pandora.floss import media_client
27from floss.pandora.floss import qa_client
28from floss.pandora.floss import scanner_client
29from floss.pandora.floss import socket_manager
30from floss.pandora.floss import telephony_client
31from floss.pandora.floss import utils
32from gi.repository import GLib
33import pydbus
34
35
36class Bluetooth(object):
37    """A bluetooth facade exposes all bluetooth functions."""
38
39    # Default to this adapter during init. We will initialize to the correct
40    # default adapter after the manager client is initialized.
41    DEFAULT_ADAPTER = 0
42
43    # Time to sleep between polls
44    ADAPTER_CLIENT_POLL_INTERVAL = 0.1
45
46    # How long we wait for the adapter to come up after we start it.
47    ADAPTER_DAEMON_TIMEOUT_SEC = 20
48
49    # How long we wait for the manager daemon to come up after we start it.
50    DAEMON_TIMEOUT_SEC = 5
51
52    # Default scanner settings
53    SCANNER_INTERVAL = 0
54    SCANNER_WINDOW = 0
55    SCANNER_SCAN_TYPE = 0
56
57    FAKE_GATT_APP_ID = '12345678123456781234567812345678'
58
59    def __init__(self):
60        self.setup_mainloop()
61
62        # self state
63        self.is_clean = False
64
65        # DBUS clients
66        self.manager_client = manager_client.FlossManagerClient(self.bus)
67        self.adapter_client = adapter_client.FlossAdapterClient(self.bus, self.DEFAULT_ADAPTER)
68        self.advertising_client = advertising_client.FlossAdvertisingClient(self.bus, self.DEFAULT_ADAPTER)
69        self.scanner_client = scanner_client.FlossScannerClient(self.bus, self.DEFAULT_ADAPTER)
70        self.qa_client = qa_client.FlossQAClient(self.bus, self.DEFAULT_ADAPTER)
71        self.media_client = media_client.FlossMediaClient(self.bus, self.DEFAULT_ADAPTER)
72        self.gatt_client = gatt_client.FlossGattClient(self.bus, self.DEFAULT_ADAPTER)
73        self.gatt_server = gatt_server.FlossGattServer(self.bus, self.DEFAULT_ADAPTER)
74        self.socket_manager = socket_manager.FlossSocketManagerClient(self.bus, self.DEFAULT_ADAPTER)
75        self.telephony_client = telephony_client.FlossTelephonyClient(self.bus, self.DEFAULT_ADAPTER)
76
77    def __del__(self):
78        if not self.is_clean:
79            self.cleanup()
80
81    def cleanup(self):
82        self.mainloop_quit.set()
83        self.mainloop.quit()
84        self.is_clean = True
85
86    def setup_mainloop(self):
87        """Start mainloop thread in background.
88
89        This will also initialize a few
90        other variables (self.bus, self.mainloop, self.event_context) that may
91        be necessary for proper operation.
92
93        Raises:
94            RuntimeError: if we timeout to wait for the mainloop ready.
95        """
96
97        self.mainloop_quit = threading.Event()
98        self.mainloop_ready = threading.Event()
99        self.thread = threading.Thread(name=utils.GLIB_THREAD_NAME, target=Bluetooth.mainloop_thread, args=(self,))
100        self.thread.start()
101
102        # Wait for mainloop to be ready
103        if not self.mainloop_ready.wait(timeout=5):
104            raise RuntimeError('Unable to initialize GLib mainloop')
105
106    def mainloop_thread(self):
107        # Set up mainloop. All subsequent buses and connections will use this
108        # mainloop. We also use a separate main context to avoid multithreading
109        # issues.
110        GLib.threads_init()
111        self.mainloop = GLib.MainLoop()
112
113        # Set up bus connection
114        self.bus = pydbus.SystemBus()
115
116        # Set thread ready
117        self.mainloop_ready.set()
118
119        while not self.mainloop_quit.is_set():
120            self.mainloop.run()
121
122    def register_clients_callback(self):
123        """Registers callback for all interfaces.
124
125        Returns:
126            True on success, False otherwise.
127        """
128        if not self.adapter_client.register_callbacks():
129            logging.error('adapter_client: Failed to register callbacks')
130            return False
131        if not self.advertising_client.register_advertiser_callback():
132            logging.error('advertising_client: Failed to register advertiser callbacks')
133            return False
134        if not self.scanner_client.register_scanner_callback():
135            logging.error('scanner_client: Failed to register callbacks')
136            return False
137        if not self.qa_client.register_qa_callback():
138            logging.error('qa_client: Failed to register callbacks')
139            return False
140        if not self.media_client.register_callback():
141            logging.error('media_client: Failed to register callbacks')
142            return False
143        if not self.gatt_client.register_client(self.FAKE_GATT_APP_ID, False):
144            logging.error('gatt_client: Failed to register callbacks')
145            return False
146        if not self.gatt_server.register_server(self.FAKE_GATT_APP_ID, False):
147            logging.error('gatt_server: Failed to register callbacks')
148            return False
149        if not self.socket_manager.register_callbacks():
150            logging.error('scanner_client: Failed to register callbacks')
151            return False
152        if not self.telephony_client.register_telephony_callback():
153            logging.error('telephony_client: Failed to register callbacks')
154            return False
155        return True
156
157    def is_bluetoothd_proxy_valid(self):
158        """Checks whether the proxy objects for Floss are ok and registers client callbacks."""
159
160        proxy_ready = all([
161            self.manager_client.has_proxy(),
162            self.adapter_client.has_proxy(),
163            self.advertising_client.has_proxy(),
164            self.scanner_client.has_proxy(),
165            self.qa_client.has_proxy(),
166            self.media_client.has_proxy(),
167            self.gatt_client.has_proxy(),
168            self.gatt_server.has_proxy(),
169            self.socket_manager.has_proxy(),
170            self.telephony_client.has_proxy()
171        ])
172
173        if not proxy_ready:
174            logging.info('Some proxy has not yet ready.')
175            return False
176
177        return self.register_clients_callback()
178
179    def set_powered(self, powered: bool):
180        """Set the power of bluetooth adapter and bluetooth clients.
181
182        Args:
183            powered: Power on or power off.
184
185        Returns:
186            bool: True if success, False otherwise.
187        """
188        default_adapter = self.manager_client.get_default_adapter()
189
190        def _is_adapter_down(client):
191            return lambda: not client.has_proxy()
192
193        if powered:
194            # FIXME: Close rootcanal will cause manager_client failed call has_default_adapter.
195            # if not self.manager_client.has_default_adapter():
196            #     logging.warning('set_powered: Default adapter not available.')
197            #     return False
198            self.manager_client.start(default_adapter)
199
200            self.adapter_client = adapter_client.FlossAdapterClient(self.bus, default_adapter)
201            self.advertising_client = advertising_client.FlossAdvertisingClient(self.bus, default_adapter)
202            self.scanner_client = scanner_client.FlossScannerClient(self.bus, default_adapter)
203            self.qa_client = qa_client.FlossQAClient(self.bus, default_adapter)
204            self.media_client = media_client.FlossMediaClient(self.bus, default_adapter)
205            self.gatt_client = gatt_client.FlossGattClient(self.bus, default_adapter)
206            self.gatt_server = gatt_server.FlossGattServer(self.bus, default_adapter)
207            self.socket_manager = socket_manager.FlossSocketManagerClient(self.bus, default_adapter)
208            self.telephony_client = telephony_client.FlossTelephonyClient(self.bus, default_adapter)
209
210            try:
211                utils.poll_for_condition(
212                    condition=lambda: self.is_bluetoothd_proxy_valid() and self.adapter_client.get_address(),
213                    desc='Wait for adapter start',
214                    sleep_interval=self.ADAPTER_CLIENT_POLL_INTERVAL,
215                    timeout=self.ADAPTER_DAEMON_TIMEOUT_SEC)
216            except TimeoutError as e:
217                logging.error('timeout: error starting adapter daemon: %s', e)
218                logging.error(traceback.format_exc())
219                return False
220        else:
221            self.manager_client.stop(default_adapter)
222            try:
223                utils.poll_for_condition(condition=_is_adapter_down(self.adapter_client),
224                                         desc='Wait for adapter stop',
225                                         sleep_interval=self.ADAPTER_CLIENT_POLL_INTERVAL,
226                                         timeout=self.ADAPTER_DAEMON_TIMEOUT_SEC)
227            except TimeoutError as e:
228                logging.error('timeout: error stopping adapter daemon: %s', e)
229                logging.error(traceback.format_exc())
230                return False
231        return True
232
233    def reset(self):
234        if not self.set_powered(False):
235            return False
236
237        if not self.set_powered(True):
238            return False
239        return True
240
241    def get_address(self):
242        return self.adapter_client.get_address()
243
244    def get_remote_type(self, address):
245        return self.adapter_client.get_remote_property(address, 'Type')
246
247    def is_connected(self, address):
248        return self.adapter_client.is_connected(address)
249
250    def is_bonded(self, address):
251        return self.adapter_client.is_bonded(address)
252
253    def is_discovering(self):
254        return self.adapter_client.is_discovering()
255
256    def set_discoverable(self, mode, duration=60):
257        return self.adapter_client.set_property('Discoverable', mode, duration)
258
259    def create_bond(self, address, transport):
260        return self.adapter_client.create_bond(address, transport)
261
262    def remove_bond(self, address):
263        return self.adapter_client.remove_bond(address)
264
265    def get_bonded_devices(self):
266        return self.adapter_client.get_bonded_devices()
267
268    def forget_device(self, address):
269        return self.adapter_client.forget_device(address)
270
271    def set_pin(self, address, accept, pin_code):
272        return self.adapter_client.set_pin(address, accept, pin_code)
273
274    def set_pairing_confirmation(self, address, accept):
275        return self.adapter_client.set_pairing_confirmation(address, accept)
276
277    def connect_device(self, address):
278        return self.adapter_client.connect_all_enabled_profiles(address)
279
280    def disconnect_device(self, address):
281        return self.adapter_client.disconnect_all_enabled_profiles(address)
282
283    def start_discovery(self):
284        if self.adapter_client.is_discovering():
285            logging.warning('Adapter is already discovering.')
286            return True
287        return self.adapter_client.start_discovery()
288
289    def stop_discovery(self):
290        if not self.adapter_client.is_discovering():
291            logging.warning('Discovery is already stopped.')
292            return True
293        return self.adapter_client.stop_discovery()
294
295    def start_advertising_set(self, parameters, advertise_data, scan_response, periodic_parameters, periodic_data,
296                              duration, max_ext_adv_events):
297        parameters = self.advertising_client.make_dbus_advertising_set_parameters(parameters)
298        advertise_data = self.advertising_client.make_dbus_advertise_data(advertise_data)
299        scan_response = utils.make_kv_optional_value(self.advertising_client.make_dbus_advertise_data(scan_response))
300        periodic_parameters = utils.make_kv_optional_value(
301            self.advertising_client.make_dbus_periodic_advertising_parameters(periodic_parameters))
302        periodic_data = utils.make_kv_optional_value(self.advertising_client.make_dbus_advertise_data(periodic_data))
303
304        return self.advertising_client.start_advertising_set(parameters, advertise_data, scan_response,
305                                                             periodic_parameters, periodic_data, duration,
306                                                             max_ext_adv_events)
307
308    def stop_advertising_set(self, advertiser_id):
309        return self.advertising_client.stop_advertising_set(advertiser_id)
310
311    def register_scanner(self):
312        return self.scanner_client.register_scanner()
313
314    def start_scan(self, scanner_id, settings=None, scan_filter=None):
315        if settings is None:
316            settings = self.scanner_client.make_dbus_scan_settings(self.SCANNER_INTERVAL, self.SCANNER_WINDOW,
317                                                                   self.SCANNER_SCAN_TYPE)
318        return self.scanner_client.start_scan(scanner_id, settings, scan_filter)
319
320    def stop_scan(self, scanner_id):
321        if not self.scanner_client.remove_monitor(scanner_id):
322            logging.error('Failed to stop scanning.')
323            return False
324        return True
325
326    def set_hid_report(self, addr, report_type, report):
327        return self.qa_client.set_hid_report(addr, report_type, report)
328
329    def read_characteristic(self, address, handle, auth_re):
330        return self.gatt_client.read_characteristic(address, handle, auth_re)
331
332    def read_descriptor(self, address, handle, auth_req):
333        return self.gatt_client.read_descriptor(address, handle, auth_req)
334
335    def discover_services(self, address):
336        return self.gatt_client.discover_services(address)
337
338    def get_bond_state(self, address):
339        self.adapter_client.get_bond_state(address)
340
341    def fetch_remote(self, address):
342        return self.adapter_client.fetch_remote_uuids(address)
343
344    def get_remote_uuids(self, address):
345        return self.adapter_client.get_remote_property(address, 'Uuids')
346
347    def btif_gattc_discover_service_by_uuid(self, address, uuid):
348        return self.gatt_client.btif_gattc_discover_service_by_uuid(address, uuid)
349
350    def configure_mtu(self, address, mtu):
351        return self.gatt_client.configure_mtu(address, mtu)
352
353    def refresh_device(self, address):
354        return self.gatt_client.refresh_device(address)
355
356    def write_descriptor(self, address, handle, auth_req, value):
357        return self.gatt_client.write_descriptor(address, handle, auth_req, value)
358
359    def write_characteristic(self, address, handle, write_type, auth_req, value):
360        return self.gatt_client.write_characteristic(address, handle, write_type, auth_req, value)
361
362    def set_mps_qualification_enabled(self, enable):
363        return self.telephony_client.set_mps_qualification_enabled(enable)
364
365    def incoming_call(self, number):
366        return self.telephony_client.incoming_call(number)
367
368    def set_phone_ops_enabled(self, enable):
369        return self.telephony_client.set_phone_ops_enabled(enable)
370
371    def dial_call(self, number):
372        return self.telephony_client.dialing_call(number)
373
374    def answer_call(self):
375        return self.telephony_client.answer_call()
376
377    def swap_active_call(self):
378        return self.telephony_client.hold_active_accept_held()
379
380    def set_last_call(self, number=None):
381        return self.telephony_client.set_last_call(number)
382
383    def set_memory_call(self, number=None):
384        return self.telephony_client.set_memory_call(number)
385
386    def get_connected_audio_devices(self):
387        return self.media_client.devices
388
389    def audio_connect(self, address):
390        return self.telephony_client.audio_connect(address)
391
392    def audio_disconnect(self, address):
393        return self.telephony_client.audio_disconnect(address)
394
395    def hangup_call(self):
396        return self.telephony_client.hangup_call()
397
398    def set_battery_level(self, battery_level):
399        return self.telephony_client.set_battery_level(battery_level)
400
401    def gatt_connect(self, address, is_direct, transport):
402        return self.gatt_client.connect_client(address, is_direct, transport)
403
404    def set_connectable(self, mode):
405        return self.qa_client.set_connectable(mode)
406
407    def read_using_characteristic_uuid(self, address, uuid, start_handle, end_handle, auth_req):
408        return self.gatt_client.read_using_characteristic_uuid(address, uuid, start_handle, end_handle, auth_req)
409
410    def register_for_notification(self, address, handle, enable):
411        return self.gatt_client.register_for_notification(address, handle, enable)
412
413    def add_service(self, service):
414        service = self.gatt_server.make_dbus_service(service)
415        return self.gatt_server.add_service(service)
416
417    def is_encrypted(self, address):
418        connection_state = self.adapter_client.get_connection_state(address)
419        if connection_state is None:
420            logging.error('Failed to get connection state for address: %s', address)
421            return False
422        return connection_state > floss_enums.BtConnectionState.CONNECTED_ONLY
423
424    def listen_using_l2cap_channel(self):
425        return self.socket_manager.listen_using_l2cap_channel()
426
427    def listen_using_insecure_l2cap_channel(self):
428        return self.socket_manager.listen_using_insecure_l2cap_channel()
429
430    def create_l2cap_channel(self, address, psm):
431        name = self.adapter_client.get_remote_property(address, 'Name')
432        device = self.socket_manager.make_dbus_device(address, name)
433        return self.socket_manager.create_l2cap_channel(device, psm)
434
435    def create_insecure_l2cap_channel(self, address, psm):
436        name = self.adapter_client.get_remote_property(address, 'Name')
437        device = self.socket_manager.make_dbus_device(address, name)
438        return self.socket_manager.create_insecure_l2cap_channel(device, psm)
439
440    def accept_socket(self, socket_id, timeout_ms=None):
441        return self.socket_manager.accept(socket_id, timeout_ms)
442
443    def create_insecure_rfcomm_socket_to_service_record(self, address, uuid):
444        name = self.adapter_client.get_remote_property(address, 'Name')
445        device = self.socket_manager.make_dbus_device(address, name)
446        return self.socket_manager.create_insecure_rfcomm_socket_to_service_record(device, uuid)
447
448    def listen_using_insecure_rfcomm_with_service_record(self, name, uuid):
449        return self.socket_manager.listen_using_insecure_rfcomm_with_service_record(name, uuid)
450
451    def close_socket(self, socket_id):
452        return self.socket_manager.close(socket_id)
453
454    def get_connected_audio_devices(self):
455        return self.media_client.devices
456
457    def disconnect_media(self, address):
458        return self.media_client.disconnect(address)
459
460    def incoming_call(self, number):
461        return self.telephony_client.incoming_call(number)
462