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"""Client class to access the Floss QA interface."""
15
16import logging
17
18from floss.pandora.floss import observer_base
19from floss.pandora.floss import utils
20
21
22class BluetoothQACallbacks:
23    """Callbacks for the QA Interface.
24
25    Implement this to observe these callbacks when exporting callbacks via register_callback.
26    """
27
28    def on_fetch_discoverable_mode_completed(self, disc_mode):
29        """Called when fetch discoverable mode completed.
30
31        Args:
32            disc_mode: BtDiscMode.
33        """
34        pass
35
36    def on_fetch_connectable_completed(self, connectable):
37        """Called when fetch connectable completed.
38
39        Args:
40            connectable: A boolean value indicates whether connectable enabled or disabled.
41        """
42        pass
43
44    def on_set_connectable_completed(self, succeed):
45        """Called when set connectable completed.
46
47        Args:
48            succeed: A boolean value indicates whether the operation is succeeded.
49        """
50        pass
51
52    def on_fetch_alias_completed(self, alias):
53        """Called when fetch alias completed.
54
55        Args:
56            alias: Alias value as string.
57        """
58        pass
59
60    def on_get_hid_report_completed(self, status):
61        """Called when get hid report completed.
62
63        Args:
64            status: BtStatus.
65        """
66        pass
67
68    def on_set_hid_report_completed(self, status):
69        """Called when set hid report completed.
70
71        Args:
72            status: BtStatus.
73        """
74        pass
75
76    def on_send_hid_data_completed(self, status):
77        """Called when send hid data completed.
78
79        Args:
80            status: BtStatus.
81        """
82        pass
83
84
85class FlossQAClient(BluetoothQACallbacks):
86    """Handles method calls to and callbacks from the QA interface."""
87
88    QA_SERVICE = 'org.chromium.bluetooth'
89    QA_INTERFACE = 'org.chromium.bluetooth.BluetoothQA'
90    QA_OBJECT_PATTERN = '/org/chromium/bluetooth/hci{}/qa'
91    QA_CB_INTF = 'org.chromium.bluetooth.QACallback'
92    QA_CB_OBJ_NAME = 'test_qa_client'
93
94    class ExportedQACallbacks(observer_base.ObserverBase):
95        """
96        <node>
97            <interface name="org.chromium.bluetooth.QACallback">
98                <method name="OnFetchDiscoverableModeComplete">
99                    <arg type="u" name="disc_mode" direction="in" />
100                </method>
101                <method name="OnFetchConnectableComplete">
102                    <arg type="b" name="connectable" direction="in" />
103                </method>
104                <method name="OnSetConnectableComplete">
105                    <arg type="b" name="succeed" direction="in" />
106                </method>
107                <method name="OnFetchAliasComplete">
108                    <arg type="s" name="alias" direction="in" />
109                </method>
110                <method name="OnGetHIDReportComplete">
111                    <arg type="u" name="status" direction="in" />
112                </method>
113                <method name="OnSetHIDReportComplete">
114                    <arg type="u" name="status" direction="in" />
115                </method>
116                <method name="OnSendHIDDataComplete">
117                    <arg type="u" name="status" direction="in" />
118                </method>
119            </interface>
120        </node>
121        """
122
123        def __init__(self):
124            """Constructs exported callbacks object."""
125            observer_base.ObserverBase.__init__(self)
126
127        def OnFetchDiscoverableModeComplete(self, disc_mode):
128            """Handles fetch discoverable mode complete callback.
129
130            Args:
131                disc_mode: BtDiscMode.
132            """
133            for observer in self.observers.values():
134                observer.on_fetch_discoverable_mode_completed(disc_mode)
135
136        def OnFetchConnectableComplete(self, connectable):
137            """Handles fetch connectable complete callback.
138
139            Args:
140                connectable: A boolean value indicates whether connectable enabled or disabled.
141            """
142            for observer in self.observers.values():
143                observer.on_fetch_connectable_completed(connectable)
144
145        def OnSetConnectableComplete(self, succeed):
146            """Handles set connectable complete callback.
147
148            Args:
149                succeed: A boolean value indicates whether the operation is succeeded.
150            """
151            for observer in self.observers.values():
152                observer.on_set_connectable_completed(succeed)
153
154        def OnFetchAliasComplete(self, alias):
155            """Handles fetch alias complete callback.
156
157            Args:
158                alias: Alias value as string.
159            """
160            for observer in self.observers.values():
161                observer.on_fetch_alias_completed(alias)
162
163        def OnGetHIDReportComplete(self, status):
164            """Handles get HID report complete callback.
165
166            Args:
167                status: BtStatus.
168            """
169            for observer in self.observers.values():
170                observer.on_get_hid_report_completed(status)
171
172        def OnSetHIDReportComplete(self, status):
173            """Handles set HID report complete callback.
174
175            Args:
176                status: BtStatus.
177            """
178            for observer in self.observers.values():
179                observer.on_set_hid_report_completed(status)
180
181        def OnSendHIDDataComplete(self, status):
182            """Handles send HID data complete callback.
183
184            Args:
185                status: BtStatus.
186            """
187            for observer in self.observers.values():
188                observer.on_send_hid_data_completed(status)
189
190    def __init__(self, bus, hci):
191        """Constructs the client.
192
193        Args:
194            bus: D-Bus bus over which we'll establish connections.
195            hci: HCI adapter index. Get this value from `get_default_adapter` on FlossManagerClient.
196        """
197        self.bus = bus
198        self.hci = hci
199        self.objpath = self.QA_OBJECT_PATTERN.format(hci)
200
201        # We don't register callbacks by default.
202        self.callbacks = None
203        self.callback_id = None
204
205    def __del__(self):
206        """Destructor."""
207        del self.callbacks
208
209    @utils.glib_callback()
210    def on_fetch_discoverable_mode_completed(self, disc_mode):
211        """Handles fetch discoverable mode completed callback.
212
213        Args:
214            disc_mode: BtDiscMode.
215        """
216        logging.debug('on_fetch_discoverable_mode_completed: disc_mode: %s', disc_mode)
217
218    @utils.glib_callback()
219    def on_fetch_connectable_completed(self, connectable):
220        """Handles fetch connectable completed callback.
221
222        Args:
223            connectable: A boolean value indicates whether connectable enabled or disabled.
224        """
225        logging.debug('on_fetch_connectable_completed: connectable: %s', connectable)
226
227    @utils.glib_callback()
228    def on_set_connectable_completed(self, succeed):
229        """Handles set connectable completed callback.
230
231        Args:
232             succeed: A boolean value indicates whether the operation is succeeded.
233        """
234        logging.debug('on_set_connectable_completed: succeed: %s', succeed)
235
236    @utils.glib_callback()
237    def on_fetch_alias_completed(self, alias):
238        """Handles fetch alias completed callback.
239
240        Args:
241            alias: Alias value as string.
242        """
243        logging.debug('on_fetch_alias_completed: alias: %s', alias)
244
245    @utils.glib_callback()
246    def on_get_hid_report_completed(self, status):
247        """Handles get HID report completed callback.
248
249        Args:
250            status: BtStatus.
251        """
252        logging.debug('on_get_hid_report_completed: status: %s', status)
253
254    @utils.glib_callback()
255    def on_set_hid_report_completed(self, status):
256        """Handles set HID report completed callback.
257
258        Args:
259            status: BtStatus.
260        """
261        logging.debug('on_set_hid_report_completed: status: %s', status)
262
263    @utils.glib_callback()
264    def on_send_hid_data_completed(self, status):
265        """Handles send HID data completed callback.
266
267        Args:
268            status: BtStatus.
269        """
270        logging.debug('on_send_hid_data_completed: status: %s', status)
271
272    @utils.glib_call(False)
273    def has_proxy(self):
274        """Checks whether QA proxy can be acquired."""
275        return bool(self.proxy())
276
277    def proxy(self):
278        """Gets proxy object to QA interface for method calls."""
279        return self.bus.get(self.QA_SERVICE, self.objpath)[self.QA_INTERFACE]
280
281    @utils.glib_call(False)
282    def register_qa_callback(self):
283        """Registers QA callbacks if it doesn't exist."""
284
285        if self.callbacks:
286            return True
287
288        # Create and publish callbacks
289        self.callbacks = self.ExportedQACallbacks()
290        self.callbacks.add_observer('QA_client', self)
291        objpath = utils.generate_dbus_cb_objpath(self.QA_CB_OBJ_NAME, self.hci)
292        self.bus.register_object(objpath, self.callbacks, None)
293
294        # Register published callbacks with QA daemon
295        self.callback_id = self.proxy().RegisterQACallback(objpath)
296        return True
297
298    @utils.glib_call(False)
299    def unregister_qa_callback(self):
300        """Unregisters QA callbacks for this client.
301
302        Returns:
303            True on success, False otherwise.
304        """
305        return self.proxy().UnregisterQACallback(self.callback_id)
306
307    def register_callback_observer(self, name, observer):
308        """Add an observer for all callbacks.
309
310        Args:
311            name: Name of the observer.
312            observer: Observer that implements all callback classes.
313        """
314        if isinstance(observer, BluetoothQACallbacks):
315            self.callbacks.add_observer(name, observer)
316
317    def unregister_callback_observer(self, name, observer):
318        """Remove an observer for all callbacks.
319
320        Args:
321            name: Name of the observer.
322            observer: Observer that implements all callback classes.
323        """
324        if isinstance(observer, BluetoothQACallbacks):
325            self.callbacks.remove_observer(name, observer)
326
327    @utils.glib_call(False)
328    def add_media_player(self, name, browsing_supported):
329        """Adds media player.
330
331        Args:
332            name: Media player name.
333            browsing_supported: A boolean value indicates whether browsing_supported or not.
334
335        Returns:
336            True on success, False otherwise.
337        """
338        self.proxy().AddMediaPlayer(name, browsing_supported)
339        return True
340
341    @utils.glib_call(False)
342    def rfcomm_send_msc(self, dlci, addr):
343        """Sends MSC command over RFCOMM to the remote device.
344
345        Args:
346            dlci: The Data Link Control Identifier (DLCI) for the RFCOMM channel.
347            addr: The Bluetooth address of the remote device.
348
349        Returns:
350            True on success, False otherwise.
351        """
352        self.proxy().RfcommSendMsc(dlci, addr)
353        return True
354
355    @utils.glib_call(False)
356    def fetch_discoverable_mode(self):
357        """Fetches discoverable mode.
358
359        Returns:
360            True on success, False otherwise.
361        """
362        self.proxy().FetchDiscoverableMode()
363        return True
364
365    @utils.glib_call(False)
366    def fetch_connectable(self):
367        """Fetches connectable.
368
369        Returns:
370            True on success, False otherwise.
371        """
372        self.proxy().FetchConnectable()
373        return True
374
375    @utils.glib_call(False)
376    def set_connectable(self, mode):
377        """Sets connectable mode.
378
379        Args:
380            mode: A boolean value indicates whether connectable mode enabled or disabled.
381
382        Returns:
383            True on success, False otherwise.
384        """
385        self.proxy().SetConnectable(mode)
386        return True
387
388    @utils.glib_call(False)
389    def fetch_alias(self):
390        """Fetches alias.
391
392        Returns:
393            True on success, False otherwise.
394        """
395        self.proxy().FetchAlias()
396        return True
397
398    @utils.glib_call(None)
399    def get_modalias(self):
400        """Gets modalias.
401
402        Returns:
403            Modalias value on success, None otherwise.
404        """
405        return self.proxy().GetModalias()
406
407    @utils.glib_call(False)
408    def get_hid_report(self, addr, report_type, report_id):
409        """Gets HID report on the remote device.
410
411        Args:
412            addr: The Bluetooth address of the remote device.
413            report_type: The type of HID report.
414            report_id: The id of HID report.
415
416        Returns:
417            True on success, False otherwise.
418        """
419        self.proxy().GetHIDReport(addr, report_type, report_id)
420        return True
421
422    @utils.glib_call(False)
423    def set_hid_report(self, addr, report_type, report):
424        """Sets HID report to the remote device.
425
426        Args:
427            addr: The Bluetooth address of the remote device.
428            report_type: The type of HID report.
429            report: The HID report to be set.
430
431        Returns:
432            True on success, False otherwise.
433        """
434        self.proxy().SetHIDReport(addr, report_type, report)
435        return True
436
437    @utils.glib_call(False)
438    def send_hid_data(self, addr, data):
439        """Sends HID report data to the remote device.
440
441        Args:
442            addr: The Bluetooth address of the remote device.
443            data: The HID report data to be sent.
444
445        Returns:
446            True on success, False otherwise.
447        """
448        self.proxy().SendHIDData(addr, data)
449        return True
450