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