Home | History | Annotate | Download | only in bluetooth
      1 #!/usr/bin/env python
      2 
      3 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 import dbus
      8 import dbus.mainloop.glib
      9 import dbus.service
     10 import gobject
     11 import json
     12 import logging
     13 import logging.handlers
     14 import os
     15 import shutil
     16 import time
     17 
     18 import common
     19 from autotest_lib.client.bin import utils
     20 from autotest_lib.client.common_lib.cros.bluetooth import bluetooth_socket
     21 from autotest_lib.client.cros import constants
     22 from autotest_lib.client.cros import xmlrpc_server
     23 from autotest_lib.client.cros.bluetooth import advertisement
     24 from autotest_lib.client.cros.bluetooth import output_recorder
     25 
     26 
     27 class PairingAgent(dbus.service.Object):
     28     """The agent handling the authentication process of bluetooth pairing.
     29 
     30     PairingAgent overrides RequestPinCode method to return a given pin code.
     31     User can use this agent to pair bluetooth device which has a known
     32     pin code.
     33 
     34     TODO (josephsih): more pairing modes other than pin code would be
     35     supported later.
     36 
     37     """
     38 
     39     def __init__(self, pin, *args, **kwargs):
     40         super(PairingAgent, self).__init__(*args, **kwargs)
     41         self._pin = pin
     42 
     43 
     44     @dbus.service.method('org.bluez.Agent1',
     45                          in_signature='o', out_signature='s')
     46     def RequestPinCode(self, device_path):
     47         """Requests pin code for a device.
     48 
     49         Returns the known pin code for the request.
     50 
     51         @param device_path: The object path of the device.
     52 
     53         @returns: The known pin code.
     54 
     55         """
     56         logging.info('RequestPinCode for %s; return %s', device_path, self._pin)
     57         return self._pin
     58 
     59 
     60 class BluetoothDeviceXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate):
     61     """Exposes DUT methods called remotely during Bluetooth autotests.
     62 
     63     All instance methods of this object without a preceding '_' are exposed via
     64     an XML-RPC server. This is not a stateless handler object, which means that
     65     if you store state inside the delegate, that state will remain around for
     66     future calls.
     67     """
     68 
     69     UPSTART_PATH = 'unix:abstract=/com/ubuntu/upstart'
     70     UPSTART_MANAGER_PATH = '/com/ubuntu/Upstart'
     71     UPSTART_MANAGER_IFACE = 'com.ubuntu.Upstart0_6'
     72     UPSTART_JOB_IFACE = 'com.ubuntu.Upstart0_6.Job'
     73 
     74     UPSTART_ERROR_UNKNOWNINSTANCE = \
     75             'com.ubuntu.Upstart0_6.Error.UnknownInstance'
     76     UPSTART_ERROR_ALREADYSTARTED = \
     77             'com.ubuntu.Upstart0_6.Error.AlreadyStarted'
     78 
     79     BLUETOOTHD_JOB = 'bluetoothd'
     80 
     81     DBUS_ERROR_SERVICEUNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown'
     82 
     83     BLUEZ_SERVICE_NAME = 'org.bluez'
     84     BLUEZ_MANAGER_PATH = '/'
     85     BLUEZ_MANAGER_IFACE = 'org.freedesktop.DBus.ObjectManager'
     86     BLUEZ_ADAPTER_IFACE = 'org.bluez.Adapter1'
     87     BLUEZ_DEVICE_IFACE = 'org.bluez.Device1'
     88     BLUEZ_LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
     89     BLUEZ_AGENT_MANAGER_PATH = '/org/bluez'
     90     BLUEZ_AGENT_MANAGER_IFACE = 'org.bluez.AgentManager1'
     91     BLUEZ_PROFILE_MANAGER_PATH = '/org/bluez'
     92     BLUEZ_PROFILE_MANAGER_IFACE = 'org.bluez.ProfileManager1'
     93     BLUEZ_ERROR_ALREADY_EXISTS = 'org.bluez.Error.AlreadyExists'
     94     DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
     95     AGENT_PATH = '/test/agent'
     96 
     97     BLUETOOTH_LIBDIR = '/var/lib/bluetooth'
     98     BTMON_STOP_DELAY_SECS = 3
     99 
    100     # Timeout for how long we'll wait for BlueZ and the Adapter to show up
    101     # after reset.
    102     ADAPTER_TIMEOUT = 30
    103 
    104     def __init__(self):
    105         super(BluetoothDeviceXmlRpcDelegate, self).__init__()
    106 
    107         # Open the Bluetooth Raw socket to the kernel which provides us direct,
    108         # raw, access to the HCI controller.
    109         self._raw = bluetooth_socket.BluetoothRawSocket()
    110 
    111         # Open the Bluetooth Control socket to the kernel which provides us
    112         # raw management access to the Bluetooth Host Subsystem. Read the list
    113         # of adapter indexes to determine whether or not this device has a
    114         # Bluetooth Adapter or not.
    115         self._control = bluetooth_socket.BluetoothControlSocket()
    116         self._has_adapter = len(self._control.read_index_list()) > 0
    117 
    118         # Set up the connection to Upstart so we can start and stop services
    119         # and fetch the bluetoothd job.
    120         self._upstart_conn = dbus.connection.Connection(self.UPSTART_PATH)
    121         self._upstart = self._upstart_conn.get_object(
    122                 None,
    123                 self.UPSTART_MANAGER_PATH)
    124 
    125         bluetoothd_path = self._upstart.GetJobByName(
    126                 self.BLUETOOTHD_JOB,
    127                 dbus_interface=self.UPSTART_MANAGER_IFACE)
    128         self._bluetoothd = self._upstart_conn.get_object(
    129                 None,
    130                 bluetoothd_path)
    131 
    132         # Arrange for the GLib main loop to be the default.
    133         dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    134 
    135         # Set up the connection to the D-Bus System Bus, get the object for
    136         # the Bluetooth Userspace Daemon (BlueZ) and that daemon's object for
    137         # the Bluetooth Adapter, and the advertising manager.
    138         self._system_bus = dbus.SystemBus()
    139         self._update_bluez()
    140         self._update_adapter()
    141         self._update_advertising()
    142 
    143         # The agent to handle pin code request, which will be
    144         # created when user calls pair_legacy_device method.
    145         self._pairing_agent = None
    146         # The default capability of the agent.
    147         self._capability = 'KeyboardDisplay'
    148 
    149         # Initailize a btmon object to record bluetoothd's activity.
    150         self.btmon = output_recorder.OutputRecorder(
    151                 'btmon', stop_delay_secs=self.BTMON_STOP_DELAY_SECS)
    152 
    153         self.advertisements = []
    154         self._adv_mainloop = gobject.MainLoop()
    155 
    156 
    157     @xmlrpc_server.dbus_safe(False)
    158     def start_bluetoothd(self):
    159         """start bluetoothd.
    160 
    161         This includes powering up the adapter.
    162 
    163         @returns: True if bluetoothd is started correctly.
    164                   False otherwise.
    165 
    166         """
    167         try:
    168             self._bluetoothd.Start(dbus.Array(signature='s'), True,
    169                                    dbus_interface=self.UPSTART_JOB_IFACE)
    170         except dbus.exceptions.DBusException as e:
    171             # if bluetoothd was already started, the exception looks like
    172             #     dbus.exceptions.DBusException:
    173             #     com.ubuntu.Upstart0_6.Error.AlreadyStarted: Job is already
    174             #     running: bluetoothd
    175             if e.get_dbus_name() != self.UPSTART_ERROR_ALREADYSTARTED:
    176                 logging.error('Error starting bluetoothd: %s', e)
    177                 return False
    178 
    179         logging.debug('waiting for bluez start')
    180         try:
    181             utils.poll_for_condition(
    182                     condition=self._update_bluez,
    183                     desc='Bluetooth Daemon has started.',
    184                     timeout=self.ADAPTER_TIMEOUT)
    185         except Exception as e:
    186             logging.error('timeout: error starting bluetoothd: %s', e)
    187             return False
    188 
    189         # Waiting for the self._adapter object.
    190         # This does not mean that the adapter is powered on.
    191         logging.debug('waiting for bluez to obtain adapter information')
    192         try:
    193             utils.poll_for_condition(
    194                     condition=self._update_adapter,
    195                     desc='Bluetooth Daemon has adapter information.',
    196                     timeout=self.ADAPTER_TIMEOUT)
    197         except Exception as e:
    198             logging.error('timeout: error starting adapter: %s', e)
    199             return False
    200 
    201         # Waiting for the self._advertising interface object.
    202         logging.debug('waiting for bluez to obtain interface manager.')
    203         try:
    204             utils.poll_for_condition(
    205                     condition=self._update_advertising,
    206                     desc='Bluetooth Daemon has advertising interface.',
    207                     timeout=self.ADAPTER_TIMEOUT)
    208         except utils.TimeoutError:
    209             logging.error('timeout: error getting advertising interface')
    210             return False
    211 
    212         return True
    213 
    214 
    215     @xmlrpc_server.dbus_safe(False)
    216     def stop_bluetoothd(self):
    217         """stop bluetoothd.
    218 
    219         @returns: True if bluetoothd is stopped correctly.
    220                   False otherwise.
    221 
    222         """
    223         def bluez_stopped():
    224             """Checks the bluetooth daemon status.
    225 
    226             @returns: True if bluez is stopped. False otherwise.
    227 
    228             """
    229             return not self._update_bluez()
    230 
    231         try:
    232             self._bluetoothd.Stop(dbus.Array(signature='s'), True,
    233                                   dbus_interface=self.UPSTART_JOB_IFACE)
    234         except dbus.exceptions.DBusException as e:
    235             # If bluetoothd was stopped already, the exception looks like
    236             #    dbus.exceptions.DBusException:
    237             #    com.ubuntu.Upstart0_6.Error.UnknownInstance: Unknown instance:
    238             if e.get_dbus_name() != self.UPSTART_ERROR_UNKNOWNINSTANCE:
    239                 logging.error('Error stopping bluetoothd!')
    240                 return False
    241 
    242         logging.debug('waiting for bluez stop')
    243         try:
    244             utils.poll_for_condition(
    245                     condition=bluez_stopped,
    246                     desc='Bluetooth Daemon has stopped.',
    247                     timeout=self.ADAPTER_TIMEOUT)
    248             bluetoothd_stopped = True
    249         except Exception as e:
    250             logging.error('timeout: error stopping bluetoothd: %s', e)
    251             bluetoothd_stopped = False
    252 
    253         return bluetoothd_stopped
    254 
    255 
    256     def is_bluetoothd_running(self):
    257         """Is bluetoothd running?
    258 
    259         @returns: True if bluetoothd is running
    260 
    261         """
    262         return bool(self._get_dbus_proxy_for_bluetoothd())
    263 
    264 
    265     def _update_bluez(self):
    266         """Store a D-Bus proxy for the Bluetooth daemon in self._bluez.
    267 
    268         This may be called in a loop until it returns True to wait for the
    269         daemon to be ready after it has been started.
    270 
    271         @return True on success, False otherwise.
    272 
    273         """
    274         self._bluez = self._get_dbus_proxy_for_bluetoothd()
    275         return bool(self._bluez)
    276 
    277 
    278     @xmlrpc_server.dbus_safe(False)
    279     def _get_dbus_proxy_for_bluetoothd(self):
    280         """Get the D-Bus proxy for the Bluetooth daemon.
    281 
    282         @return True on success, False otherwise.
    283 
    284         """
    285         bluez = None
    286         try:
    287             bluez = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
    288                                                 self.BLUEZ_MANAGER_PATH)
    289             logging.debug('bluetoothd is running')
    290         except dbus.exceptions.DBusException as e:
    291             # When bluetoothd is not running, the exception looks like
    292             #     dbus.exceptions.DBusException:
    293             #     org.freedesktop.DBus.Error.ServiceUnknown: The name org.bluez
    294             #     was not provided by any .service files
    295             if e.get_dbus_name() == self.DBUS_ERROR_SERVICEUNKNOWN:
    296                 logging.debug('bluetoothd is not running')
    297             else:
    298                 logging.error('Error getting dbus proxy for Bluez: %s', e)
    299         return bluez
    300 
    301 
    302     def _update_adapter(self):
    303         """Store a D-Bus proxy for the local adapter in self._adapter.
    304 
    305         This may be called in a loop until it returns True to wait for the
    306         daemon to be ready, and have obtained the adapter information itself,
    307         after it has been started.
    308 
    309         Since not all devices will have adapters, this will also return True
    310         in the case where we have obtained an empty adapter index list from the
    311         kernel.
    312 
    313         Note that this method does not power on the adapter.
    314 
    315         @return True on success, including if there is no local adapter,
    316             False otherwise.
    317 
    318         """
    319         self._adapter = None
    320         if self._bluez is None:
    321             logging.warning('Bluez not found!')
    322             return False
    323         if not self._has_adapter:
    324             logging.debug('Device has no adapter; returning')
    325             return True
    326         self._adapter = self._get_adapter()
    327         return bool(self._adapter)
    328 
    329     def _update_advertising(self):
    330         """Store a D-Bus proxy for the local advertising interface manager.
    331 
    332         This may be called repeatedly in a loop until True is returned;
    333         otherwise we wait for bluetoothd to start. After bluetoothd starts, we
    334         check the existence of a local adapter and proceed to get the
    335         advertisement interface manager.
    336 
    337         Since not all devices will have adapters, this will also return True
    338         in the case where there is no adapter.
    339 
    340         @return True on success, including if there is no local adapter,
    341                 False otherwise.
    342 
    343         """
    344         self._advertising = None
    345         if self._bluez is None:
    346             logging.warning('Bluez not found!')
    347             return False
    348         if not self._has_adapter:
    349             logging.debug('Device has no adapter; returning')
    350             return True
    351         self._advertising = self._get_advertising()
    352         return bool(self._advertising)
    353 
    354 
    355     @xmlrpc_server.dbus_safe(False)
    356     def _get_adapter(self):
    357         """Get the D-Bus proxy for the local adapter.
    358 
    359         @return the adapter on success. None otherwise.
    360 
    361         """
    362         objects = self._bluez.GetManagedObjects(
    363                 dbus_interface=self.BLUEZ_MANAGER_IFACE)
    364         for path, ifaces in objects.iteritems():
    365             logging.debug('%s -> %r', path, ifaces.keys())
    366             if self.BLUEZ_ADAPTER_IFACE in ifaces:
    367                 logging.debug('using adapter %s', path)
    368                 adapter = self._system_bus.get_object(
    369                         self.BLUEZ_SERVICE_NAME,
    370                         path)
    371                 return adapter
    372         else:
    373             logging.warning('No adapter found in interface!')
    374             return None
    375 
    376 
    377     @xmlrpc_server.dbus_safe(False)
    378     def _get_advertising(self):
    379         """Get the D-Bus proxy for the local advertising interface.
    380 
    381         @return the advertising interface object.
    382 
    383         """
    384         return dbus.Interface(self._adapter,
    385                               self.BLUEZ_LE_ADVERTISING_MANAGER_IFACE)
    386 
    387 
    388     @xmlrpc_server.dbus_safe(False)
    389     def reset_on(self):
    390         """Reset the adapter and settings and power up the adapter.
    391 
    392         @return True on success, False otherwise.
    393 
    394         """
    395         return self._reset(set_power=True)
    396 
    397 
    398     @xmlrpc_server.dbus_safe(False)
    399     def reset_off(self):
    400         """Reset the adapter and settings, leave the adapter powered off.
    401 
    402         @return True on success, False otherwise.
    403 
    404         """
    405         return self._reset(set_power=False)
    406 
    407 
    408     def has_adapter(self):
    409         """Return if an adapter is present.
    410 
    411         This will only return True if we have determined both that there is
    412         a Bluetooth adapter on this device (kernel adapter index list is not
    413         empty) and that the Bluetooth daemon has exported an object for it.
    414 
    415         @return True if an adapter is present, False if not.
    416 
    417         """
    418         return self._has_adapter and self._adapter is not None
    419 
    420 
    421     def _reset(self, set_power=False):
    422         """Reset the Bluetooth adapter and settings.
    423 
    424         @param set_power: adapter power state to set (True or False).
    425 
    426         @return True on success, False otherwise.
    427 
    428         """
    429         logging.debug('_reset')
    430 
    431         # Power off the adapter before stopping the bluetoothd.
    432         if self._adapter and not set_power:
    433             self._set_powered(False)
    434 
    435         # Stop bluetoothd.
    436         if not self.stop_bluetoothd():
    437             return False
    438 
    439         # Remove the settings and cached devices.
    440         try:
    441             for subdir in os.listdir(self.BLUETOOTH_LIBDIR):
    442                 shutil.rmtree(os.path.join(self.BLUETOOTH_LIBDIR, subdir))
    443             remove_settings = True
    444         except Exception as e:
    445             logging.error('Error in removing subdirs in %s: %s.',
    446                           self.BLUETOOTH_LIBDIR, e)
    447             remove_settings = False
    448 
    449         # Start bluetoothd.
    450         if not self.start_bluetoothd():
    451             return False
    452 
    453         # Power on the adapter after restarting the bluetoothd.
    454         if self._adapter and set_power:
    455             self._set_powered(True)
    456 
    457         return remove_settings
    458 
    459     @xmlrpc_server.dbus_safe(False)
    460     def set_powered(self, powered):
    461         """Set the adapter power state.
    462 
    463         @param powered: adapter power state to set (True or False).
    464 
    465         @return True on success, False otherwise.
    466 
    467         """
    468         if not self._adapter:
    469             if not powered:
    470                 # Return success if we are trying to power off an adapter that's
    471                 # missing or gone away, since the expected result has happened.
    472                 return True
    473             else:
    474                 logging.warning('Adapter not found!')
    475                 return False
    476         self._set_powered(powered)
    477         return True
    478 
    479 
    480     @xmlrpc_server.dbus_safe(False)
    481     def _set_powered(self, powered):
    482         """Set the adapter power state.
    483 
    484         @param powered: adapter power state to set (True or False).
    485 
    486         """
    487         logging.debug('_set_powered %r', powered)
    488         self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 'Powered', powered,
    489                           dbus_interface=dbus.PROPERTIES_IFACE)
    490 
    491 
    492     @xmlrpc_server.dbus_safe(False)
    493     def set_discoverable(self, discoverable):
    494         """Set the adapter discoverable state.
    495 
    496         @param discoverable: adapter discoverable state to set (True or False).
    497 
    498         @return True on success, False otherwise.
    499 
    500         """
    501         if not discoverable and not self._adapter:
    502             # Return success if we are trying to make an adapter that's
    503             # missing or gone away, undiscoverable, since the expected result
    504             # has happened.
    505             return True
    506         self._adapter.Set(self.BLUEZ_ADAPTER_IFACE,
    507                           'Discoverable', discoverable,
    508                           dbus_interface=dbus.PROPERTIES_IFACE)
    509         return True
    510 
    511 
    512     @xmlrpc_server.dbus_safe(False)
    513     def set_pairable(self, pairable):
    514         """Set the adapter pairable state.
    515 
    516         @param pairable: adapter pairable state to set (True or False).
    517 
    518         @return True on success, False otherwise.
    519 
    520         """
    521         self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 'Pairable', pairable,
    522                           dbus_interface=dbus.PROPERTIES_IFACE)
    523         return True
    524 
    525 
    526     @xmlrpc_server.dbus_safe(False)
    527     def get_adapter_properties(self):
    528         """Read the adapter properties from the Bluetooth Daemon.
    529 
    530         @return the properties as a JSON-encoded dictionary on success,
    531             the value False otherwise.
    532 
    533         """
    534         if self._bluez:
    535             objects = self._bluez.GetManagedObjects(
    536                     dbus_interface=self.BLUEZ_MANAGER_IFACE)
    537             props = objects[self._adapter.object_path][self.BLUEZ_ADAPTER_IFACE]
    538         else:
    539             props = {}
    540         logging.debug('get_adapter_properties: %s', props)
    541         return json.dumps(props)
    542 
    543 
    544     def read_version(self):
    545         """Read the version of the management interface from the Kernel.
    546 
    547         @return the information as a JSON-encoded tuple of:
    548           ( version, revision )
    549 
    550         """
    551         return json.dumps(self._control.read_version())
    552 
    553 
    554     def read_supported_commands(self):
    555         """Read the set of supported commands from the Kernel.
    556 
    557         @return the information as a JSON-encoded tuple of:
    558           ( commands, events )
    559 
    560         """
    561         return json.dumps(self._control.read_supported_commands())
    562 
    563 
    564     def read_index_list(self):
    565         """Read the list of currently known controllers from the Kernel.
    566 
    567         @return the information as a JSON-encoded array of controller indexes.
    568 
    569         """
    570         return json.dumps(self._control.read_index_list())
    571 
    572 
    573     def read_info(self):
    574         """Read the adapter information from the Kernel.
    575 
    576         @return the information as a JSON-encoded tuple of:
    577           ( address, bluetooth_version, manufacturer_id,
    578             supported_settings, current_settings, class_of_device,
    579             name, short_name )
    580 
    581         """
    582         return json.dumps(self._control.read_info(0))
    583 
    584 
    585     def add_device(self, address, address_type, action):
    586         """Add a device to the Kernel action list.
    587 
    588         @param address: Address of the device to add.
    589         @param address_type: Type of device in @address.
    590         @param action: Action to take.
    591 
    592         @return on success, a JSON-encoded typle of:
    593           ( address, address_type ), None on failure.
    594 
    595         """
    596         return json.dumps(self._control.add_device(
    597                 0, address, address_type, action))
    598 
    599 
    600     def remove_device(self, address, address_type):
    601         """Remove a device from the Kernel action list.
    602 
    603         @param address: Address of the device to remove.
    604         @param address_type: Type of device in @address.
    605 
    606         @return on success, a JSON-encoded typle of:
    607           ( address, address_type ), None on failure.
    608 
    609         """
    610         return json.dumps(self._control.remove_device(
    611                 0, address, address_type))
    612 
    613 
    614     @xmlrpc_server.dbus_safe(False)
    615     def get_devices(self):
    616         """Read information about remote devices known to the adapter.
    617 
    618         @return the properties of each device as a JSON-encoded array of
    619             dictionaries on success, the value False otherwise.
    620 
    621         """
    622         objects = self._bluez.GetManagedObjects(
    623                 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True)
    624         devices = []
    625         for path, ifaces in objects.iteritems():
    626             if self.BLUEZ_DEVICE_IFACE in ifaces:
    627                 devices.append(objects[path][self.BLUEZ_DEVICE_IFACE])
    628         return json.dumps(devices)
    629 
    630 
    631     @xmlrpc_server.dbus_safe(False)
    632     def get_device_by_address(self, address):
    633         """Read information about the remote device with the specified address.
    634 
    635         @param address: Address of the device to get.
    636 
    637         @return the properties of the device as a JSON-encoded dictionary
    638             on success, the value False otherwise.
    639 
    640         """
    641         objects = self._bluez.GetManagedObjects(
    642                 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True)
    643         devices = []
    644         for path, ifaces in objects.iteritems():
    645             if self.BLUEZ_DEVICE_IFACE in ifaces:
    646                 device = objects[path][self.BLUEZ_DEVICE_IFACE]
    647                 if device.get('Address') == address:
    648                     return json.dumps(device)
    649 
    650         devices = json.loads(self.get_devices())
    651         for device in devices:
    652             if device.get['Address'] == address:
    653                 return json.dumps(device)
    654         return json.dumps(dict())
    655 
    656 
    657     @xmlrpc_server.dbus_safe(False)
    658     def start_discovery(self):
    659         """Start discovery of remote devices.
    660 
    661         Obtain the discovered device information using get_devices(), called
    662         stop_discovery() when done.
    663 
    664         @return True on success, False otherwise.
    665 
    666         """
    667         if not self._adapter:
    668             return False
    669         self._adapter.StartDiscovery(dbus_interface=self.BLUEZ_ADAPTER_IFACE)
    670         return True
    671 
    672 
    673     @xmlrpc_server.dbus_safe(False)
    674     def stop_discovery(self):
    675         """Stop discovery of remote devices.
    676 
    677         @return True on success, False otherwise.
    678 
    679         """
    680         if not self._adapter:
    681             return False
    682         self._adapter.StopDiscovery(dbus_interface=self.BLUEZ_ADAPTER_IFACE)
    683         return True
    684 
    685 
    686     def get_dev_info(self):
    687         """Read raw HCI device information.
    688 
    689         @return JSON-encoded tuple of:
    690                 (index, name, address, flags, device_type, bus_type,
    691                        features, pkt_type, link_policy, link_mode,
    692                        acl_mtu, acl_pkts, sco_mtu, sco_pkts,
    693                        err_rx, err_tx, cmd_tx, evt_rx, acl_tx, acl_rx,
    694                        sco_tx, sco_rx, byte_rx, byte_tx) on success,
    695                 None on failure.
    696 
    697         """
    698         return json.dumps(self._raw.get_dev_info(0))
    699 
    700 
    701     @xmlrpc_server.dbus_safe(False)
    702     def register_profile(self, path, uuid, options):
    703         """Register new profile (service).
    704 
    705         @param path: Path to the profile object.
    706         @param uuid: Service Class ID of the service as string.
    707         @param options: Dictionary of options for the new service, compliant
    708                         with BlueZ D-Bus Profile API standard.
    709 
    710         @return True on success, False otherwise.
    711 
    712         """
    713         profile_manager = dbus.Interface(
    714                               self._system_bus.get_object(
    715                                   self.BLUEZ_SERVICE_NAME,
    716                                   self.BLUEZ_PROFILE_MANAGER_PATH),
    717                               self.BLUEZ_PROFILE_MANAGER_IFACE)
    718         profile_manager.RegisterProfile(path, uuid, options)
    719         return True
    720 
    721 
    722     def has_device(self, address):
    723         """Checks if the device with a given address exists.
    724 
    725         @param address: Address of the device.
    726 
    727         @returns: True if there is an interface object with that address.
    728                   False if the device is not found.
    729 
    730         @raises: Exception if a D-Bus error is encountered.
    731 
    732         """
    733         result = self._find_device(address)
    734         logging.debug('has_device result: %s', str(result))
    735 
    736         # The result being False indicates that there is a D-Bus error.
    737         if result is False:
    738             raise Exception('dbus.Interface error')
    739 
    740         # Return True if the result is not None, e.g. a D-Bus interface object;
    741         # False otherwise.
    742         return bool(result)
    743 
    744 
    745     @xmlrpc_server.dbus_safe(False)
    746     def _find_device(self, address):
    747         """Finds the device with a given address.
    748 
    749         Find the device with a given address and returns the
    750         device interface.
    751 
    752         @param address: Address of the device.
    753 
    754         @returns: An 'org.bluez.Device1' interface to the device.
    755                   None if device can not be found.
    756 
    757         """
    758         objects = self._bluez.GetManagedObjects(
    759                 dbus_interface=self.BLUEZ_MANAGER_IFACE)
    760         for path, ifaces in objects.iteritems():
    761             device = ifaces.get(self.BLUEZ_DEVICE_IFACE)
    762             if device is None:
    763                 continue
    764             if (device['Address'] == address and
    765                 path.startswith(self._adapter.object_path)):
    766                 obj = self._system_bus.get_object(
    767                         self.BLUEZ_SERVICE_NAME, path)
    768                 return dbus.Interface(obj, self.BLUEZ_DEVICE_IFACE)
    769         logging.info('Device not found')
    770         return None
    771 
    772 
    773     @xmlrpc_server.dbus_safe(False)
    774     def _setup_pairing_agent(self, pin):
    775         """Initializes and resiters a PairingAgent to handle authenticaiton.
    776 
    777         @param pin: The pin code this agent will answer.
    778 
    779         """
    780         if self._pairing_agent:
    781             logging.info('Removing the old agent before initializing a new one')
    782             self._pairing_agent.remove_from_connection()
    783             self._pairing_agent = None
    784         self._pairing_agent= PairingAgent(pin, self._system_bus,
    785                                           self.AGENT_PATH)
    786         agent_manager = dbus.Interface(
    787                 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
    788                                             self.BLUEZ_AGENT_MANAGER_PATH),
    789                 self.BLUEZ_AGENT_MANAGER_IFACE)
    790         try:
    791             agent_manager.RegisterAgent(self.AGENT_PATH, self._capability)
    792         except dbus.exceptions.DBusException, e:
    793             if e.get_dbus_name() == self.BLUEZ_ERROR_ALREADY_EXISTS:
    794                 logging.info('Unregistering old agent and registering the new')
    795                 agent_manager.UnregisterAgent(self.AGENT_PATH)
    796                 agent_manager.RegisterAgent(self.AGENT_PATH, self._capability)
    797             else:
    798                 logging.error('Error setting up pin agent: %s', e)
    799                 raise
    800         logging.info('Agent registered: %s', self.AGENT_PATH)
    801 
    802 
    803     @xmlrpc_server.dbus_safe(False)
    804     def _is_paired(self,  device):
    805         """Checks if a device is paired.
    806 
    807         @param device: An 'org.bluez.Device1' interface to the device.
    808 
    809         @returns: True if device is paired. False otherwise.
    810 
    811         """
    812         props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
    813         paired = props.Get(self.BLUEZ_DEVICE_IFACE, 'Paired')
    814         return bool(paired)
    815 
    816 
    817     @xmlrpc_server.dbus_safe(False)
    818     def device_is_paired(self, address):
    819         """Checks if a device is paired.
    820 
    821         @param address: address of the device.
    822 
    823         @returns: True if device is paired. False otherwise.
    824 
    825         """
    826         device = self._find_device(address)
    827         if not device:
    828             logging.error('Device not found')
    829             return False
    830         return self._is_paired(device)
    831 
    832 
    833     @xmlrpc_server.dbus_safe(False)
    834     def _is_connected(self,  device):
    835         """Checks if a device is connected.
    836 
    837         @param device: An 'org.bluez.Device1' interface to the device.
    838 
    839         @returns: True if device is connected. False otherwise.
    840 
    841         """
    842         props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
    843         connected = props.Get(self.BLUEZ_DEVICE_IFACE, 'Connected')
    844         logging.info('Got connected = %r', connected)
    845         return bool(connected)
    846 
    847 
    848     @xmlrpc_server.dbus_safe(False)
    849     def _set_trusted_by_device(self, device, trusted=True):
    850         """Set the device trusted by device object.
    851 
    852         @param device: the device object to set trusted.
    853         @param trusted: True or False indicating whether to set trusted or not.
    854 
    855         @returns: True if successful. False otherwise.
    856 
    857         """
    858         try:
    859             properties = dbus.Interface(device, self.DBUS_PROP_IFACE)
    860             properties.Set(self.BLUEZ_DEVICE_IFACE, 'Trusted', trusted)
    861             return True
    862         except Exception as e:
    863             logging.error('_set_trusted_by_device: %s', e)
    864         except:
    865             logging.error('_set_trusted_by_device: unexpected error')
    866         return False
    867 
    868 
    869     @xmlrpc_server.dbus_safe(False)
    870     def _set_trusted_by_path(self, device_path, trusted=True):
    871         """Set the device trusted by the device path.
    872 
    873         @param device_path: the object path of the device.
    874         @param trusted: True or False indicating whether to set trusted or not.
    875 
    876         @returns: True if successful. False otherwise.
    877 
    878         """
    879         try:
    880             device = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
    881                                                  device_path)
    882             return self._set_trusted_by_device(device, trusted)
    883         except Exception as e:
    884             logging.error('_set_trusted_by_path: %s', e)
    885         except:
    886             logging.error('_set_trusted_by_path: unexpected error')
    887         return False
    888 
    889 
    890     @xmlrpc_server.dbus_safe(False)
    891     def set_trusted(self, address, trusted=True):
    892         """Set the device trusted by address.
    893 
    894         @param address: The bluetooth address of the device.
    895         @param trusted: True or False indicating whether to set trusted or not.
    896 
    897         @returns: True if successful. False otherwise.
    898 
    899         """
    900         try:
    901             device = self._find_device(address)
    902             return self._set_trusted_by_device(device, trusted)
    903         except Exception as e:
    904             logging.error('set_trusted: %s', e)
    905         except:
    906             logging.error('set_trusted: unexpected error')
    907         return False
    908 
    909 
    910     @xmlrpc_server.dbus_safe(False)
    911     def pair_legacy_device(self, address, pin, trusted, timeout=60):
    912         """Pairs a device with a given pin code.
    913 
    914         Registers a agent who handles pin code request and
    915         pairs a device with known pin code.
    916 
    917         Note that the adapter does not automatically connnect to the device
    918         when pairing is done. The connect_device() method has to be invoked
    919         explicitly to connect to the device. This provides finer control
    920         for testing purpose.
    921 
    922         @param address: Address of the device to pair.
    923         @param pin: The pin code of the device to pair.
    924         @param trusted: indicating whether to set the device trusted.
    925         @param timeout: The timeout in seconds for pairing.
    926 
    927         @returns: True on success. False otherwise.
    928 
    929         """
    930         device = self._find_device(address)
    931         if not device:
    932             logging.error('Device not found')
    933             return False
    934         if self._is_paired(device):
    935             logging.info('Device is already paired')
    936             return True
    937 
    938         device_path = device.object_path
    939         logging.info('Device %s is found.' % device.object_path)
    940 
    941         self._setup_pairing_agent(pin)
    942         mainloop = gobject.MainLoop()
    943 
    944 
    945         def pair_reply():
    946             """Handler when pairing succeeded."""
    947             logging.info('Device paired: %s', device_path)
    948             if trusted:
    949                 self._set_trusted_by_path(device_path, trusted=True)
    950                 logging.info('Device trusted: %s', device_path)
    951             mainloop.quit()
    952 
    953 
    954         def pair_error(error):
    955             """Handler when pairing failed.
    956 
    957             @param error: one of errors defined in org.bluez.Error representing
    958                           the error in pairing.
    959 
    960             """
    961             try:
    962                 error_name = error.get_dbus_name()
    963                 if error_name == 'org.freedesktop.DBus.Error.NoReply':
    964                     logging.error('Timed out after %d ms. Cancelling pairing.',
    965                                   timeout)
    966                     device.CancelPairing()
    967                 else:
    968                     logging.error('Pairing device failed: %s', error)
    969             finally:
    970                 mainloop.quit()
    971 
    972 
    973         device.Pair(reply_handler=pair_reply, error_handler=pair_error,
    974                     timeout=timeout * 1000)
    975         mainloop.run()
    976         return self._is_paired(device)
    977 
    978 
    979     @xmlrpc_server.dbus_safe(False)
    980     def remove_device_object(self, address):
    981         """Removes a device object and the pairing information.
    982 
    983         Calls RemoveDevice method to remove remote device
    984         object and the pairing information.
    985 
    986         @param address: Address of the device to unpair.
    987 
    988         @returns: True on success. False otherwise.
    989 
    990         """
    991         device = self._find_device(address)
    992         if not device:
    993             logging.error('Device not found')
    994             return False
    995         self._adapter.RemoveDevice(
    996                 device.object_path, dbus_interface=self.BLUEZ_ADAPTER_IFACE)
    997         return True
    998 
    999 
   1000     @xmlrpc_server.dbus_safe(False)
   1001     def connect_device(self, address):
   1002         """Connects a device.
   1003 
   1004         Connects a device if it is not connected.
   1005 
   1006         @param address: Address of the device to connect.
   1007 
   1008         @returns: True on success. False otherwise.
   1009 
   1010         """
   1011         device = self._find_device(address)
   1012         if not device:
   1013             logging.error('Device not found')
   1014             return False
   1015         if self._is_connected(device):
   1016           logging.info('Device is already connected')
   1017           return True
   1018         device.Connect()
   1019         return self._is_connected(device)
   1020 
   1021 
   1022     @xmlrpc_server.dbus_safe(False)
   1023     def device_is_connected(self, address):
   1024         """Checks if a device is connected.
   1025 
   1026         @param address: Address of the device to connect.
   1027 
   1028         @returns: True if device is connected. False otherwise.
   1029 
   1030         """
   1031         device = self._find_device(address)
   1032         if not device:
   1033             logging.error('Device not found')
   1034             return False
   1035         return self._is_connected(device)
   1036 
   1037 
   1038     @xmlrpc_server.dbus_safe(False)
   1039     def disconnect_device(self, address):
   1040         """Disconnects a device.
   1041 
   1042         Disconnects a device if it is connected.
   1043 
   1044         @param address: Address of the device to disconnect.
   1045 
   1046         @returns: True on success. False otherwise.
   1047 
   1048         """
   1049         device = self._find_device(address)
   1050         if not device:
   1051             logging.error('Device not found')
   1052             return False
   1053         if not self._is_connected(device):
   1054           logging.info('Device is not connected')
   1055           return True
   1056         device.Disconnect()
   1057         return not self._is_connected(device)
   1058 
   1059 
   1060     def btmon_start(self):
   1061         """Start btmon monitoring."""
   1062         self.btmon.start()
   1063 
   1064 
   1065     def btmon_stop(self):
   1066         """Stop btmon monitoring."""
   1067         self.btmon.stop()
   1068 
   1069 
   1070     def btmon_get(self, search_str, start_str):
   1071         """Get btmon output contents.
   1072 
   1073         @param search_str: only lines with search_str would be kept.
   1074         @param start_str: all lines before the occurrence of start_str would be
   1075                 filtered.
   1076 
   1077         @returns: the recorded btmon output.
   1078 
   1079         """
   1080         return self.btmon.get_contents(search_str=search_str,
   1081                                        start_str=start_str)
   1082 
   1083 
   1084     def btmon_find(self, pattern_str):
   1085         """Find if a pattern string exists in btmon output.
   1086 
   1087         @param pattern_str: the pattern string to find.
   1088 
   1089         @returns: True on success. False otherwise.
   1090 
   1091         """
   1092         return self.btmon.find(pattern_str)
   1093 
   1094 
   1095     @xmlrpc_server.dbus_safe(False)
   1096     def advertising_async_method(self, dbus_method,
   1097                                  reply_handler, error_handler, *args):
   1098         """Run an async dbus method.
   1099 
   1100         @param dbus_method: the dbus async method to invoke.
   1101         @param reply_handler: the reply handler for the dbus method.
   1102         @param error_handler: the error handler for the dbus method.
   1103         @param *args: additional arguments for the dbus method.
   1104 
   1105         @returns: an empty string '' on success;
   1106                   None if there is no _advertising interface manager; and
   1107                   an error string if the dbus method fails.
   1108 
   1109         """
   1110 
   1111         def successful_cb():
   1112             """Called when the dbus_method completed successfully."""
   1113             reply_handler()
   1114             self.advertising_cb_msg = ''
   1115             self._adv_mainloop.quit()
   1116 
   1117 
   1118         def error_cb(error):
   1119             """Called when the dbus_method failed."""
   1120             error_handler(error)
   1121             self.advertising_cb_msg = str(error)
   1122             self._adv_mainloop.quit()
   1123 
   1124 
   1125         if not self._advertising:
   1126             return None
   1127 
   1128         # Call dbus_method with handlers.
   1129         dbus_method(*args, reply_handler=successful_cb, error_handler=error_cb)
   1130 
   1131         self._adv_mainloop.run()
   1132 
   1133         return self.advertising_cb_msg
   1134 
   1135 
   1136     def register_advertisement(self, advertisement_data):
   1137         """Register an advertisement.
   1138 
   1139         Note that rpc supports only conformable types. Hence, a
   1140         dict about the advertisement is passed as a parameter such
   1141         that the advertisement object could be constructed on the host.
   1142 
   1143         @param advertisement_data: a dict of the advertisement to register.
   1144 
   1145         @returns: True on success. False otherwise.
   1146 
   1147         """
   1148         adv = advertisement.Advertisement(self._system_bus, advertisement_data)
   1149         self.advertisements.append(adv)
   1150         return self.advertising_async_method(
   1151                 self._advertising.RegisterAdvertisement,
   1152                 # reply handler
   1153                 lambda: logging.info('register_advertisement: succeeded.'),
   1154                 # error handler
   1155                 lambda error: logging.error(
   1156                     'register_advertisement: failed: %s', str(error)),
   1157                 # other arguments
   1158                 adv.get_path(), {})
   1159 
   1160 
   1161     def unregister_advertisement(self, advertisement_data):
   1162         """Unregister an advertisement.
   1163 
   1164         Note that to unregister an advertisement, it is required to use
   1165         the same self._advertising interface manager. This is because
   1166         bluez only allows the same sender to invoke UnregisterAdvertisement
   1167         method. Hence, watch out that the bluetoothd is not restarted or
   1168         self.start_bluetoothd() is not executed between the time span that
   1169         an advertisement is registered and unregistered.
   1170 
   1171         @param advertisement_data: a dict of the advertisements to unregister.
   1172 
   1173         @returns: True on success. False otherwise.
   1174 
   1175         """
   1176         path = advertisement_data.get('Path')
   1177         for index, adv in enumerate(self.advertisements):
   1178             if adv.get_path() == path:
   1179                 break
   1180         else:
   1181             logging.error('Fail to find the advertisement under the path: %s',
   1182                           path)
   1183             return False
   1184 
   1185         result = self.advertising_async_method(
   1186                 self._advertising.UnregisterAdvertisement,
   1187                 # reply handler
   1188                 lambda: logging.info('unregister_advertisement: succeeded.'),
   1189                 # error handler
   1190                 lambda error: logging.error(
   1191                     'unregister_advertisement: failed: %s', str(error)),
   1192                 # other arguments
   1193                 adv.get_path())
   1194 
   1195         # Call remove_from_connection() so that the same path could be reused.
   1196         adv.remove_from_connection()
   1197         del self.advertisements[index]
   1198 
   1199         return result
   1200 
   1201 
   1202     def set_advertising_intervals(self, min_adv_interval_ms,
   1203                                   max_adv_interval_ms):
   1204         """Set advertising intervals.
   1205 
   1206         @param min_adv_interval_ms: the min advertising interval in ms.
   1207         @param max_adv_interval_ms: the max advertising interval in ms.
   1208 
   1209         @returns: True on success. False otherwise.
   1210 
   1211         """
   1212         return self.advertising_async_method(
   1213                 self._advertising.SetAdvertisingIntervals,
   1214                 # reply handler
   1215                 lambda: logging.info('set_advertising_intervals: succeeded.'),
   1216                 # error handler
   1217                 lambda error: logging.error(
   1218                     'set_advertising_intervals: failed: %s', str(error)),
   1219                 # other arguments
   1220                 min_adv_interval_ms, max_adv_interval_ms)
   1221 
   1222 
   1223     def reset_advertising(self):
   1224         """Reset advertising.
   1225 
   1226         This includes un-registering all advertisements, reset advertising
   1227         intervals, and disable advertising.
   1228 
   1229         @returns: True on success. False otherwise.
   1230 
   1231         """
   1232         # It is required to execute remove_from_connection() to unregister the
   1233         # object-path handler of each advertisement. In this way, we could
   1234         # register an advertisement with the same path repeatedly.
   1235         for adv in self.advertisements:
   1236             adv.remove_from_connection()
   1237         del self.advertisements[:]
   1238 
   1239         return self.advertising_async_method(
   1240                 self._advertising.ResetAdvertising,
   1241                 # reply handler
   1242                 lambda: logging.info('reset_advertising: succeeded.'),
   1243                 # error handler
   1244                 lambda error: logging.error(
   1245                     'reset_advertising: failed: %s', str(error)))
   1246 
   1247 
   1248 if __name__ == '__main__':
   1249     logging.basicConfig(level=logging.DEBUG)
   1250     handler = logging.handlers.SysLogHandler(address='/dev/log')
   1251     formatter = logging.Formatter(
   1252             'bluetooth_device_xmlrpc_server: [%(levelname)s] %(message)s')
   1253     handler.setFormatter(formatter)
   1254     logging.getLogger().addHandler(handler)
   1255     logging.debug('bluetooth_device_xmlrpc_server main...')
   1256     server = xmlrpc_server.XmlRpcServer(
   1257             'localhost',
   1258             constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_PORT)
   1259     server.register_delegate(BluetoothDeviceXmlRpcDelegate())
   1260     server.run()
   1261