xref: /aosp_15_r20/external/autotest/client/cros/multimedia/bluetooth_facade.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright 2020 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li"""Facade to access the bluetooth-related functionality."""
6*9c5db199SXin Li
7*9c5db199SXin Lifrom __future__ import absolute_import
8*9c5db199SXin Lifrom __future__ import division
9*9c5db199SXin Lifrom __future__ import print_function
10*9c5db199SXin Li
11*9c5db199SXin Liimport base64
12*9c5db199SXin Liimport binascii
13*9c5db199SXin Liimport collections
14*9c5db199SXin Lifrom datetime import datetime, timedelta
15*9c5db199SXin Liimport glob
16*9c5db199SXin Li# AU tests use ToT client code, but ToT -3 client version.
17*9c5db199SXin Litry:
18*9c5db199SXin Li    from gi.repository import GLib, GObject
19*9c5db199SXin Liexcept ImportError:
20*9c5db199SXin Li    import gobject as GObject
21*9c5db199SXin Liimport json
22*9c5db199SXin Liimport logging
23*9c5db199SXin Liimport logging.handlers
24*9c5db199SXin Liimport os
25*9c5db199SXin Li
26*9c5db199SXin Li# TODO(b/215715213) - Wait until ebuild runs as python3 to remove this try
27*9c5db199SXin Litry:
28*9c5db199SXin Li    import pydbus
29*9c5db199SXin Liexcept Exception as e:
30*9c5db199SXin Li    import platform
31*9c5db199SXin Li    logging.error('Unable to import pydbus at version=%s: %s',
32*9c5db199SXin Li                  platform.python_version(), e)
33*9c5db199SXin Li    pydbus = {}
34*9c5db199SXin Li
35*9c5db199SXin Liimport re
36*9c5db199SXin Liimport subprocess
37*9c5db199SXin Liimport functools
38*9c5db199SXin Liimport time
39*9c5db199SXin Liimport threading
40*9c5db199SXin Liimport traceback
41*9c5db199SXin Li
42*9c5db199SXin Liimport common
43*9c5db199SXin Lifrom autotest_lib.client.bin import utils
44*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros.bluetooth import bluetooth_socket
45*9c5db199SXin Lifrom autotest_lib.client.common_lib import error
46*9c5db199SXin Lifrom autotest_lib.client.cros.udev_helpers import UdevadmInfo, UdevadmTrigger
47*9c5db199SXin Lifrom autotest_lib.client.cros.audio import (audio_test_data as
48*9c5db199SXin Li                                            audio_test_data_module)
49*9c5db199SXin Lifrom autotest_lib.client.cros.audio import check_quality
50*9c5db199SXin Lifrom autotest_lib.client.cros.audio import cras_utils
51*9c5db199SXin Lifrom autotest_lib.client.cros.audio.sox_utils import (
52*9c5db199SXin Li        convert_format, convert_raw_file, get_file_length,
53*9c5db199SXin Li        trim_silence_from_wav_file)
54*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth import advertisement
55*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth import adv_monitor_helper
56*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth import output_recorder
57*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth import logger_helper
58*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth.floss.adapter_client import (
59*9c5db199SXin Li        FlossAdapterClient, BluetoothCallbacks, BluetoothConnectionCallbacks,
60*9c5db199SXin Li        BondState, SspVariant, Transport)
61*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth.floss.manager_client import FlossManagerClient
62*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth.floss.utils import GLIB_THREAD_NAME
63*9c5db199SXin Lifrom autotest_lib.client.cros.power import sys_power
64*9c5db199SXin Liimport six
65*9c5db199SXin Lifrom six.moves import map
66*9c5db199SXin Lifrom six.moves import range
67*9c5db199SXin Li
68*9c5db199SXin LiCheckQualityArgsClass = collections.namedtuple(
69*9c5db199SXin Li        'args_type', ['filename', 'rate', 'channel', 'bit_width'])
70*9c5db199SXin Li
71*9c5db199SXin Li
72*9c5db199SXin Lidef _dbus_byte_array_to_b64_string(dbus_byte_array):
73*9c5db199SXin Li    """Base64 encodes a dbus byte array for use with the xml rpc proxy.
74*9c5db199SXin Li
75*9c5db199SXin Li    Input is encoded to bytes using base64 encoding. Then the base64 bytes is
76*9c5db199SXin Li    decoded as string.
77*9c5db199SXin Li    """
78*9c5db199SXin Li    return base64.standard_b64encode(bytearray(dbus_byte_array)).decode()
79*9c5db199SXin Li
80*9c5db199SXin Li
81*9c5db199SXin Lidef _b64_string_to_dbus_byte_array(b64_string):
82*9c5db199SXin Li    """Base64 decodes a dbus byte array for use with the xml rpc proxy."""
83*9c5db199SXin Li    dbus_array = []
84*9c5db199SXin Li    bytes = bytearray(base64.standard_b64decode(b64_string))
85*9c5db199SXin Li    for byte in bytes:
86*9c5db199SXin Li        dbus_array.append(byte)
87*9c5db199SXin Li    return dbus_array
88*9c5db199SXin Li
89*9c5db199SXin Li
90*9c5db199SXin Lidef dbus_safe(default_return_value, return_error=False):
91*9c5db199SXin Li    """Catch all DBus exceptions and return a default value instead.
92*9c5db199SXin Li
93*9c5db199SXin Li    Wrap a function with a try block that catches DBus exceptions and
94*9c5db199SXin Li    returns the error with the specified return status. The exception is logged
95*9c5db199SXin Li    to aid in debugging.
96*9c5db199SXin Li
97*9c5db199SXin Li    If |return_error| is set, the call will return a tuple with
98*9c5db199SXin Li    (default_return_value, str(error)).
99*9c5db199SXin Li
100*9c5db199SXin Li    @param default_return_value: What value to return in case of errors.
101*9c5db199SXin Li    @param return_error: Whether to return the error string as well.
102*9c5db199SXin Li
103*9c5db199SXin Li    @return Either the return value from the method call if successful or
104*9c5db199SXin Li            the |default_return_value| or a tuple(default_return_value,
105*9c5db199SXin Li            str(error))
106*9c5db199SXin Li    """
107*9c5db199SXin Li
108*9c5db199SXin Li    def decorator(wrapped_function):
109*9c5db199SXin Li        """Call a function and catch DBus errors.
110*9c5db199SXin Li
111*9c5db199SXin Li        @param wrapped_function function to call in dbus safe context.
112*9c5db199SXin Li        @return function return value or default_return_value on failure.
113*9c5db199SXin Li
114*9c5db199SXin Li        """
115*9c5db199SXin Li
116*9c5db199SXin Li        @functools.wraps(wrapped_function)
117*9c5db199SXin Li        def wrapper(*args, **kwargs):
118*9c5db199SXin Li            """Pass args and kwargs to a dbus safe function.
119*9c5db199SXin Li
120*9c5db199SXin Li            @param args formal python arguments.
121*9c5db199SXin Li            @param kwargs keyword python arguments.
122*9c5db199SXin Li            @return function return value or default_return_value on failure.
123*9c5db199SXin Li
124*9c5db199SXin Li            """
125*9c5db199SXin Li            logging.debug('%s()', wrapped_function.__name__)
126*9c5db199SXin Li            try:
127*9c5db199SXin Li                return wrapped_function(*args, **kwargs)
128*9c5db199SXin Li            except GLib.Error as e:
129*9c5db199SXin Li                logging.debug('Exception while performing operation %s: %s',
130*9c5db199SXin Li                              wrapped_function.__name__, e)
131*9c5db199SXin Li
132*9c5db199SXin Li                if return_error:
133*9c5db199SXin Li                    return (default_return_value, str(e))
134*9c5db199SXin Li                else:
135*9c5db199SXin Li                    return default_return_value
136*9c5db199SXin Li            except Exception as e:
137*9c5db199SXin Li                logging.debug('Exception in %s: %s', wrapped_function.__name__,
138*9c5db199SXin Li                              e)
139*9c5db199SXin Li                logging.debug(traceback.format_exc())
140*9c5db199SXin Li                raise
141*9c5db199SXin Li
142*9c5db199SXin Li        return wrapper
143*9c5db199SXin Li
144*9c5db199SXin Li    return decorator
145*9c5db199SXin Li
146*9c5db199SXin Li
147*9c5db199SXin Lidef raw_dbus_call_sync(bus,
148*9c5db199SXin Li                       proxy,
149*9c5db199SXin Li                       iface,
150*9c5db199SXin Li                       method,
151*9c5db199SXin Li                       variant_in_args,
152*9c5db199SXin Li                       variant_out_type,
153*9c5db199SXin Li                       timeout_ms=None):
154*9c5db199SXin Li    """Makes a raw D-Bus call and returns the unpacked result.
155*9c5db199SXin Li
156*9c5db199SXin Li    @param bus: System bus object.
157*9c5db199SXin Li    @param proxy: Proxy object.
158*9c5db199SXin Li    @param iface: D-Bus interface that exposes this method.
159*9c5db199SXin Li    @param method: Name of method to call.
160*9c5db199SXin Li    @param variant_in_args: A Glib.Variant that corresponds to the method's
161*9c5db199SXin Li                            inputs.
162*9c5db199SXin Li    @param variant_out_type: A Glib.VariantType that describes the output. This
163*9c5db199SXin Li                             is the type that will be unpacked from the result.
164*9c5db199SXin Li    @param timeout_ms: Timeout in milliseconds for this method call.
165*9c5db199SXin Li
166*9c5db199SXin Li    @returns: Unpacked result from the method call.
167*9c5db199SXin Li    """
168*9c5db199SXin Li    if timeout_ms is None:
169*9c5db199SXin Li        timeout_ms = GLib.MAXINT
170*9c5db199SXin Li
171*9c5db199SXin Li    return bus.con.call_sync(proxy._bus_name, proxy._path, iface, method,
172*9c5db199SXin Li                             variant_in_args, variant_out_type, 0, timeout_ms,
173*9c5db199SXin Li                             None).unpack()
174*9c5db199SXin Li
175*9c5db199SXin Li
176*9c5db199SXin Lidef unpack_if_variant(value):
177*9c5db199SXin Li    """If given value is GLib.Variant, unpack it to the actual type."""
178*9c5db199SXin Li    if isinstance(value, GLib.Variant):
179*9c5db199SXin Li        return value.unpack()
180*9c5db199SXin Li
181*9c5db199SXin Li    return value
182*9c5db199SXin Li
183*9c5db199SXin Li
184*9c5db199SXin Liclass UpstartClient:
185*9c5db199SXin Li    """Upstart D-Bus client that allows actions on upstart targets."""
186*9c5db199SXin Li
187*9c5db199SXin Li    UPSTART_MANAGER_SERVICE = 'com.ubuntu.Upstart'
188*9c5db199SXin Li    UPSTART_MANAGER_PATH = '/com/ubuntu/Upstart'
189*9c5db199SXin Li    UPSTART_MANAGER_IFACE = 'com.ubuntu.Upstart0_6'
190*9c5db199SXin Li    UPSTART_JOB_IFACE = 'com.ubuntu.Upstart0_6.Job'
191*9c5db199SXin Li
192*9c5db199SXin Li    UPSTART_ERROR_UNKNOWNINSTANCE = (
193*9c5db199SXin Li            'com.ubuntu.Upstart0_6.Error.UnknownInstance')
194*9c5db199SXin Li    UPSTART_ERROR_ALREADYSTARTED = (
195*9c5db199SXin Li            'com.ubuntu.Upstart0_6.Error.AlreadyStarted')
196*9c5db199SXin Li
197*9c5db199SXin Li    @classmethod
198*9c5db199SXin Li    def _get_job(cls, job_name):
199*9c5db199SXin Li        """Get job by name."""
200*9c5db199SXin Li        bus = pydbus.SystemBus()
201*9c5db199SXin Li        obj = bus.get(cls.UPSTART_MANAGER_SERVICE, cls.UPSTART_MANAGER_PATH)
202*9c5db199SXin Li        job_path = obj[cls.UPSTART_MANAGER_IFACE].GetJobByName(job_name)
203*9c5db199SXin Li
204*9c5db199SXin Li        return bus.get(cls.UPSTART_MANAGER_SERVICE,
205*9c5db199SXin Li                       job_path)[cls.UPSTART_JOB_IFACE]
206*9c5db199SXin Li
207*9c5db199SXin Li    @staticmethod
208*9c5db199SXin Li    def _convert_instance_args(source):
209*9c5db199SXin Li        """Convert instance args dict to array."""
210*9c5db199SXin Li        return ['{}={}'.format(k, v) for k, v in source.items()]
211*9c5db199SXin Li
212*9c5db199SXin Li    @classmethod
213*9c5db199SXin Li    def start(cls, job_name, instance_args = {}):
214*9c5db199SXin Li        """Starts a job.
215*9c5db199SXin Li
216*9c5db199SXin Li        @param job_name: Name of upstart job to start.
217*9c5db199SXin Li        @param instance_args: Instance arguments. Will be converted to array of
218*9c5db199SXin Li                              "key=value".
219*9c5db199SXin Li
220*9c5db199SXin Li        @return True if job start was sent successfully.
221*9c5db199SXin Li        """
222*9c5db199SXin Li        try:
223*9c5db199SXin Li            job = cls._get_job(job_name)
224*9c5db199SXin Li            converted_args = cls._convert_instance_args(instance_args)
225*9c5db199SXin Li            job.Start(converted_args, True)
226*9c5db199SXin Li        except TypeError as t:
227*9c5db199SXin Li            # Can occur if cls._get_job fails
228*9c5db199SXin Li            logging.error('Error starting {}: {}'.format(job_name, t))
229*9c5db199SXin Li            return False
230*9c5db199SXin Li        except GLib.Error as e:
231*9c5db199SXin Li            # An already started error is ok. All other dbus errors should
232*9c5db199SXin Li            # return False.
233*9c5db199SXin Li            if cls.UPSTART_ERROR_ALREADYSTARTED not in str(e):
234*9c5db199SXin Li                logging.error('Error starting {}: {}'.format(job_name, e))
235*9c5db199SXin Li                return False
236*9c5db199SXin Li
237*9c5db199SXin Li        return True
238*9c5db199SXin Li
239*9c5db199SXin Li    @classmethod
240*9c5db199SXin Li    def stop(cls, job_name, instance_args = {}):
241*9c5db199SXin Li        """Stops a job.
242*9c5db199SXin Li
243*9c5db199SXin Li        @param job_name: Name of upstart job to stop.
244*9c5db199SXin Li        @param instance_args: Instance arguments. Will be converted to
245*9c5db199SXin Li                              array of "key=value".
246*9c5db199SXin Li
247*9c5db199SXin Li        @return True if job stop was sent successfully.
248*9c5db199SXin Li        """
249*9c5db199SXin Li        try:
250*9c5db199SXin Li            job = cls._get_job(job_name)
251*9c5db199SXin Li            converted_args = cls._convert_instance_args(instance_args)
252*9c5db199SXin Li            job.Stop(converted_args, True)
253*9c5db199SXin Li        except TypeError as t:
254*9c5db199SXin Li            # Can occur if cls._get_job fails
255*9c5db199SXin Li            logging.error('Error stopping {}: {}'.format(job_name, t))
256*9c5db199SXin Li            return False
257*9c5db199SXin Li        except GLib.Error as e:
258*9c5db199SXin Li            # If the job was already stopped, we will see an UnknownInstance
259*9c5db199SXin Li            # exception. All other failure reasons should be treated as
260*9c5db199SXin Li            # a failure to stop.
261*9c5db199SXin Li            if cls.UPSTART_ERROR_UNKNOWNINSTANCE not in str(e):
262*9c5db199SXin Li                logging.error('Error starting {}: {}'.format(job_name, e))
263*9c5db199SXin Li                return False
264*9c5db199SXin Li
265*9c5db199SXin Li        return True
266*9c5db199SXin Li
267*9c5db199SXin Li
268*9c5db199SXin Liclass BluetoothBaseFacadeLocal(object):
269*9c5db199SXin Li    """Base facade shared by Bluez and Floss daemons. This takes care of any
270*9c5db199SXin Li    functionality that is common across the two daemons.
271*9c5db199SXin Li    """
272*9c5db199SXin Li
273*9c5db199SXin Li    # Both bluez and floss share the same lib dir for configuration and cache
274*9c5db199SXin Li    BLUETOOTH_LIBDIR = '/var/lib/bluetooth'
275*9c5db199SXin Li
276*9c5db199SXin Li    SYSLOG_LEVELS = [
277*9c5db199SXin Li            'EMERG', 'ALERT', 'CRIT', 'ERR', 'WARNING', 'NOTICE', 'INFO',
278*9c5db199SXin Li            'DEBUG'
279*9c5db199SXin Li    ]
280*9c5db199SXin Li
281*9c5db199SXin Li    # How long to wait for hid device
282*9c5db199SXin Li    HID_TIMEOUT = 15
283*9c5db199SXin Li    HID_CHECK_SECS = 2
284*9c5db199SXin Li
285*9c5db199SXin Li    # Due to problems transferring a date object, we convert to stringtime first
286*9c5db199SXin Li    # This is the standard format that we will use.
287*9c5db199SXin Li    OUT_DATE_FORMAT = '%Y-%m-%d %H:%M:%S.%f'
288*9c5db199SXin Li
289*9c5db199SXin Li    # Upstart job name for the Floss Manager daemon
290*9c5db199SXin Li    MANAGER_JOB = "btmanagerd"
291*9c5db199SXin Li    # File path for btmanagerd
292*9c5db199SXin Li    BTMANGERD_FILE_PATH = '/usr/bin/btmanagerd'
293*9c5db199SXin Li    # How long we wait for the manager daemon to come up after we start it
294*9c5db199SXin Li    DAEMON_TIMEOUT_SEC = 5
295*9c5db199SXin Li
296*9c5db199SXin Li    # Upstart job name for ChromeOS Audio daemon
297*9c5db199SXin Li    CRAS_JOB = "cras"
298*9c5db199SXin Li
299*9c5db199SXin Li    CHIPSET_TO_VIDPID = {
300*9c5db199SXin Li            'MVL-8897': [(('0x02df', '0x912d'), 'SDIO')],
301*9c5db199SXin Li            'MVL-8997': [(('0x1b4b', '0x2b42'), 'USB')],
302*9c5db199SXin Li            'QCA-6174A-5-USB': [(('0x168c', '0x003e'), 'USB')],
303*9c5db199SXin Li            'QCA-6174A-3-UART': [(('0x0271', '0x050a'), 'UART')],
304*9c5db199SXin Li            'QCA-WCN6856': [(('0x17cb', '0x1103'), 'USB')],
305*9c5db199SXin Li            'Intel-AX200': [(('0x8086', '0x2723'), 'USB')],  # CcP2
306*9c5db199SXin Li            'Intel-AX201': [
307*9c5db199SXin Li                    (('0x8086', '0x02f0'), 'USB'),
308*9c5db199SXin Li                    (('0x8086', '0x4df0'), 'USB'),
309*9c5db199SXin Li                    (('0x8086', '0xa0f0'), 'USB'),
310*9c5db199SXin Li            ],  # HrP2
311*9c5db199SXin Li            'Intel-AC9260': [(('0x8086', '0x2526'), 'USB')],  # ThP2
312*9c5db199SXin Li            'Intel-AC9560': [
313*9c5db199SXin Li                    (('0x8086', '0x31dc'), 'USB'),  # JfP2
314*9c5db199SXin Li                    (('0x8086', '0x9df0'), 'USB')
315*9c5db199SXin Li            ],
316*9c5db199SXin Li            'Intel-AC7260': [
317*9c5db199SXin Li                    (('0x8086', '0x08b1'), 'USB'),  # WP2
318*9c5db199SXin Li                    (('0x8086', '0x08b2'), 'USB')
319*9c5db199SXin Li            ],
320*9c5db199SXin Li            'Intel-AC7265': [
321*9c5db199SXin Li                    (('0x8086', '0x095a'), 'USB'),  # StP2
322*9c5db199SXin Li                    (('0x8086', '0x095b'), 'USB')
323*9c5db199SXin Li            ],
324*9c5db199SXin Li            'Realtek-RTL8822C-USB': [(('0x10ec', '0xc822'), 'USB')],
325*9c5db199SXin Li            'Realtek-RTL8822C-UART': [(('0x10ec', '0xc822'), 'UART')],
326*9c5db199SXin Li            'Realtek-RTL8852A-USB': [(('0x10ec', '0x8852'), 'USB')],
327*9c5db199SXin Li            'Mediatek-MTK7921-USB': [(('0x14c3', '0x7961'), 'USB')],
328*9c5db199SXin Li            'Mediatek-MTK7921-SDIO': [(('0x037a', '0x7901'), 'SDIO')]
329*9c5db199SXin Li
330*9c5db199SXin Li            # The following doesn't expose vid:pid
331*9c5db199SXin Li            # 'WCN3991-UART'
332*9c5db199SXin Li    }
333*9c5db199SXin Li
334*9c5db199SXin Li    def __init__(self):
335*9c5db199SXin Li        # Initialize a messages object to record general logging.
336*9c5db199SXin Li        self.messages = logger_helper.LogManager()
337*9c5db199SXin Li
338*9c5db199SXin Li        # Set up cras test client for audio tests
339*9c5db199SXin Li        self._cras_test_client = cras_utils.CrasTestClient()
340*9c5db199SXin Li
341*9c5db199SXin Li    def configure_floss(self, enabled):
342*9c5db199SXin Li        """Start and configure the Floss manager daemon.
343*9c5db199SXin Li
344*9c5db199SXin Li        In order to manage whether we use bluez or floss, we need to start the
345*9c5db199SXin Li        Floss manager daemon and then set floss enabled. This exists in the base
346*9c5db199SXin Li        implementation because bluez tests will need to start the manager to
347*9c5db199SXin Li        disable Floss.
348*9c5db199SXin Li
349*9c5db199SXin Li        @param enabled: Whether to enable Floss
350*9c5db199SXin Li
351*9c5db199SXin Li        @return Whether Floss was configured successfully.
352*9c5db199SXin Li        """
353*9c5db199SXin Li        # Start manager daemon or exit early
354*9c5db199SXin Li        if not UpstartClient.start(self.MANAGER_JOB):
355*9c5db199SXin Li            return False
356*9c5db199SXin Li
357*9c5db199SXin Li        # Since we've just started the manager daemon, we also need to recreate
358*9c5db199SXin Li        # the client.
359*9c5db199SXin Li        self.manager_client = FlossManagerClient(self.bus)
360*9c5db199SXin Li
361*9c5db199SXin Li        # Wait for the manager daemon to come up
362*9c5db199SXin Li        try:
363*9c5db199SXin Li            utils.poll_for_condition(
364*9c5db199SXin Li                    condition=(lambda: self.manager_client.has_proxy()),
365*9c5db199SXin Li                    desc='Wait for manager daemon to come up',
366*9c5db199SXin Li                    sleep_interval=0.5,
367*9c5db199SXin Li                    timeout=self.DAEMON_TIMEOUT_SEC)
368*9c5db199SXin Li        except Exception as e:
369*9c5db199SXin Li            logging.error('timeout: error starting manager daemon: %s', e)
370*9c5db199SXin Li
371*9c5db199SXin Li        # We need to observe callbacks for proper operation.
372*9c5db199SXin Li        if not self.manager_client.register_callbacks():
373*9c5db199SXin Li            logging.error('manager_client: Failed to register callbacks')
374*9c5db199SXin Li            return False
375*9c5db199SXin Li
376*9c5db199SXin Li        # Floss may not yet be enabled so make sure to enable it here.
377*9c5db199SXin Li        if self.manager_client.get_floss_enabled() != enabled:
378*9c5db199SXin Li            self.manager_client.set_floss_enabled(enabled)
379*9c5db199SXin Li            default_adapter = self.manager_client.get_default_adapter()
380*9c5db199SXin Li            try:
381*9c5db199SXin Li                utils.poll_for_condition(
382*9c5db199SXin Li                        condition=(lambda: self.manager_client.
383*9c5db199SXin Li                                   get_adapter_enabled(default_adapter
384*9c5db199SXin Li                                                       ) == enabled),
385*9c5db199SXin Li                        desc='Wait for set floss enabled to complete',
386*9c5db199SXin Li                        sleep_interval=0.5,
387*9c5db199SXin Li                        timeout=self.DAEMON_TIMEOUT_SEC)
388*9c5db199SXin Li            except Exception as e:
389*9c5db199SXin Li                logging.error('timeout: error waiting for set_floss_enabled')
390*9c5db199SXin Li
391*9c5db199SXin Li        # Also configure cras to enable/disable floss
392*9c5db199SXin Li        self.configure_cras_floss(enabled)
393*9c5db199SXin Li
394*9c5db199SXin Li        return True
395*9c5db199SXin Li
396*9c5db199SXin Li    def configure_cras_floss(self, enabled):
397*9c5db199SXin Li        """Configure whether CRAS has floss enabled."""
398*9c5db199SXin Li        cras_utils.set_floss_enabled(enabled)
399*9c5db199SXin Li
400*9c5db199SXin Li    def _restart_cras(self, enable_floss=False):
401*9c5db199SXin Li        """Restarts CRAS and sets whether Floss is enabled."""
402*9c5db199SXin Li        UpstartClient.stop(self.CRAS_JOB)
403*9c5db199SXin Li        started = UpstartClient.start(self.CRAS_JOB)
404*9c5db199SXin Li
405*9c5db199SXin Li        def _set_floss():
406*9c5db199SXin Li            try:
407*9c5db199SXin Li                self.configure_cras_floss(enable_floss)
408*9c5db199SXin Li                return True
409*9c5db199SXin Li            except:
410*9c5db199SXin Li                return False
411*9c5db199SXin Li
412*9c5db199SXin Li        try:
413*9c5db199SXin Li            if started:
414*9c5db199SXin Li                utils.poll_for_condition(
415*9c5db199SXin Li                        condition=_set_floss,
416*9c5db199SXin Li                        desc='Wait for CRAS to come up and configure floss',
417*9c5db199SXin Li                        sleep_interval=1,
418*9c5db199SXin Li                        timeout=self.DAEMON_TIMEOUT_SEC)
419*9c5db199SXin Li        except Exception as e:
420*9c5db199SXin Li            logging.error('timeout: error waiting to set floss on cras')
421*9c5db199SXin Li            return False
422*9c5db199SXin Li
423*9c5db199SXin Li        # Did we successfully start the cras daemon?
424*9c5db199SXin Li        return started
425*9c5db199SXin Li
426*9c5db199SXin Li    def log_message(self, msg):
427*9c5db199SXin Li        """ log a message to /var/log/messages."""
428*9c5db199SXin Li        try:
429*9c5db199SXin Li            cmd = ['logger', msg]
430*9c5db199SXin Li            subprocess.call(cmd)
431*9c5db199SXin Li        except Exception as e:
432*9c5db199SXin Li            logging.error("log_message %s failed with %s", cmd, str(e))
433*9c5db199SXin Li
434*9c5db199SXin Li    def messages_start(self):
435*9c5db199SXin Li        """Start messages monitoring.
436*9c5db199SXin Li
437*9c5db199SXin Li        @returns: True if logging started successfully, else False
438*9c5db199SXin Li        """
439*9c5db199SXin Li
440*9c5db199SXin Li        try:
441*9c5db199SXin Li            self.messages.StartRecording()
442*9c5db199SXin Li            return True
443*9c5db199SXin Li
444*9c5db199SXin Li        except Exception as e:
445*9c5db199SXin Li            logging.error('Failed to start log recording with error: %s', e)
446*9c5db199SXin Li
447*9c5db199SXin Li        return False
448*9c5db199SXin Li
449*9c5db199SXin Li    def messages_stop(self):
450*9c5db199SXin Li        """Stop messages monitoring.
451*9c5db199SXin Li
452*9c5db199SXin Li        @returns: True if logs were successfully gathered since logging started,
453*9c5db199SXin Li                else False
454*9c5db199SXin Li        """
455*9c5db199SXin Li        try:
456*9c5db199SXin Li            self.messages.StopRecording()
457*9c5db199SXin Li            return True
458*9c5db199SXin Li
459*9c5db199SXin Li        except Exception as e:
460*9c5db199SXin Li            logging.error('Failed to stop log recording with error: %s', e)
461*9c5db199SXin Li
462*9c5db199SXin Li        return False
463*9c5db199SXin Li
464*9c5db199SXin Li    def messages_find(self, pattern_str):
465*9c5db199SXin Li        """Find if a pattern string exists in messages output.
466*9c5db199SXin Li
467*9c5db199SXin Li        @param pattern_str: the pattern string to find.
468*9c5db199SXin Li
469*9c5db199SXin Li        @returns: True on success. False otherwise.
470*9c5db199SXin Li
471*9c5db199SXin Li        """
472*9c5db199SXin Li        return self.messages.LogContains(pattern_str)
473*9c5db199SXin Li
474*9c5db199SXin Li    def clean_bluetooth_kernel_log(self, log_level):
475*9c5db199SXin Li        """Remove Bluetooth kernel logs in /var/log/messages with loglevel
476*9c5db199SXin Li           equal to or greater than |log_level|
477*9c5db199SXin Li
478*9c5db199SXin Li        @param log_level: int in range [0..7]
479*9c5db199SXin Li        """
480*9c5db199SXin Li        reg_exp = '[^ ]+ ({LEVEL}) kernel: \[.*\] Bluetooth: .*'.format(
481*9c5db199SXin Li                LEVEL='|'.join(self.SYSLOG_LEVELS[log_level:]))
482*9c5db199SXin Li
483*9c5db199SXin Li        logging.debug('Set kernel filter to level %d', log_level)
484*9c5db199SXin Li
485*9c5db199SXin Li        self.messages.FilterOut(reg_exp)
486*9c5db199SXin Li
487*9c5db199SXin Li    def _encode_base64_json(self, data):
488*9c5db199SXin Li        """Base64 encode and json encode the data.
489*9c5db199SXin Li        Required to handle non-ascii data
490*9c5db199SXin Li
491*9c5db199SXin Li        @param data: data to be base64 and JSON encoded
492*9c5db199SXin Li
493*9c5db199SXin Li        @return: base64 and JSON encoded data
494*9c5db199SXin Li
495*9c5db199SXin Li        """
496*9c5db199SXin Li        logging.debug('_encode_base64_json raw data is %s', data)
497*9c5db199SXin Li        b64_encoded = utils.base64_recursive_encode(data)
498*9c5db199SXin Li        logging.debug('base64 encoded data is %s', b64_encoded)
499*9c5db199SXin Li        json_encoded = json.dumps(b64_encoded)
500*9c5db199SXin Li        logging.debug('JSON encoded data is %s', json_encoded)
501*9c5db199SXin Li        return json_encoded
502*9c5db199SXin Li
503*9c5db199SXin Li    def is_wrt_supported(self):
504*9c5db199SXin Li        """Check if Bluetooth adapter support WRT logs
505*9c5db199SXin Li
506*9c5db199SXin Li        WRT is supported on Intel adapters other than (StP2 and WP2)
507*9c5db199SXin Li
508*9c5db199SXin Li        @returns : True if adapter is Intel made.
509*9c5db199SXin Li        """
510*9c5db199SXin Li        # Dict of Intel Adapters that support WRT and vid:pid
511*9c5db199SXin Li        vid_pid_dict = {
512*9c5db199SXin Li                'HrP2': '8086:02f0',
513*9c5db199SXin Li                'ThP2': '8086:2526',
514*9c5db199SXin Li                'JfP2': '8086:31dc',
515*9c5db199SXin Li                'JfP2-2': '8086:9df0'
516*9c5db199SXin Li        }  # On Sarien/Arcada
517*9c5db199SXin Li
518*9c5db199SXin Li        def _get_lspci_vid_pid(output):
519*9c5db199SXin Li            """ parse output of lspci -knn and get the vid:pid
520*9c5db199SXin Li
521*9c5db199SXin Li            output is of the form '01:00.0 Network controller [0280]:
522*9c5db199SXin Li            \Intel Corporation Device [8086:2526] (rev 29)\n'
523*9c5db199SXin Li
524*9c5db199SXin Li            @returns : 'vid:pid' or None
525*9c5db199SXin Li            """
526*9c5db199SXin Li            try:
527*9c5db199SXin Li                for i in output.split(b'\n'):
528*9c5db199SXin Li                    if 'Network controller' in i.decode('utf-8'):
529*9c5db199SXin Li                        logging.debug('Got line %s', i)
530*9c5db199SXin Li                        if 'Intel Corporation' in i.decode('utf-8'):
531*9c5db199SXin Li                            return i.split(b'[')[2].split(b']')[0]
532*9c5db199SXin Li                return None
533*9c5db199SXin Li            except Exception as e:
534*9c5db199SXin Li                logging.debug('Exception in _get_lspci_vidpid %s', str(e))
535*9c5db199SXin Li                return None
536*9c5db199SXin Li
537*9c5db199SXin Li        try:
538*9c5db199SXin Li            cmd = ['lspci', '-knn']
539*9c5db199SXin Li            output = subprocess.check_output(cmd, encoding='UTF-8')
540*9c5db199SXin Li            vid_pid = _get_lspci_vid_pid(output)
541*9c5db199SXin Li            logging.debug("got vid_pid %s", vid_pid)
542*9c5db199SXin Li            if vid_pid is not None:
543*9c5db199SXin Li                if vid_pid in list(vid_pid_dict.values()):
544*9c5db199SXin Li                    return True
545*9c5db199SXin Li        except Exception as e:
546*9c5db199SXin Li            logging.error('is_intel_adapter  failed with %s', cmd, str(e))
547*9c5db199SXin Li            return False
548*9c5db199SXin Li
549*9c5db199SXin Li    def enable_wrt_logs(self):
550*9c5db199SXin Li        """ Enable WRT logs for Intel Bluetooth adapters.
551*9c5db199SXin Li
552*9c5db199SXin Li            This is applicable only to Intel adapters.
553*9c5db199SXin Li            Execute a series of custom hciconfig commands to
554*9c5db199SXin Li            setup WRT log collection
555*9c5db199SXin Li
556*9c5db199SXin Li            Precondition :
557*9c5db199SXin Li                1) Check if the DUT has Intel controller other than StP2
558*9c5db199SXin Li                2) Make sure the controller is powered on
559*9c5db199SXin Li        """
560*9c5db199SXin Li        fw_trace_cmd = (
561*9c5db199SXin Li                'hcitool cmd 3f 7c 01 10 00 00 00 FE 81 02 80 04 00 00'
562*9c5db199SXin Li                ' 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00'
563*9c5db199SXin Li                ' 00 00 00 00 00 00 00')
564*9c5db199SXin Li        ddc_read_cmd = 'hcitool cmd 3f 8c 28 01'
565*9c5db199SXin Li        ddc_write_cmd_prefix = 'hcitool cmd 3f 8b 03 28 01'
566*9c5db199SXin Li        hw_trace_cmd = (
567*9c5db199SXin Li                'hcitool cmd 3f 6f 01 08 00 00 00 00 00 00 00 00 01 00'
568*9c5db199SXin Li                ' 00 03 01 03 03 03 10 03 6A 0A 6A 0A 6A 0A 6A 0A 00 00'
569*9c5db199SXin Li                ' 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00'
570*9c5db199SXin Li                ' 00 00 00 00 00 00')
571*9c5db199SXin Li        multi_comm_trace_str = ('000000F600000000005002000000003F3F3F3'
572*9c5db199SXin Li                                'F3F003F000000000000000001000000000000000000'
573*9c5db199SXin Li                                '000000000000000000000000000000000000000000'
574*9c5db199SXin Li                                '00000000000000000000000000000000000000000'
575*9c5db199SXin Li                                '00000000000000000')
576*9c5db199SXin Li        multi_comm_trace_file = ('/sys/kernel/debug/ieee80211'
577*9c5db199SXin Li                                 '/phy0/iwlwifi/iwlmvm/send_hcmd')
578*9c5db199SXin Li
579*9c5db199SXin Li        def _execute_cmd(cmd_str, msg=''):
580*9c5db199SXin Li            """Wrapper around subprocess.check_output.
581*9c5db199SXin Li
582*9c5db199SXin Li            @params cmd: Command to be executed as a string
583*9c5db199SXin Li            @params msg: Optional description of the command
584*9c5db199SXin Li
585*9c5db199SXin Li            @returns: (True, output) if execution succeeded
586*9c5db199SXin Li                  (False, None) if execution failed
587*9c5db199SXin Li
588*9c5db199SXin Li            """
589*9c5db199SXin Li            try:
590*9c5db199SXin Li                logging.info('Executing %s cmd', msg)
591*9c5db199SXin Li                cmd = cmd_str.split(' ')
592*9c5db199SXin Li                logging.debug('command is "%s"', cmd)
593*9c5db199SXin Li                output = subprocess.check_output(cmd, enconding='UTF-8')
594*9c5db199SXin Li                logging.info('%s cmd successfully executed', msg)
595*9c5db199SXin Li                logging.debug('output is %s', output)
596*9c5db199SXin Li                return (True, output)
597*9c5db199SXin Li            except Exception as e:
598*9c5db199SXin Li                logging.error('Exception %s while executing %s command',
599*9c5db199SXin Li                              str(e), msg)
600*9c5db199SXin Li                return (False, None)
601*9c5db199SXin Li
602*9c5db199SXin Li        def _get_ddc_write_cmd(ddc_read_result, ddc_write_cmd_prefix):
603*9c5db199SXin Li            """ Create ddc_write_cmd from read command
604*9c5db199SXin Li
605*9c5db199SXin Li           This function performs the following
606*9c5db199SXin Li           1) Take the output of ddc_read_cmd which is in following form
607*9c5db199SXin Li              '< HCI Command: ogf 0x3f, ocf 0x008c, plen 1\n
608*9c5db199SXin Li               01 \n>
609*9c5db199SXin Li               HCI Event: 0x0e plen 6\n  01 8C FC 12 00 18 \n'
610*9c5db199SXin Li           2) Take the last value of the output
611*9c5db199SXin Li              01 8C FC 12 00 ===>> 18 <====
612*9c5db199SXin Li           3) Bitwise or with 0x40
613*9c5db199SXin Li              0x18 | 0x40 = 0x58
614*9c5db199SXin Li           4) Add it to the end of the ddc_write_cmd
615*9c5db199SXin Li              'hcitool 01 8C FC 00 28 01 ===> 58 <===='
616*9c5db199SXin Li
617*9c5db199SXin Li           """
618*9c5db199SXin Li            last_line = [
619*9c5db199SXin Li                    i for i in ddc_read_result.strip().split(b'\n') if i != ''
620*9c5db199SXin Li            ][-1]
621*9c5db199SXin Li            last_byte = [i for i in last_line.split(b' ') if i != ''][-1]
622*9c5db199SXin Li            processed_byte = hex(int(last_byte, 16) | 0x40).split('0x')[1]
623*9c5db199SXin Li            cmd = ddc_write_cmd_prefix + ' ' + processed_byte
624*9c5db199SXin Li            logging.debug('ddc_write_cmd is %s', cmd)
625*9c5db199SXin Li            return cmd
626*9c5db199SXin Li
627*9c5db199SXin Li        try:
628*9c5db199SXin Li            logging.info('Enabling WRT logs')
629*9c5db199SXin Li            status, _ = _execute_cmd(fw_trace_cmd, 'FW trace cmd')
630*9c5db199SXin Li            if not status:
631*9c5db199SXin Li                logging.info('FW trace command execution failed')
632*9c5db199SXin Li                return False
633*9c5db199SXin Li
634*9c5db199SXin Li            status, ddc_read_result = _execute_cmd(ddc_read_cmd, 'DDC Read')
635*9c5db199SXin Li            if not status:
636*9c5db199SXin Li                logging.info('DDC Read command  execution failed')
637*9c5db199SXin Li                return False
638*9c5db199SXin Li
639*9c5db199SXin Li            ddc_write_cmd = _get_ddc_write_cmd(ddc_read_result,
640*9c5db199SXin Li                                               ddc_write_cmd_prefix)
641*9c5db199SXin Li            logging.debug('DDC Write command  is %s', ddc_write_cmd)
642*9c5db199SXin Li            status, _ = _execute_cmd(ddc_write_cmd, 'DDC Write')
643*9c5db199SXin Li            if not status:
644*9c5db199SXin Li                logging.info('DDC Write commanad execution failed')
645*9c5db199SXin Li                return False
646*9c5db199SXin Li
647*9c5db199SXin Li            status, hw_trace_result = _execute_cmd(hw_trace_cmd, 'HW trace')
648*9c5db199SXin Li            if not status:
649*9c5db199SXin Li                logging.info('HW Trace command  execution failed')
650*9c5db199SXin Li                return False
651*9c5db199SXin Li
652*9c5db199SXin Li            logging.debug('Executing the multi_comm_trace cmd %s to file %s',
653*9c5db199SXin Li                          multi_comm_trace_str, multi_comm_trace_file)
654*9c5db199SXin Li            with open(multi_comm_trace_file, 'w') as f:
655*9c5db199SXin Li                f.write(multi_comm_trace_str + '\n')
656*9c5db199SXin Li                f.flush()
657*9c5db199SXin Li
658*9c5db199SXin Li            logging.info('WRT Logs enabled')
659*9c5db199SXin Li            return True
660*9c5db199SXin Li        except Exception as e:
661*9c5db199SXin Li            logging.error('Exception %s while enabling WRT logs', str(e))
662*9c5db199SXin Li            return False
663*9c5db199SXin Li
664*9c5db199SXin Li    def collect_wrt_logs(self):
665*9c5db199SXin Li        """Collect the WRT logs for Intel Bluetooth adapters
666*9c5db199SXin Li
667*9c5db199SXin Li           This is applicable only to Intel adapters.
668*9c5db199SXin Li           Execute following command to collect WRT log. The logs are
669*9c5db199SXin Li           copied to /var/spool/crash/
670*9c5db199SXin Li
671*9c5db199SXin Li           'echo 1 > sudo tee /sys/kernel/debug/ieee80211/phy0'
672*9c5db199SXin Li                           '/iwlwifi/iwlmvm/fw_dbg_collect'
673*9c5db199SXin Li           This is to be called only after enable_wrt_logs is called
674*9c5db199SXin Li
675*9c5db199SXin Li
676*9c5db199SXin Li           Precondition:
677*9c5db199SXin Li                 1) enable_wrt_logs has been called
678*9c5db199SXin Li        """
679*9c5db199SXin Li
680*9c5db199SXin Li        def _collect_logs():
681*9c5db199SXin Li            """Execute command to collect wrt logs."""
682*9c5db199SXin Li            try:
683*9c5db199SXin Li                with open(
684*9c5db199SXin Li                        '/sys/kernel/debug/ieee80211/phy0/iwlwifi/'
685*9c5db199SXin Li                        'iwlmvm/fw_dbg_collect', 'w') as f:
686*9c5db199SXin Li                    f.write('1')
687*9c5db199SXin Li                    f.flush()
688*9c5db199SXin Li                # There is some flakiness in log collection. This sleep
689*9c5db199SXin Li                # is due to the flakiness
690*9c5db199SXin Li                time.sleep(10)
691*9c5db199SXin Li                return True
692*9c5db199SXin Li            except Exception as e:
693*9c5db199SXin Li                logging.error('Exception %s in _collect logs ', str(e))
694*9c5db199SXin Li                return False
695*9c5db199SXin Li
696*9c5db199SXin Li        def _get_num_log_files():
697*9c5db199SXin Li            """Return number of WRT log files."""
698*9c5db199SXin Li            try:
699*9c5db199SXin Li                return len(glob.glob('/var/spool/crash/devcoredump_iwlwifi*'))
700*9c5db199SXin Li            except Exception as e:
701*9c5db199SXin Li                logging.debug('Exception %s raised in _get_num_log_files',
702*9c5db199SXin Li                              str(e))
703*9c5db199SXin Li                return 0
704*9c5db199SXin Li
705*9c5db199SXin Li        try:
706*9c5db199SXin Li            logging.info('Collecting WRT logs')
707*9c5db199SXin Li            #
708*9c5db199SXin Li            # The command to trigger the logs does seems to work always.
709*9c5db199SXin Li            # As a workaround for this flakiness, execute it multiple times
710*9c5db199SXin Li            # until a new log is created
711*9c5db199SXin Li            #
712*9c5db199SXin Li            num_logs_present = _get_num_log_files()
713*9c5db199SXin Li            logging.debug('%s logs present', num_logs_present)
714*9c5db199SXin Li            for i in range(10):
715*9c5db199SXin Li                time.sleep(1)
716*9c5db199SXin Li                logging.debug('Executing command to collect WRT logs ')
717*9c5db199SXin Li                if _collect_logs():
718*9c5db199SXin Li                    logging.debug('Command to collect WRT logs executed')
719*9c5db199SXin Li                else:
720*9c5db199SXin Li                    logging.debug('Command to collect WRT logs failed')
721*9c5db199SXin Li                    continue
722*9c5db199SXin Li
723*9c5db199SXin Li                if _get_num_log_files() > num_logs_present:
724*9c5db199SXin Li                    logging.info('Successfully collected WRT logs ')
725*9c5db199SXin Li                    return True
726*9c5db199SXin Li                else:
727*9c5db199SXin Li                    logging.debug('Log file not written. Trying again')
728*9c5db199SXin Li
729*9c5db199SXin Li            logging.info('Unable to collect WRT logs')
730*9c5db199SXin Li            return False
731*9c5db199SXin Li        except Exception as e:
732*9c5db199SXin Li            logging.error('Exception %s while collecting WRT logs', str(e))
733*9c5db199SXin Li            return False
734*9c5db199SXin Li
735*9c5db199SXin Li    def _get_wake_enabled_path(self):
736*9c5db199SXin Li        # Walk up the parents from hci0 sysfs path and find the first one with
737*9c5db199SXin Li        # a power/wakeup property. Return that path (including power/wakeup).
738*9c5db199SXin Li
739*9c5db199SXin Li        # Resolve hci path to get full device path (i.e. w/ usb or uart)
740*9c5db199SXin Li        search_at = os.path.realpath('/sys/class/bluetooth/hci0')
741*9c5db199SXin Li
742*9c5db199SXin Li        # Exit early if path doesn't exist
743*9c5db199SXin Li        if not os.path.exists(search_at):
744*9c5db199SXin Li            return None
745*9c5db199SXin Li
746*9c5db199SXin Li        # Walk up parents and try to find one with 'power/wakeup'
747*9c5db199SXin Li        for _ in range(search_at.count('/') - 1):
748*9c5db199SXin Li            search_at = os.path.normpath(os.path.join(search_at, '..'))
749*9c5db199SXin Li            try:
750*9c5db199SXin Li                path = os.path.join(search_at, 'power', 'wakeup')
751*9c5db199SXin Li                with open(path, 'r') as f:
752*9c5db199SXin Li                    return path
753*9c5db199SXin Li            except IOError:
754*9c5db199SXin Li                # No power wakeup at the given location so keep going
755*9c5db199SXin Li                continue
756*9c5db199SXin Li
757*9c5db199SXin Li        return None
758*9c5db199SXin Li
759*9c5db199SXin Li    def _is_wake_enabled(self):
760*9c5db199SXin Li        search_at = self._get_wake_enabled_path()
761*9c5db199SXin Li
762*9c5db199SXin Li        if search_at is not None:
763*9c5db199SXin Li            try:
764*9c5db199SXin Li                with open(search_at, 'r') as f:
765*9c5db199SXin Li                    value = f.read()
766*9c5db199SXin Li                    logging.info('Power/wakeup found at {}: {}'.format(
767*9c5db199SXin Li                            search_at, value))
768*9c5db199SXin Li                    return 'enabled' in value
769*9c5db199SXin Li            except IOError:
770*9c5db199SXin Li                # Path was not readable
771*9c5db199SXin Li                return False
772*9c5db199SXin Li
773*9c5db199SXin Li        logging.debug('No power/wakeup path found')
774*9c5db199SXin Li        return False
775*9c5db199SXin Li
776*9c5db199SXin Li    def _set_wake_enabled(self, value):
777*9c5db199SXin Li        path = self._get_wake_enabled_path()
778*9c5db199SXin Li        if path is not None:
779*9c5db199SXin Li            try:
780*9c5db199SXin Li                with open(path, 'w') as f:
781*9c5db199SXin Li                    f.write('enabled' if value else 'disabled')
782*9c5db199SXin Li                    return True
783*9c5db199SXin Li            except IOError:
784*9c5db199SXin Li                # Path was not writeable
785*9c5db199SXin Li                return False
786*9c5db199SXin Li
787*9c5db199SXin Li        return False
788*9c5db199SXin Li
789*9c5db199SXin Li    def is_wake_enabled(self):
790*9c5db199SXin Li        """Checks whether the bluetooth adapter has wake enabled.
791*9c5db199SXin Li
792*9c5db199SXin Li        This will walk through all parents of the hci0 sysfs path and try to
793*9c5db199SXin Li        find one with a 'power/wakeup' entry and returns whether its value is
794*9c5db199SXin Li        'enabled'.
795*9c5db199SXin Li
796*9c5db199SXin Li        @return True if 'power/wakeup' of an hci0 parent is 'enabled'
797*9c5db199SXin Li        """
798*9c5db199SXin Li        enabled = self._is_wake_enabled()
799*9c5db199SXin Li        return enabled
800*9c5db199SXin Li
801*9c5db199SXin Li    def set_wake_enabled(self, value):
802*9c5db199SXin Li        """Sets wake enabled to the value if path exists.
803*9c5db199SXin Li
804*9c5db199SXin Li        This will walk through all parents of the hci0 sysfs path and write the
805*9c5db199SXin Li        value to the first one it finds.
806*9c5db199SXin Li
807*9c5db199SXin Li        @param value: Sets power/wakeup to "enabled" if value is true, else
808*9c5db199SXin Li                   "disabled"
809*9c5db199SXin Li
810*9c5db199SXin Li        @return True if it wrote value to a power/wakeup, False otherwise
811*9c5db199SXin Li        """
812*9c5db199SXin Li        return self._set_wake_enabled(value)
813*9c5db199SXin Li
814*9c5db199SXin Li    def wait_for_hid_device(self, device_address, timeout, sleep_interval):
815*9c5db199SXin Li        """Waits for hid device with given device address.
816*9c5db199SXin Li
817*9c5db199SXin Li        @param device_address: Peripheral address
818*9c5db199SXin Li        @param timeout: maximum number of seconds to wait
819*9c5db199SXin Li        @param sleep_interval: time to sleep between polls
820*9c5db199SXin Li
821*9c5db199SXin Li        @return True if hid device found, False otherwise
822*9c5db199SXin Li        """
823*9c5db199SXin Li
824*9c5db199SXin Li        def _match_hid_to_device(hidpath, device_address):
825*9c5db199SXin Li            """Check if given hid syspath is for the given device address """
826*9c5db199SXin Li            # If the syspath has a uniq property that matches the peripheral
827*9c5db199SXin Li            # device's address, then it has matched
828*9c5db199SXin Li            props = UdevadmInfo.GetProperties(hidpath)
829*9c5db199SXin Li            if (props.get(b'uniq', b'').lower().decode() == device_address):
830*9c5db199SXin Li                logging.info('Found hid device for address {} at {}'.format(
831*9c5db199SXin Li                        device_address, hidpath))
832*9c5db199SXin Li                return True
833*9c5db199SXin Li            else:
834*9c5db199SXin Li                logging.info('Path {} is not right device.'.format(hidpath))
835*9c5db199SXin Li
836*9c5db199SXin Li            return False
837*9c5db199SXin Li
838*9c5db199SXin Li        def _hid_is_created(device_address):
839*9c5db199SXin Li            existing_inputs = UdevadmTrigger(
840*9c5db199SXin Li                    subsystem_match=['input']).DryRun()
841*9c5db199SXin Li            for entry in existing_inputs:
842*9c5db199SXin Li                entry = entry.decode()
843*9c5db199SXin Li                bt_hid = any([t in entry for t in ['uhid', 'hci']])
844*9c5db199SXin Li                logging.info('udevadm trigger entry is {}: {}'.format(
845*9c5db199SXin Li                        bt_hid, entry))
846*9c5db199SXin Li
847*9c5db199SXin Li                if (bt_hid and _match_hid_to_device(entry,
848*9c5db199SXin Li                                                    device_address.lower())):
849*9c5db199SXin Li                    return True
850*9c5db199SXin Li
851*9c5db199SXin Li            return False
852*9c5db199SXin Li
853*9c5db199SXin Li        if timeout is None:
854*9c5db199SXin Li            timeout = self.HID_TIMEOUT
855*9c5db199SXin Li        if sleep_interval is None:
856*9c5db199SXin Li            sleep_interval = self.HID_CHECK_SECS
857*9c5db199SXin Li
858*9c5db199SXin Li        method_name = 'wait_for_hid_device'
859*9c5db199SXin Li        try:
860*9c5db199SXin Li            utils.poll_for_condition(
861*9c5db199SXin Li                    condition=(lambda: _hid_is_created(device_address)),
862*9c5db199SXin Li                    timeout=timeout,
863*9c5db199SXin Li                    sleep_interval=sleep_interval,
864*9c5db199SXin Li                    desc=('Waiting for HID device to be created from %s' %
865*9c5db199SXin Li                          device_address))
866*9c5db199SXin Li            return True
867*9c5db199SXin Li        except utils.TimeoutError as e:
868*9c5db199SXin Li            logging.error('%s: %s', method_name, e)
869*9c5db199SXin Li        except Exception as e:
870*9c5db199SXin Li            logging.error('%s: unexpected error: %s', method_name, e)
871*9c5db199SXin Li
872*9c5db199SXin Li        return False
873*9c5db199SXin Li
874*9c5db199SXin Li    def _powerd_last_resume_details(self, before=5, after=0):
875*9c5db199SXin Li        """ Look at powerd logs for last suspend/resume attempt.
876*9c5db199SXin Li
877*9c5db199SXin Li        Note that logs are in reverse order (chronologically). Keep that in mind
878*9c5db199SXin Li        for the 'before' and 'after' parameters.
879*9c5db199SXin Li
880*9c5db199SXin Li        @param before: Number of context lines before search item to show.
881*9c5db199SXin Li        @param after: Number of context lines after search item to show.
882*9c5db199SXin Li
883*9c5db199SXin Li        @return Most recent lines containing suspend resume details or ''.
884*9c5db199SXin Li        """
885*9c5db199SXin Li        event_file = '/var/log/power_manager/powerd.LATEST'
886*9c5db199SXin Li
887*9c5db199SXin Li        # Each powerd_suspend wakeup has a log "powerd_suspend returned 0",
888*9c5db199SXin Li        # with the return code of the suspend. We search for the last
889*9c5db199SXin Li        # occurrence in the log, and then find the collocated event_count log,
890*9c5db199SXin Li        # indicating the wakeup cause. -B option for grep will actually grab the
891*9c5db199SXin Li        # *next* 5 logs in time, since we are piping the powerd file backwards
892*9c5db199SXin Li        # with tac command
893*9c5db199SXin Li        resume_indicator = 'powerd_suspend returned'
894*9c5db199SXin Li        cmd = 'tac {} | grep -A {} -B {} -m1 "{}"'.format(
895*9c5db199SXin Li                event_file, after, before, resume_indicator)
896*9c5db199SXin Li
897*9c5db199SXin Li        try:
898*9c5db199SXin Li            return utils.run(cmd).stdout
899*9c5db199SXin Li        except error.CmdError:
900*9c5db199SXin Li            logging.error('Could not locate recent suspend')
901*9c5db199SXin Li
902*9c5db199SXin Li        return ''
903*9c5db199SXin Li
904*9c5db199SXin Li    def bt_caused_last_resume(self):
905*9c5db199SXin Li        """Checks if last resume from suspend was caused by bluetooth
906*9c5db199SXin Li
907*9c5db199SXin Li        @return: True if BT wake path was cause of resume, False otherwise
908*9c5db199SXin Li        """
909*9c5db199SXin Li
910*9c5db199SXin Li        # When the resume cause is printed to powerd log, it omits the
911*9c5db199SXin Li        # /power/wakeup portion of wake path
912*9c5db199SXin Li        bt_wake_path = self._get_wake_enabled_path()
913*9c5db199SXin Li
914*9c5db199SXin Li        # If bluetooth does not have a valid wake path, it could not have caused
915*9c5db199SXin Li        # the resume
916*9c5db199SXin Li        if not bt_wake_path:
917*9c5db199SXin Li            return False
918*9c5db199SXin Li
919*9c5db199SXin Li        bt_wake_path = bt_wake_path.replace('/power/wakeup', '')
920*9c5db199SXin Li
921*9c5db199SXin Li        last_resume_details = self._powerd_last_resume_details().rstrip(
922*9c5db199SXin Li                '\n ').split('\n')
923*9c5db199SXin Li        logging.debug('/var/log/power_manager/powerd.LATEST: 5 lines after '
924*9c5db199SXin Li                      'powerd_suspend returns:')
925*9c5db199SXin Li        for l in last_resume_details[::-1]:
926*9c5db199SXin Li            logging.debug(l)
927*9c5db199SXin Li        # If BT caused wake, there will be a line describing the bt wake
928*9c5db199SXin Li        # path's event_count before and after the resume
929*9c5db199SXin Li        for line in last_resume_details:
930*9c5db199SXin Li            if 'event_count' in line:
931*9c5db199SXin Li                logging.info('Checking wake event: {}'.format(line))
932*9c5db199SXin Li                if bt_wake_path in line:
933*9c5db199SXin Li                    logging.debug('BT event woke the DUT')
934*9c5db199SXin Li                    return True
935*9c5db199SXin Li
936*9c5db199SXin Li        return False
937*9c5db199SXin Li
938*9c5db199SXin Li    def find_last_suspend_via_powerd_logs(self):
939*9c5db199SXin Li        """ Finds the last suspend attempt via powerd logs.
940*9c5db199SXin Li
941*9c5db199SXin Li        Finds the last suspend attempt using powerd logs by searching backwards
942*9c5db199SXin Li        through the logs to find the latest entries with 'powerd_suspend'. If we
943*9c5db199SXin Li        can't find a suspend attempt, we return None.
944*9c5db199SXin Li
945*9c5db199SXin Li        @return: Tuple (suspend start time, suspend end time, suspend result) or
946*9c5db199SXin Li                None if we can't find a suspend attempt
947*9c5db199SXin Li        """
948*9c5db199SXin Li        # Logs look like this (ignore newline):
949*9c5db199SXin Li        # 2021-02-11T18:53:43.561880Z INFO powerd:
950*9c5db199SXin Li        #       [daemon.cc(724)] powerd_suspend returned 0
951*9c5db199SXin Li        # ... stuff in between ...
952*9c5db199SXin Li        # 2021-02-11T18:53:13.277695Z INFO powerd:
953*9c5db199SXin Li        #       [suspender.cc(574)] Starting suspend
954*9c5db199SXin Li
955*9c5db199SXin Li        # Date format for strptime and strftime
956*9c5db199SXin Li        date_format = '%Y-%m-%dT%H:%M:%S.%fZ'
957*9c5db199SXin Li        date_group_re = ('(?P<date>[0-9]+-[0-9]+-[0-9]+T'
958*9c5db199SXin Li                         '[0-9]+:[0-9]+:[0-9]+[.][0-9]+Z)\s')
959*9c5db199SXin Li
960*9c5db199SXin Li        finish_suspend_re = re.compile(
961*9c5db199SXin Li                '^{date_regex}'
962*9c5db199SXin Li                '.*daemon.*powerd_suspend returned '
963*9c5db199SXin Li                '(?P<exitcode>[0-9]+)'.format(date_regex=date_group_re))
964*9c5db199SXin Li        start_suspend_re = re.compile(
965*9c5db199SXin Li                '^{date_regex}.*suspender.*'
966*9c5db199SXin Li                'Starting suspend'.format(date_regex=date_group_re))
967*9c5db199SXin Li
968*9c5db199SXin Li        now = datetime.now()
969*9c5db199SXin Li        last_resume_details = self._powerd_last_resume_details(before=0,
970*9c5db199SXin Li                                                               after=8)
971*9c5db199SXin Li        if last_resume_details:
972*9c5db199SXin Li            start_time, end_time, ret = None, None, None
973*9c5db199SXin Li            try:
974*9c5db199SXin Li                for line in last_resume_details.split('\n'):
975*9c5db199SXin Li                    logging.debug('Last suspend search: %s', line)
976*9c5db199SXin Li                    m = finish_suspend_re.match(line)
977*9c5db199SXin Li                    if m:
978*9c5db199SXin Li                        logging.debug('Found suspend end: date(%s) ret(%s)',
979*9c5db199SXin Li                                      m.group('date'), m.group('exitcode'))
980*9c5db199SXin Li                        end_time = datetime.strptime(
981*9c5db199SXin Li                                m.group('date'),
982*9c5db199SXin Li                                date_format).replace(year=now.year)
983*9c5db199SXin Li                        ret = int(m.group('exitcode'))
984*9c5db199SXin Li
985*9c5db199SXin Li                    m = start_suspend_re.match(line)
986*9c5db199SXin Li                    if m:
987*9c5db199SXin Li                        logging.debug('Found suspend start: date(%s)',
988*9c5db199SXin Li                                      m.group('date'))
989*9c5db199SXin Li                        start_time = datetime.strptime(
990*9c5db199SXin Li                                m.group('date'),
991*9c5db199SXin Li                                date_format).replace(year=now.year)
992*9c5db199SXin Li                        break
993*9c5db199SXin Li
994*9c5db199SXin Li                if all([x is not None for x in [start_time, end_time, ret]]):
995*9c5db199SXin Li                    # Return dates in string format due to inconsistency between
996*9c5db199SXin Li                    # python2/3 usage on host and dut
997*9c5db199SXin Li                    return (start_time.strftime(self.OUT_DATE_FORMAT),
998*9c5db199SXin Li                            end_time.strftime(self.OUT_DATE_FORMAT), ret)
999*9c5db199SXin Li                else:
1000*9c5db199SXin Li                    logging.error(
1001*9c5db199SXin Li                            'Failed to parse details from last suspend. %s %s %s',
1002*9c5db199SXin Li                            str(start_time), str(end_time), str(ret))
1003*9c5db199SXin Li            except Exception as e:
1004*9c5db199SXin Li                logging.error('Failed to parse last suspend: %s', str(e))
1005*9c5db199SXin Li        else:
1006*9c5db199SXin Li            logging.error('No powerd_suspend attempt found')
1007*9c5db199SXin Li
1008*9c5db199SXin Li        return None
1009*9c5db199SXin Li
1010*9c5db199SXin Li    def do_suspend(self, seconds, expect_bt_wake):
1011*9c5db199SXin Li        """Suspend DUT using the power manager.
1012*9c5db199SXin Li
1013*9c5db199SXin Li        @param seconds: The number of seconds to suspend the device.
1014*9c5db199SXin Li        @param expect_bt_wake: Whether we expect bluetooth to wake us from
1015*9c5db199SXin Li            suspend. If true, we expect this resume will occur early
1016*9c5db199SXin Li
1017*9c5db199SXin Li        @throws: SuspendFailure on resume with unexpected timing or wake source.
1018*9c5db199SXin Li            The raised exception will be handled as a non-zero retcode over the
1019*9c5db199SXin Li            RPC, signalling for the test to fail.
1020*9c5db199SXin Li        """
1021*9c5db199SXin Li        early_wake = False
1022*9c5db199SXin Li        try:
1023*9c5db199SXin Li            sys_power.do_suspend(seconds)
1024*9c5db199SXin Li
1025*9c5db199SXin Li        except sys_power.SpuriousWakeupError:
1026*9c5db199SXin Li            logging.info('Early resume detected...')
1027*9c5db199SXin Li            early_wake = True
1028*9c5db199SXin Li
1029*9c5db199SXin Li        # Handle error conditions based on test expectations, whether resume
1030*9c5db199SXin Li        # was early, and cause of the resume
1031*9c5db199SXin Li        bt_caused_wake = self.bt_caused_last_resume()
1032*9c5db199SXin Li        logging.info('Cause for resume: {}'.format(
1033*9c5db199SXin Li                'BT' if bt_caused_wake else 'Not BT'))
1034*9c5db199SXin Li
1035*9c5db199SXin Li        if not expect_bt_wake and bt_caused_wake:
1036*9c5db199SXin Li            raise sys_power.SuspendFailure('BT woke us unexpectedly')
1037*9c5db199SXin Li
1038*9c5db199SXin Li        # TODO(b/160803597) - Uncomment when BT wake reason is correctly
1039*9c5db199SXin Li        # captured in powerd log.
1040*9c5db199SXin Li        #
1041*9c5db199SXin Li        # if expect_bt_wake and not bt_caused_wake:
1042*9c5db199SXin Li        #   raise sys_power.SuspendFailure('BT should have woken us')
1043*9c5db199SXin Li        #
1044*9c5db199SXin Li        # if bt_caused_wake and not early_wake:
1045*9c5db199SXin Li        #   raise sys_power.SuspendFailure('BT wake did not come early')
1046*9c5db199SXin Li
1047*9c5db199SXin Li        return True
1048*9c5db199SXin Li
1049*9c5db199SXin Li    def get_wlan_vid_pid(self):
1050*9c5db199SXin Li        """ Return vendor id and product id of the wlan chip on BT/WiFi module
1051*9c5db199SXin Li
1052*9c5db199SXin Li        @returns: (vid,pid) on success; (None,None) on failure
1053*9c5db199SXin Li        """
1054*9c5db199SXin Li        vid = None
1055*9c5db199SXin Li        pid = None
1056*9c5db199SXin Li        path_template = '/sys/class/net/%s/device/'
1057*9c5db199SXin Li        for dev_name in ['wlan0', 'mlan0']:
1058*9c5db199SXin Li            if os.path.exists(path_template % dev_name):
1059*9c5db199SXin Li                path_v = path_template % dev_name + 'vendor'
1060*9c5db199SXin Li                path_d = path_template % dev_name + 'device'
1061*9c5db199SXin Li                logging.debug('Paths are %s %s', path_v, path_d)
1062*9c5db199SXin Li                try:
1063*9c5db199SXin Li                    vid = open(path_v).read().strip('\n')
1064*9c5db199SXin Li                    pid = open(path_d).read().strip('\n')
1065*9c5db199SXin Li                    break
1066*9c5db199SXin Li                except Exception as e:
1067*9c5db199SXin Li                    logging.error('Exception %s while reading vid/pid', str(e))
1068*9c5db199SXin Li        logging.debug('returning vid:%s pid:%s', vid, pid)
1069*9c5db199SXin Li        return (vid, pid)
1070*9c5db199SXin Li
1071*9c5db199SXin Li    def get_bt_transport(self):
1072*9c5db199SXin Li        """ Return transport (UART/USB/SDIO) used by BT module
1073*9c5db199SXin Li
1074*9c5db199SXin Li        @returns: USB/UART/SDIO on success; None on failure
1075*9c5db199SXin Li        """
1076*9c5db199SXin Li        try:
1077*9c5db199SXin Li            transport_str = os.path.realpath(
1078*9c5db199SXin Li                    '/sys/class/bluetooth/hci0/device/driver/module')
1079*9c5db199SXin Li            logging.debug('transport is %s', transport_str)
1080*9c5db199SXin Li            transport = transport_str.split('/')[-1]
1081*9c5db199SXin Li            if transport == 'btusb':
1082*9c5db199SXin Li                return 'USB'
1083*9c5db199SXin Li            elif transport == 'hci_uart':
1084*9c5db199SXin Li                return 'UART'
1085*9c5db199SXin Li            elif transport in ['btmrvl_sdio', 'btmtksdio']:
1086*9c5db199SXin Li                return 'SDIO'
1087*9c5db199SXin Li            else:
1088*9c5db199SXin Li                return None
1089*9c5db199SXin Li        except Exception as e:
1090*9c5db199SXin Li            logging.error('Exception %s in get_bt_transport', str(e))
1091*9c5db199SXin Li            return None
1092*9c5db199SXin Li
1093*9c5db199SXin Li    def get_bt_module_name(self):
1094*9c5db199SXin Li        """ Return bluetooth module name for non-USB devices
1095*9c5db199SXin Li
1096*9c5db199SXin Li        @returns '' on failure. On success return chipset name, if found in
1097*9c5db199SXin Li                 dict.Otherwise it returns the raw string read.
1098*9c5db199SXin Li        """
1099*9c5db199SXin Li        # map the string read from device to chipset name
1100*9c5db199SXin Li        chipset_string_dict = {
1101*9c5db199SXin Li                'qcom,wcn3991-bt\x00': 'WCN3991',
1102*9c5db199SXin Li                'qcom,wcn6750-bt\x00': 'WCN6750',
1103*9c5db199SXin Li        }
1104*9c5db199SXin Li
1105*9c5db199SXin Li        hci_device = '/sys/class/bluetooth/hci0'
1106*9c5db199SXin Li        real_path = os.path.realpath(hci_device)
1107*9c5db199SXin Li
1108*9c5db199SXin Li        logging.debug('real path is %s', real_path)
1109*9c5db199SXin Li        if 'usb' in real_path:
1110*9c5db199SXin Li            return ''
1111*9c5db199SXin Li
1112*9c5db199SXin Li        device_path = os.path.join(real_path, 'device', 'of_node',
1113*9c5db199SXin Li                                   'compatible')
1114*9c5db199SXin Li        try:
1115*9c5db199SXin Li            chipset_string = open(device_path).read()
1116*9c5db199SXin Li            logging.debug('read string %s from %s', chipset_string,
1117*9c5db199SXin Li                          device_path)
1118*9c5db199SXin Li        except Exception as e:
1119*9c5db199SXin Li            logging.error('Exception %s while reading from file', str(e),
1120*9c5db199SXin Li                          device_path)
1121*9c5db199SXin Li            return ''
1122*9c5db199SXin Li
1123*9c5db199SXin Li        if chipset_string in chipset_string_dict:
1124*9c5db199SXin Li            return chipset_string_dict[chipset_string]
1125*9c5db199SXin Li        else:
1126*9c5db199SXin Li            logging.debug("Chipset not known. Returning %s", chipset_string)
1127*9c5db199SXin Li            return chipset_string
1128*9c5db199SXin Li
1129*9c5db199SXin Li    def get_chipset_name(self):
1130*9c5db199SXin Li        """ Get the name of BT/WiFi chipset on this host
1131*9c5db199SXin Li
1132*9c5db199SXin Li        @returns chipset name if successful else ''
1133*9c5db199SXin Li        """
1134*9c5db199SXin Li        (vid, pid) = self.get_wlan_vid_pid()
1135*9c5db199SXin Li        logging.debug('Bluetooth module vid pid is %s %s', vid, pid)
1136*9c5db199SXin Li        transport = self.get_bt_transport()
1137*9c5db199SXin Li        logging.debug('Bluetooth transport is %s', transport)
1138*9c5db199SXin Li        if vid is None or pid is None:
1139*9c5db199SXin Li            # Controllers that aren't WLAN+BT combo chips does not expose
1140*9c5db199SXin Li            # Vendor ID/Product ID. Use alternate method.
1141*9c5db199SXin Li            # This will return one of ['WCN3991', ''] or a string containing
1142*9c5db199SXin Li            # the name of chipset read from DUT
1143*9c5db199SXin Li            return self.get_bt_module_name()
1144*9c5db199SXin Li        for name, l in self.CHIPSET_TO_VIDPID.items():
1145*9c5db199SXin Li            if ((vid, pid), transport) in l:
1146*9c5db199SXin Li                return name
1147*9c5db199SXin Li        return ''
1148*9c5db199SXin Li
1149*9c5db199SXin Li    def get_bt_usb_device_strs(self):
1150*9c5db199SXin Li        """ Return the usb endpoints for the bluetooth device, if they exist
1151*9c5db199SXin Li
1152*9c5db199SXin Li        We wish to be able to identify usb disconnect events that affect our
1153*9c5db199SXin Li        bluetooth operation. To do so, we must first identify the usb endpoint
1154*9c5db199SXin Li        that is associated with our bluetooth device.
1155*9c5db199SXin Li
1156*9c5db199SXin Li        @returns: Relevant usb endpoints for the bluetooth device,
1157*9c5db199SXin Li                  i.e. ['1-1','1-1.2'] if they exist,
1158*9c5db199SXin Li                  [] otherwise
1159*9c5db199SXin Li        """
1160*9c5db199SXin Li
1161*9c5db199SXin Li        hci_device = '/sys/class/bluetooth/hci0'
1162*9c5db199SXin Li        real_path = os.path.realpath(hci_device)
1163*9c5db199SXin Li
1164*9c5db199SXin Li        # real_path for a usb bluetooth controller will look something like:
1165*9c5db199SXin Li        # ../../devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4:1.0/bluetooth/hci0
1166*9c5db199SXin Li        if 'usb' not in real_path:
1167*9c5db199SXin Li            return []
1168*9c5db199SXin Li
1169*9c5db199SXin Li        logging.debug('Searching for usb path: {}'.format(real_path))
1170*9c5db199SXin Li
1171*9c5db199SXin Li        # Grab all numbered entries between 'usb' and 'bluetooth' descriptors
1172*9c5db199SXin Li        m = re.search(r'usb(.*)bluetooth', real_path)
1173*9c5db199SXin Li
1174*9c5db199SXin Li        if not m:
1175*9c5db199SXin Li            logging.error(
1176*9c5db199SXin Li                    'Unable to extract usb dev from {}'.format(real_path))
1177*9c5db199SXin Li            return []
1178*9c5db199SXin Li
1179*9c5db199SXin Li        # Return the path as a list of individual usb descriptors
1180*9c5db199SXin Li        return m.group(1).split('/')
1181*9c5db199SXin Li
1182*9c5db199SXin Li    def get_bt_usb_disconnect_str(self):
1183*9c5db199SXin Li        """ Return the expected log error on USB disconnect
1184*9c5db199SXin Li
1185*9c5db199SXin Li        Locate the descriptor that will be used from the list of all usb
1186*9c5db199SXin Li        descriptors associated with our bluetooth chip, and format into the
1187*9c5db199SXin Li        expected string error for USB disconnect
1188*9c5db199SXin Li
1189*9c5db199SXin Li        @returns: string representing expected usb disconnect log entry if usb
1190*9c5db199SXin Li                  device could be identified, None otherwise
1191*9c5db199SXin Li        """
1192*9c5db199SXin Li        disconnect_log_template = 'usb {}: USB disconnect'
1193*9c5db199SXin Li        descriptors = self.get_bt_usb_device_strs()
1194*9c5db199SXin Li
1195*9c5db199SXin Li        # The usb disconnect log message seems to use the most detailed
1196*9c5db199SXin Li        # descriptor that does not use the ':1.0' entry
1197*9c5db199SXin Li        for d in sorted(descriptors, key=len, reverse=True):
1198*9c5db199SXin Li            if ':' not in d:
1199*9c5db199SXin Li                return disconnect_log_template.format(d)
1200*9c5db199SXin Li
1201*9c5db199SXin Li        return None
1202*9c5db199SXin Li
1203*9c5db199SXin Li    def get_device_utc_time(self):
1204*9c5db199SXin Li        """ Get the current device time in UTC. """
1205*9c5db199SXin Li        return datetime.utcnow().strftime(self.OUT_DATE_FORMAT)
1206*9c5db199SXin Li
1207*9c5db199SXin Li    def create_audio_record_directory(self, audio_record_dir):
1208*9c5db199SXin Li        """Create the audio recording directory.
1209*9c5db199SXin Li
1210*9c5db199SXin Li        @param audio_record_dir: the audio recording directory
1211*9c5db199SXin Li
1212*9c5db199SXin Li        @returns: True on success. False otherwise.
1213*9c5db199SXin Li        """
1214*9c5db199SXin Li        try:
1215*9c5db199SXin Li            if not os.path.exists(audio_record_dir):
1216*9c5db199SXin Li                os.makedirs(audio_record_dir)
1217*9c5db199SXin Li            return True
1218*9c5db199SXin Li        except Exception as e:
1219*9c5db199SXin Li            logging.error('Failed to create %s on the DUT: %s',
1220*9c5db199SXin Li                          audio_record_dir, e)
1221*9c5db199SXin Li            return False
1222*9c5db199SXin Li
1223*9c5db199SXin Li    def start_capturing_audio_subprocess(self, audio_data, recording_device):
1224*9c5db199SXin Li        """Start capturing audio in a subprocess.
1225*9c5db199SXin Li
1226*9c5db199SXin Li        @param audio_data: the audio test data
1227*9c5db199SXin Li        @param recording_device: which device recorded the audio,
1228*9c5db199SXin Li                possible values are 'recorded_by_dut' or 'recorded_by_peer'
1229*9c5db199SXin Li
1230*9c5db199SXin Li        @returns: True on success. False otherwise.
1231*9c5db199SXin Li        """
1232*9c5db199SXin Li        audio_data = json.loads(audio_data)
1233*9c5db199SXin Li        return self._cras_test_client.start_capturing_subprocess(
1234*9c5db199SXin Li                audio_data[recording_device],
1235*9c5db199SXin Li                sample_format=audio_data['format'],
1236*9c5db199SXin Li                channels=audio_data['channels'],
1237*9c5db199SXin Li                rate=audio_data['rate'],
1238*9c5db199SXin Li                duration=audio_data['duration'])
1239*9c5db199SXin Li
1240*9c5db199SXin Li    def stop_capturing_audio_subprocess(self):
1241*9c5db199SXin Li        """Stop capturing audio.
1242*9c5db199SXin Li
1243*9c5db199SXin Li        @returns: True on success. False otherwise.
1244*9c5db199SXin Li        """
1245*9c5db199SXin Li        return self._cras_test_client.stop_capturing_subprocess()
1246*9c5db199SXin Li
1247*9c5db199SXin Li    def _generate_playback_file(self, audio_data):
1248*9c5db199SXin Li        """Generate the playback file if it does not exist yet.
1249*9c5db199SXin Li
1250*9c5db199SXin Li        Some audio test files may be large. Generate them on the fly
1251*9c5db199SXin Li        to save the storage of the source tree.
1252*9c5db199SXin Li
1253*9c5db199SXin Li        @param audio_data: the audio test data
1254*9c5db199SXin Li        """
1255*9c5db199SXin Li        if not os.path.exists(audio_data['file']):
1256*9c5db199SXin Li            data_format = dict(file_type='raw',
1257*9c5db199SXin Li                               sample_format='S16_LE',
1258*9c5db199SXin Li                               channel=audio_data['channels'],
1259*9c5db199SXin Li                               rate=audio_data['rate'])
1260*9c5db199SXin Li
1261*9c5db199SXin Li            # Make the audio file a bit longer to handle any delay
1262*9c5db199SXin Li            # issue in capturing.
1263*9c5db199SXin Li            duration = audio_data['duration'] + 3
1264*9c5db199SXin Li            audio_test_data_module.GenerateAudioTestData(
1265*9c5db199SXin Li                    data_format=data_format,
1266*9c5db199SXin Li                    path=audio_data['file'],
1267*9c5db199SXin Li                    duration_secs=duration,
1268*9c5db199SXin Li                    frequencies=audio_data['frequencies'])
1269*9c5db199SXin Li            logging.debug("Raw file generated: %s", audio_data['file'])
1270*9c5db199SXin Li
1271*9c5db199SXin Li    def start_playing_audio_subprocess(self, audio_data, pin_device=None):
1272*9c5db199SXin Li        """Start playing audio in a subprocess.
1273*9c5db199SXin Li
1274*9c5db199SXin Li        @param audio_data: the audio test data.
1275*9c5db199SXin Li        @param pin_device: the device id to play audio.
1276*9c5db199SXin Li
1277*9c5db199SXin Li        @returns: True on success. False otherwise.
1278*9c5db199SXin Li        """
1279*9c5db199SXin Li        audio_data = json.loads(audio_data)
1280*9c5db199SXin Li        self._generate_playback_file(audio_data)
1281*9c5db199SXin Li        try:
1282*9c5db199SXin Li            return self._cras_test_client.start_playing_subprocess(
1283*9c5db199SXin Li                    audio_data['file'],
1284*9c5db199SXin Li                    pin_device=pin_device,
1285*9c5db199SXin Li                    channels=audio_data['channels'],
1286*9c5db199SXin Li                    rate=audio_data['rate'],
1287*9c5db199SXin Li                    duration=audio_data['duration'])
1288*9c5db199SXin Li        except Exception as e:
1289*9c5db199SXin Li            logging.error("start_playing_subprocess() failed: %s", str(e))
1290*9c5db199SXin Li            return False
1291*9c5db199SXin Li
1292*9c5db199SXin Li    def stop_playing_audio_subprocess(self):
1293*9c5db199SXin Li        """Stop playing audio in the subprocess.
1294*9c5db199SXin Li
1295*9c5db199SXin Li        @returns: True on success. False otherwise.
1296*9c5db199SXin Li        """
1297*9c5db199SXin Li        return self._cras_test_client.stop_playing_subprocess()
1298*9c5db199SXin Li
1299*9c5db199SXin Li    def play_audio(self, audio_data):
1300*9c5db199SXin Li        """Play audio.
1301*9c5db199SXin Li
1302*9c5db199SXin Li        It blocks until it has completed playing back the audio.
1303*9c5db199SXin Li
1304*9c5db199SXin Li        @param audio_data: the audio test data
1305*9c5db199SXin Li
1306*9c5db199SXin Li        @returns: True on success. False otherwise.
1307*9c5db199SXin Li        """
1308*9c5db199SXin Li        audio_data = json.loads(audio_data)
1309*9c5db199SXin Li        self._generate_playback_file(audio_data)
1310*9c5db199SXin Li        return self._cras_test_client.play(audio_data['file'],
1311*9c5db199SXin Li                                           channels=audio_data['channels'],
1312*9c5db199SXin Li                                           rate=audio_data['rate'],
1313*9c5db199SXin Li                                           duration=audio_data['duration'])
1314*9c5db199SXin Li
1315*9c5db199SXin Li    def check_audio_frames_legitimacy(self, audio_test_data, recording_device,
1316*9c5db199SXin Li                                      recorded_file):
1317*9c5db199SXin Li        """Get the number of frames in the recorded audio file.
1318*9c5db199SXin Li
1319*9c5db199SXin Li        @param audio_test_data: the audio test data
1320*9c5db199SXin Li        @param recording_device: which device recorded the audio,
1321*9c5db199SXin Li                possible values are 'recorded_by_dut' or 'recorded_by_peer'
1322*9c5db199SXin Li        @param recorded_file: the recorded file name
1323*9c5db199SXin Li
1324*9c5db199SXin Li        @returns: True if audio frames are legitimate.
1325*9c5db199SXin Li        """
1326*9c5db199SXin Li        if bool(recorded_file):
1327*9c5db199SXin Li            recorded_filename = recorded_file
1328*9c5db199SXin Li        else:
1329*9c5db199SXin Li            audio_test_data = json.loads(audio_test_data)
1330*9c5db199SXin Li            recorded_filename = audio_test_data[recording_device]
1331*9c5db199SXin Li
1332*9c5db199SXin Li        if recorded_filename.endswith('.raw'):
1333*9c5db199SXin Li            # Make sure that the recorded file does not contain all zeros.
1334*9c5db199SXin Li            filesize = os.path.getsize(recorded_filename)
1335*9c5db199SXin Li            cmd_str = 'cmp -s -n %d %s /dev/zero' % (filesize,
1336*9c5db199SXin Li                                                     recorded_filename)
1337*9c5db199SXin Li            try:
1338*9c5db199SXin Li                result = subprocess.call(cmd_str.split())
1339*9c5db199SXin Li                return result != 0
1340*9c5db199SXin Li            except Exception as e:
1341*9c5db199SXin Li                logging.error("Failed: %s (%s)", cmd_str, str(e))
1342*9c5db199SXin Li                return False
1343*9c5db199SXin Li        else:
1344*9c5db199SXin Li            # The recorded wav file should not be empty.
1345*9c5db199SXin Li            wav_file = check_quality.WaveFile(recorded_filename)
1346*9c5db199SXin Li            return wav_file.get_number_frames() > 0
1347*9c5db199SXin Li
1348*9c5db199SXin Li    def convert_audio_sample_rate(self, input_file, out_file, test_data,
1349*9c5db199SXin Li                                  new_rate):
1350*9c5db199SXin Li        """Convert audio file to new sample rate.
1351*9c5db199SXin Li
1352*9c5db199SXin Li        @param input_file: Path to file to upsample.
1353*9c5db199SXin Li        @param out_file: Path to create upsampled file.
1354*9c5db199SXin Li        @param test_data: Dictionary with information about file.
1355*9c5db199SXin Li        @param new_rate: New rate to upsample file to.
1356*9c5db199SXin Li
1357*9c5db199SXin Li        @returns: True if upsampling succeeded, False otherwise.
1358*9c5db199SXin Li        """
1359*9c5db199SXin Li        test_data = json.loads(test_data)
1360*9c5db199SXin Li        logging.debug('Resampling file {} to new rate {}'.format(
1361*9c5db199SXin Li                input_file, new_rate))
1362*9c5db199SXin Li
1363*9c5db199SXin Li        convert_format(input_file,
1364*9c5db199SXin Li                       test_data['channels'],
1365*9c5db199SXin Li                       test_data['bit_width'],
1366*9c5db199SXin Li                       test_data['rate'],
1367*9c5db199SXin Li                       out_file,
1368*9c5db199SXin Li                       test_data['channels'],
1369*9c5db199SXin Li                       test_data['bit_width'],
1370*9c5db199SXin Li                       new_rate,
1371*9c5db199SXin Li                       1.0,
1372*9c5db199SXin Li                       use_src_header=True,
1373*9c5db199SXin Li                       use_dst_header=True)
1374*9c5db199SXin Li
1375*9c5db199SXin Li        return os.path.isfile(out_file)
1376*9c5db199SXin Li
1377*9c5db199SXin Li    def trim_wav_file(self,
1378*9c5db199SXin Li                      in_file,
1379*9c5db199SXin Li                      out_file,
1380*9c5db199SXin Li                      new_duration,
1381*9c5db199SXin Li                      test_data,
1382*9c5db199SXin Li                      tolerance=0.1):
1383*9c5db199SXin Li        """Trim long file to desired length.
1384*9c5db199SXin Li
1385*9c5db199SXin Li        Trims audio file to length by cutting out silence from beginning and
1386*9c5db199SXin Li        end.
1387*9c5db199SXin Li
1388*9c5db199SXin Li        @param in_file: Path to audio file to be trimmed.
1389*9c5db199SXin Li        @param out_file: Path to trimmed audio file to create.
1390*9c5db199SXin Li        @param new_duration: A float representing the desired duration of
1391*9c5db199SXin Li                the resulting trimmed file.
1392*9c5db199SXin Li        @param test_data: Dictionary containing information about the test file.
1393*9c5db199SXin Li        @param tolerance: (optional) A float representing the allowable
1394*9c5db199SXin Li                difference between trimmed file length and desired duration
1395*9c5db199SXin Li
1396*9c5db199SXin Li        @returns: True if file was trimmed successfully, False otherwise.
1397*9c5db199SXin Li        """
1398*9c5db199SXin Li        test_data = json.loads(test_data)
1399*9c5db199SXin Li        trim_silence_from_wav_file(in_file, out_file, new_duration)
1400*9c5db199SXin Li        measured_length = get_file_length(out_file, test_data['channels'],
1401*9c5db199SXin Li                                          test_data['bit_width'],
1402*9c5db199SXin Li                                          test_data['rate'])
1403*9c5db199SXin Li        return abs(measured_length - new_duration) <= tolerance
1404*9c5db199SXin Li
1405*9c5db199SXin Li    def unzip_audio_test_data(self, tar_path, data_dir):
1406*9c5db199SXin Li        """Unzip audio test data files.
1407*9c5db199SXin Li
1408*9c5db199SXin Li        @param tar_path: Path to audio test data tarball on DUT.
1409*9c5db199SXin Li        @oaram data_dir: Path to directory where to extract test data directory.
1410*9c5db199SXin Li
1411*9c5db199SXin Li        @returns: True if audio test data folder exists, False otherwise.
1412*9c5db199SXin Li        """
1413*9c5db199SXin Li        logging.debug('Downloading audio test data on DUT')
1414*9c5db199SXin Li        # creates path to dir to extract test data to by taking name of the
1415*9c5db199SXin Li        # tarball without the extension eg. <dir>/file.ext to data_dir/file/
1416*9c5db199SXin Li        audio_test_dir = os.path.join(
1417*9c5db199SXin Li                data_dir,
1418*9c5db199SXin Li                os.path.split(tar_path)[1].split('.', 1)[0])
1419*9c5db199SXin Li
1420*9c5db199SXin Li        unzip_cmd = 'tar -xf {0} -C {1}'.format(tar_path, data_dir)
1421*9c5db199SXin Li
1422*9c5db199SXin Li        unzip_proc = subprocess.Popen(unzip_cmd.split(),
1423*9c5db199SXin Li                                      stdout=subprocess.PIPE,
1424*9c5db199SXin Li                                      stderr=subprocess.PIPE)
1425*9c5db199SXin Li        _, stderr = unzip_proc.communicate()
1426*9c5db199SXin Li
1427*9c5db199SXin Li        if stderr:
1428*9c5db199SXin Li            logging.error('Error occurred in unzipping audio data: {}'.format(
1429*9c5db199SXin Li                    str(stderr)))
1430*9c5db199SXin Li            return False
1431*9c5db199SXin Li
1432*9c5db199SXin Li        return unzip_proc.returncode == 0 and os.path.isdir(audio_test_dir)
1433*9c5db199SXin Li
1434*9c5db199SXin Li    def convert_raw_to_wav(self, input_file, output_file, test_data):
1435*9c5db199SXin Li        """Convert raw audio file to wav file.
1436*9c5db199SXin Li
1437*9c5db199SXin Li        @oaram input_file: the location of the raw file
1438*9c5db199SXin Li        @param output_file: the location to place the resulting wav file
1439*9c5db199SXin Li        @param test_data: the data for the file being converted
1440*9c5db199SXin Li
1441*9c5db199SXin Li        @returns: True if conversion was successful otherwise false
1442*9c5db199SXin Li        """
1443*9c5db199SXin Li        test_data = json.loads(test_data)
1444*9c5db199SXin Li        convert_raw_file(input_file, test_data['channels'],
1445*9c5db199SXin Li                         test_data['bit_width'], test_data['rate'],
1446*9c5db199SXin Li                         output_file)
1447*9c5db199SXin Li
1448*9c5db199SXin Li        return os.path.isfile(output_file)
1449*9c5db199SXin Li
1450*9c5db199SXin Li    def get_primary_frequencies(self, audio_test_data, recording_device,
1451*9c5db199SXin Li                                recorded_file):
1452*9c5db199SXin Li        """Get primary frequencies of the audio test file.
1453*9c5db199SXin Li
1454*9c5db199SXin Li        @param audio_test_data: the audio test data
1455*9c5db199SXin Li        @param recording_device: which device recorded the audio,
1456*9c5db199SXin Li                possible values are 'recorded_by_dut' or 'recorded_by_peer'
1457*9c5db199SXin Li        @param recorded_file: the recorded file name
1458*9c5db199SXin Li
1459*9c5db199SXin Li        @returns: a list of primary frequencies of channels in the audio file
1460*9c5db199SXin Li        """
1461*9c5db199SXin Li        audio_test_data = json.loads(audio_test_data)
1462*9c5db199SXin Li
1463*9c5db199SXin Li        if bool(recorded_file):
1464*9c5db199SXin Li            recorded_filename = recorded_file
1465*9c5db199SXin Li        else:
1466*9c5db199SXin Li            recorded_filename = audio_test_data[recording_device]
1467*9c5db199SXin Li
1468*9c5db199SXin Li        args = CheckQualityArgsClass(filename=recorded_filename,
1469*9c5db199SXin Li                                     rate=audio_test_data['rate'],
1470*9c5db199SXin Li                                     channel=audio_test_data['channels'],
1471*9c5db199SXin Li                                     bit_width=16)
1472*9c5db199SXin Li        raw_data, rate = check_quality.read_audio_file(args)
1473*9c5db199SXin Li        checker = check_quality.QualityChecker(raw_data, rate)
1474*9c5db199SXin Li        # The highest frequency recorded would be near 24 Khz
1475*9c5db199SXin Li        # as the max sample rate is 48000 in our tests.
1476*9c5db199SXin Li        # So let's set ignore_high_freq to be 48000.
1477*9c5db199SXin Li        checker.do_spectral_analysis(ignore_high_freq=48000,
1478*9c5db199SXin Li                                     check_quality=False,
1479*9c5db199SXin Li                                     quality_params=None)
1480*9c5db199SXin Li        spectra = checker._spectrals
1481*9c5db199SXin Li        primary_freq = [
1482*9c5db199SXin Li                float(spectra[i][0][0]) if spectra[i] else 0
1483*9c5db199SXin Li                for i in range(len(spectra))
1484*9c5db199SXin Li        ]
1485*9c5db199SXin Li        primary_freq.sort()
1486*9c5db199SXin Li        return primary_freq
1487*9c5db199SXin Li
1488*9c5db199SXin Li    def enable_wbs(self, value):
1489*9c5db199SXin Li        """Enable or disable wideband speech (wbs) per the value.
1490*9c5db199SXin Li
1491*9c5db199SXin Li        @param value: True to enable wbs.
1492*9c5db199SXin Li
1493*9c5db199SXin Li        @returns: True if the operation succeeds.
1494*9c5db199SXin Li        """
1495*9c5db199SXin Li        return self._cras_test_client.enable_wbs(value)
1496*9c5db199SXin Li
1497*9c5db199SXin Li    def set_player_playback_status(self, status):
1498*9c5db199SXin Li        """Set playback status for the registered media player.
1499*9c5db199SXin Li
1500*9c5db199SXin Li        @param status: playback status in string.
1501*9c5db199SXin Li
1502*9c5db199SXin Li        """
1503*9c5db199SXin Li        return self._cras_test_client.set_player_playback_status(status)
1504*9c5db199SXin Li
1505*9c5db199SXin Li    def set_player_position(self, position):
1506*9c5db199SXin Li        """Set media position for the registered media player.
1507*9c5db199SXin Li
1508*9c5db199SXin Li        @param position: position in micro seconds.
1509*9c5db199SXin Li
1510*9c5db199SXin Li        """
1511*9c5db199SXin Li        return self._cras_test_client.set_player_position(position)
1512*9c5db199SXin Li
1513*9c5db199SXin Li    def set_player_metadata(self, metadata):
1514*9c5db199SXin Li        """Set metadata for the registered media player.
1515*9c5db199SXin Li
1516*9c5db199SXin Li        @param metadata: dictionary of media metadata.
1517*9c5db199SXin Li
1518*9c5db199SXin Li        """
1519*9c5db199SXin Li        return self._cras_test_client.set_player_metadata(metadata)
1520*9c5db199SXin Li
1521*9c5db199SXin Li    def set_player_length(self, length):
1522*9c5db199SXin Li        """Set media length for the registered media player.
1523*9c5db199SXin Li
1524*9c5db199SXin Li        Media length is a part of metadata information. However, without
1525*9c5db199SXin Li        specify its type to int64. dbus-python will guess the variant type to
1526*9c5db199SXin Li        be int32 by default. Separate it from the metadata function to help
1527*9c5db199SXin Li        prepare the data differently.
1528*9c5db199SXin Li
1529*9c5db199SXin Li        @param length: length in micro seconds.
1530*9c5db199SXin Li
1531*9c5db199SXin Li        """
1532*9c5db199SXin Li        return self._cras_test_client.set_player_length(length)
1533*9c5db199SXin Li
1534*9c5db199SXin Li    def select_input_device(self, device_name):
1535*9c5db199SXin Li        """Select the audio input device.
1536*9c5db199SXin Li
1537*9c5db199SXin Li        @param device_name: the name of the Bluetooth peer device
1538*9c5db199SXin Li
1539*9c5db199SXin Li        @returns: True if the operation succeeds.
1540*9c5db199SXin Li        """
1541*9c5db199SXin Li        return self._cras_test_client.select_input_device(device_name)
1542*9c5db199SXin Li
1543*9c5db199SXin Li    @dbus_safe(None)
1544*9c5db199SXin Li    def select_output_node(self, node_type):
1545*9c5db199SXin Li        """Select the audio output node.
1546*9c5db199SXin Li
1547*9c5db199SXin Li        @param node_type: the node type of the Bluetooth peer device
1548*9c5db199SXin Li
1549*9c5db199SXin Li        @returns: True if the operation succeeds.
1550*9c5db199SXin Li        """
1551*9c5db199SXin Li        return cras_utils.set_single_selected_output_node(node_type)
1552*9c5db199SXin Li
1553*9c5db199SXin Li    @dbus_safe(None)
1554*9c5db199SXin Li    def get_selected_output_device_type(self):
1555*9c5db199SXin Li        """Get the selected audio output node type.
1556*9c5db199SXin Li
1557*9c5db199SXin Li        @returns: the node type of the selected output device.
1558*9c5db199SXin Li        """
1559*9c5db199SXin Li        # Note: should convert the dbus.String to the regular string.
1560*9c5db199SXin Li        return str(cras_utils.get_selected_output_device_type())
1561*9c5db199SXin Li
1562*9c5db199SXin Li    @dbus_safe(None)
1563*9c5db199SXin Li    def get_device_id_from_node_type(self, node_type, is_input):
1564*9c5db199SXin Li        """Gets device id from node type.
1565*9c5db199SXin Li
1566*9c5db199SXin Li        @param node_type: a node type defined in CRAS_NODE_TYPES.
1567*9c5db199SXin Li        @param is_input: True if the node is input. False otherwise.
1568*9c5db199SXin Li
1569*9c5db199SXin Li        @returns: a string for device id.
1570*9c5db199SXin Li        """
1571*9c5db199SXin Li        return cras_utils.get_device_id_from_node_type(node_type, is_input)
1572*9c5db199SXin Li
1573*9c5db199SXin Li    def get_audio_thread_summary(self):
1574*9c5db199SXin Li        """Dumps audio thread info.
1575*9c5db199SXin Li
1576*9c5db199SXin Li        @returns: a list of cras audio information.
1577*9c5db199SXin Li        """
1578*9c5db199SXin Li        return cras_utils.get_audio_thread_summary()
1579*9c5db199SXin Li
1580*9c5db199SXin Li    def is_btmanagerd_present(self):
1581*9c5db199SXin Li        """ Check if /usr/bin/btmanagerd file is present
1582*9c5db199SXin Li
1583*9c5db199SXin Li        @returns: True if /usr/bin/btmanagerd is present and False if not
1584*9c5db199SXin Li        """
1585*9c5db199SXin Li        return os.path.exists(self.BTMANGERD_FILE_PATH)
1586*9c5db199SXin Li
1587*9c5db199SXin Li
1588*9c5db199SXin Liclass BluezPairingAgent:
1589*9c5db199SXin Li    """The agent handling the authentication process of bluetooth pairing.
1590*9c5db199SXin Li
1591*9c5db199SXin Li    BluezPairingAgent overrides RequestPinCode method to return a given pin code.
1592*9c5db199SXin Li    User can use this agent to pair bluetooth device which has a known
1593*9c5db199SXin Li    pin code.
1594*9c5db199SXin Li
1595*9c5db199SXin Li    TODO (josephsih): more pairing modes other than pin code would be
1596*9c5db199SXin Li    supported later.
1597*9c5db199SXin Li
1598*9c5db199SXin Li    """
1599*9c5db199SXin Li
1600*9c5db199SXin Li    def __init__(self, bus, path, pin):
1601*9c5db199SXin Li        """Constructor.
1602*9c5db199SXin Li
1603*9c5db199SXin Li        @param bus: system bus object.
1604*9c5db199SXin Li        @param path: Object path to register.
1605*9c5db199SXin Li        @param pin: Pin to respond with for |RequestPinCode|.
1606*9c5db199SXin Li        """
1607*9c5db199SXin Li        self._pin = pin
1608*9c5db199SXin Li        self.path = path
1609*9c5db199SXin Li        self.obj = bus.register_object(path, self, None)
1610*9c5db199SXin Li
1611*9c5db199SXin Li    # D-Bus service definition (required by pydbus).
1612*9c5db199SXin Li    dbus = """
1613*9c5db199SXin Li        <node>
1614*9c5db199SXin Li            <interface name="org.bluez.Agent1">
1615*9c5db199SXin Li                <method name="RequestPinCode">
1616*9c5db199SXin Li                    <arg type="o" name="device_path" direction="in" />
1617*9c5db199SXin Li                    <arg type="s" name="response" direction="out" />
1618*9c5db199SXin Li                </method>
1619*9c5db199SXin Li                <method name="AuthorizeService">
1620*9c5db199SXin Li                    <arg type="o" name="device_path" direction="in" />
1621*9c5db199SXin Li                    <arg type="s" name="uuid" direction="in" />
1622*9c5db199SXin Li                    <arg type="b" name="response" direction="out" />
1623*9c5db199SXin Li                </method>
1624*9c5db199SXin Li            </interface>
1625*9c5db199SXin Li        </node>
1626*9c5db199SXin Li        """
1627*9c5db199SXin Li
1628*9c5db199SXin Li    def unregister(self):
1629*9c5db199SXin Li        """Unregisters self from bus."""
1630*9c5db199SXin Li        self.obj.unregister()
1631*9c5db199SXin Li
1632*9c5db199SXin Li    def RequestPinCode(self, device_path):
1633*9c5db199SXin Li        """Requests pin code for a device.
1634*9c5db199SXin Li
1635*9c5db199SXin Li        Returns the known pin code for the request.
1636*9c5db199SXin Li
1637*9c5db199SXin Li        @param device_path: The object path of the device.
1638*9c5db199SXin Li
1639*9c5db199SXin Li        @returns: The known pin code.
1640*9c5db199SXin Li
1641*9c5db199SXin Li        """
1642*9c5db199SXin Li        logging.info('RequestPinCode for %s; return %s', device_path,
1643*9c5db199SXin Li                     self._pin)
1644*9c5db199SXin Li        return self._pin
1645*9c5db199SXin Li
1646*9c5db199SXin Li    def AuthorizeService(self, device_path, uuid):
1647*9c5db199SXin Li        """Authorize given service for device.
1648*9c5db199SXin Li
1649*9c5db199SXin Li        @param device_path: The object path of the device.
1650*9c5db199SXin Li        @param uuid: The service that needs to be authorized.
1651*9c5db199SXin Li
1652*9c5db199SXin Li        @returns: True (we authorize everything since this is a test)
1653*9c5db199SXin Li        """
1654*9c5db199SXin Li        return True
1655*9c5db199SXin Li
1656*9c5db199SXin Li
1657*9c5db199SXin Liclass BluezFacadeLocal(BluetoothBaseFacadeLocal):
1658*9c5db199SXin Li    """Exposes DUT methods called remotely during Bluetooth autotests for the
1659*9c5db199SXin Li    Bluez daemon.
1660*9c5db199SXin Li
1661*9c5db199SXin Li    All instance methods of this object without a preceding '_' are exposed via
1662*9c5db199SXin Li    an XML-RPC server. This is not a stateless handler object, which means that
1663*9c5db199SXin Li    if you store state inside the delegate, that state will remain around for
1664*9c5db199SXin Li    future calls.
1665*9c5db199SXin Li    """
1666*9c5db199SXin Li
1667*9c5db199SXin Li    BLUETOOTHD_JOB = 'bluetoothd'
1668*9c5db199SXin Li
1669*9c5db199SXin Li    DBUS_ERROR_SERVICEUNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown'
1670*9c5db199SXin Li
1671*9c5db199SXin Li    BLUEZ_SERVICE_NAME = 'org.bluez'
1672*9c5db199SXin Li    BLUEZ_MANAGER_PATH = '/'
1673*9c5db199SXin Li    BLUEZ_DEBUG_LOG_PATH = '/org/chromium/Bluetooth'
1674*9c5db199SXin Li    BLUEZ_DEBUG_LOG_IFACE = 'org.chromium.Bluetooth.Debug'
1675*9c5db199SXin Li    BLUEZ_MANAGER_IFACE = 'org.freedesktop.DBus.ObjectManager'
1676*9c5db199SXin Li    BLUEZ_ADAPTER_IFACE = 'org.bluez.Adapter1'
1677*9c5db199SXin Li    BLUEZ_ADMIN_POLICY_SET_IFACE = 'org.bluez.AdminPolicySet1'
1678*9c5db199SXin Li    BLUEZ_ADMIN_POLICY_STATUS_IFACE = 'org.bluez.AdminPolicyStatus1'
1679*9c5db199SXin Li    BLUEZ_BATTERY_IFACE = 'org.bluez.Battery1'
1680*9c5db199SXin Li    BLUEZ_DEVICE_IFACE = 'org.bluez.Device1'
1681*9c5db199SXin Li    BLUEZ_GATT_SERV_IFACE = 'org.bluez.GattService1'
1682*9c5db199SXin Li    BLUEZ_GATT_CHAR_IFACE = 'org.bluez.GattCharacteristic1'
1683*9c5db199SXin Li    BLUEZ_GATT_DESC_IFACE = 'org.bluez.GattDescriptor1'
1684*9c5db199SXin Li    BLUEZ_LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
1685*9c5db199SXin Li    BLUEZ_ADV_MONITOR_MANAGER_IFACE = 'org.bluez.AdvertisementMonitorManager1'
1686*9c5db199SXin Li    BLUEZ_AGENT_MANAGER_PATH = '/org/bluez'
1687*9c5db199SXin Li    BLUEZ_AGENT_MANAGER_IFACE = 'org.bluez.AgentManager1'
1688*9c5db199SXin Li    BLUEZ_PROFILE_MANAGER_PATH = '/org/bluez'
1689*9c5db199SXin Li    BLUEZ_PROFILE_MANAGER_IFACE = 'org.bluez.ProfileManager1'
1690*9c5db199SXin Li    BLUEZ_ERROR_ALREADY_EXISTS = 'org.bluez.Error.AlreadyExists'
1691*9c5db199SXin Li    BLUEZ_PLUGIN_DEVICE_IFACE = 'org.chromium.BluetoothDevice'
1692*9c5db199SXin Li    DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
1693*9c5db199SXin Li    AGENT_PATH = '/test/agent'
1694*9c5db199SXin Li
1695*9c5db199SXin Li    BTMON_STOP_DELAY_SECS = 3
1696*9c5db199SXin Li
1697*9c5db199SXin Li    # Timeout for how long we'll wait for BlueZ and the Adapter to show up
1698*9c5db199SXin Li    # after reset.
1699*9c5db199SXin Li    ADAPTER_TIMEOUT = 30
1700*9c5db199SXin Li
1701*9c5db199SXin Li    # How long we should wait for property update signal before we cancel it.
1702*9c5db199SXin Li    PROPERTY_UPDATE_TIMEOUT_MILLI_SECS = 5000
1703*9c5db199SXin Li
1704*9c5db199SXin Li    # How often we should check for property update exit.
1705*9c5db199SXin Li    PROPERTY_UPDATE_CHECK_MILLI_SECS = 500
1706*9c5db199SXin Li
1707*9c5db199SXin Li    def __init__(self):
1708*9c5db199SXin Li        # Init the BaseFacade first
1709*9c5db199SXin Li        super(BluezFacadeLocal, self).__init__()
1710*9c5db199SXin Li
1711*9c5db199SXin Li        # Open the Bluetooth Raw socket to the kernel which provides us direct,
1712*9c5db199SXin Li        # raw, access to the HCI controller.
1713*9c5db199SXin Li        self._raw = bluetooth_socket.BluetoothRawSocket()
1714*9c5db199SXin Li
1715*9c5db199SXin Li        # Open the Bluetooth Control socket to the kernel which provides us
1716*9c5db199SXin Li        # raw management access to the Bluetooth Host Subsystem. Read the list
1717*9c5db199SXin Li        # of adapter indexes to determine whether or not this device has a
1718*9c5db199SXin Li        # Bluetooth Adapter or not.
1719*9c5db199SXin Li        self._control = bluetooth_socket.BluetoothControlSocket()
1720*9c5db199SXin Li        self._has_adapter = len(self._control.read_index_list()) > 0
1721*9c5db199SXin Li
1722*9c5db199SXin Li        # Create an Advertisement Monitor App Manager instance.
1723*9c5db199SXin Li        # This needs to be created before making any dbus connections as
1724*9c5db199SXin Li        # AdvMonitorAppMgr internally forks a new helper process and due to
1725*9c5db199SXin Li        # a limitation of python, it is not possible to fork a new process
1726*9c5db199SXin Li        # once any dbus connections are established.
1727*9c5db199SXin Li        self.advmon_appmgr = adv_monitor_helper.AdvMonitorAppMgr()
1728*9c5db199SXin Li
1729*9c5db199SXin Li        # Set up the connection to the D-Bus System Bus, get the object for
1730*9c5db199SXin Li        # the Bluetooth Userspace Daemon (BlueZ) and that daemon's object for
1731*9c5db199SXin Li        # the Bluetooth Adapter, and the advertising manager.
1732*9c5db199SXin Li        self.bus = pydbus.SystemBus()
1733*9c5db199SXin Li        self._update_bluez()
1734*9c5db199SXin Li        self._update_adapter()
1735*9c5db199SXin Li        self._update_advertising()
1736*9c5db199SXin Li        self._update_adv_monitor_manager()
1737*9c5db199SXin Li
1738*9c5db199SXin Li        # The agent to handle pin code request, which will be
1739*9c5db199SXin Li        # created when user calls pair_legacy_device method.
1740*9c5db199SXin Li        self._pairing_agent = None
1741*9c5db199SXin Li        # The default capability of the agent.
1742*9c5db199SXin Li        self._capability = 'KeyboardDisplay'
1743*9c5db199SXin Li
1744*9c5db199SXin Li        # Initialize a btmon object to record bluetoothd's activity.
1745*9c5db199SXin Li        self.btmon = output_recorder.OutputRecorder(
1746*9c5db199SXin Li                ['btmon', '-c', 'never'],
1747*9c5db199SXin Li                stop_delay_secs=self.BTMON_STOP_DELAY_SECS)
1748*9c5db199SXin Li
1749*9c5db199SXin Li        self.advertisements = []
1750*9c5db199SXin Li        self.advmon_interleave_logger = logger_helper.InterleaveLogger()
1751*9c5db199SXin Li        self._chrc_property = None
1752*9c5db199SXin Li        self._timeout_id = 0
1753*9c5db199SXin Li        self._signal_watch = None
1754*9c5db199SXin Li        self._dbus_mainloop = GObject.MainLoop()
1755*9c5db199SXin Li
1756*9c5db199SXin Li    @dbus_safe(False)
1757*9c5db199SXin Li    def set_debug_log_levels(self, bluez_vb, kernel_vb):
1758*9c5db199SXin Li        """Enable or disable the debug logs of bluetooth
1759*9c5db199SXin Li
1760*9c5db199SXin Li        @param bluez_vb: verbosity of bluez debug log, either 0 or 1
1761*9c5db199SXin Li        @param kernel_vb: verbosity of kernel debug log, either 0 or 1
1762*9c5db199SXin Li
1763*9c5db199SXin Li        """
1764*9c5db199SXin Li        debug_object = self.bus.get(self.BLUEZ_SERVICE_NAME,
1765*9c5db199SXin Li                                    self.BLUEZ_DEBUG_LOG_PATH)
1766*9c5db199SXin Li
1767*9c5db199SXin Li        # Make a raw synchronous call using GLib (pydbus doesn't correctly
1768*9c5db199SXin Li        # serialize '(yy)'.
1769*9c5db199SXin Li        raw_dbus_call_sync(self.bus, debug_object, self.BLUEZ_DEBUG_LOG_IFACE,
1770*9c5db199SXin Li                           'SetLevels',
1771*9c5db199SXin Li                           GLib.Variant('(yy)', (bluez_vb, kernel_vb)),
1772*9c5db199SXin Li                           GLib.VariantType.new('()'))
1773*9c5db199SXin Li        return
1774*9c5db199SXin Li
1775*9c5db199SXin Li    @dbus_safe(False)
1776*9c5db199SXin Li    def set_quality_debug_log(self, enable):
1777*9c5db199SXin Li        """Enable or disable bluez quality debug log in the DUT
1778*9c5db199SXin Li        @param enable: True to enable all of the debug log,
1779*9c5db199SXin Li                       False to disable all of the debug log.
1780*9c5db199SXin Li        """
1781*9c5db199SXin Li        bluez_debug = self.bus.get(
1782*9c5db199SXin Li                self.BLUEZ_SERVICE_NAME, self.BLUEZ_DEBUG_LOG_PATH)[
1783*9c5db199SXin Li                        self.BLUEZ_DEBUG_LOG_IFACE]
1784*9c5db199SXin Li        bluez_debug.SetQualityDebug(enable)
1785*9c5db199SXin Li
1786*9c5db199SXin Li    @dbus_safe(False)
1787*9c5db199SXin Li    def start_bluetoothd(self):
1788*9c5db199SXin Li        """start bluetoothd.
1789*9c5db199SXin Li
1790*9c5db199SXin Li        This includes powering up the adapter.
1791*9c5db199SXin Li
1792*9c5db199SXin Li        @returns: True if bluetoothd is started correctly.
1793*9c5db199SXin Li                  False otherwise.
1794*9c5db199SXin Li
1795*9c5db199SXin Li        """
1796*9c5db199SXin Li        # Always start bluez tests with Floss disabled
1797*9c5db199SXin Li        self.configure_floss(enabled=False)
1798*9c5db199SXin Li
1799*9c5db199SXin Li        # Start the daemon and exit if that fails.
1800*9c5db199SXin Li        if not UpstartClient.start(self.BLUETOOTHD_JOB):
1801*9c5db199SXin Li            return False
1802*9c5db199SXin Li
1803*9c5db199SXin Li        logging.debug('waiting for bluez start')
1804*9c5db199SXin Li        try:
1805*9c5db199SXin Li            utils.poll_for_condition(condition=self._update_bluez,
1806*9c5db199SXin Li                                     desc='Bluetooth Daemon has started.',
1807*9c5db199SXin Li                                     timeout=self.ADAPTER_TIMEOUT)
1808*9c5db199SXin Li        except Exception as e:
1809*9c5db199SXin Li            logging.error('timeout: error starting bluetoothd: %s', e)
1810*9c5db199SXin Li            return False
1811*9c5db199SXin Li
1812*9c5db199SXin Li        # Waiting for the self._adapter object.
1813*9c5db199SXin Li        # This does not mean that the adapter is powered on.
1814*9c5db199SXin Li        logging.debug('waiting for bluez to obtain adapter information')
1815*9c5db199SXin Li        try:
1816*9c5db199SXin Li            utils.poll_for_condition(
1817*9c5db199SXin Li                    condition=self._update_adapter,
1818*9c5db199SXin Li                    desc='Bluetooth Daemon has adapter information.',
1819*9c5db199SXin Li                    timeout=self.ADAPTER_TIMEOUT)
1820*9c5db199SXin Li        except Exception as e:
1821*9c5db199SXin Li            logging.error('timeout: error starting adapter: %s', e)
1822*9c5db199SXin Li            return False
1823*9c5db199SXin Li
1824*9c5db199SXin Li        # Waiting for the self._advertising interface object.
1825*9c5db199SXin Li        logging.debug('waiting for bluez to obtain interface manager.')
1826*9c5db199SXin Li        try:
1827*9c5db199SXin Li            utils.poll_for_condition(
1828*9c5db199SXin Li                    condition=self._update_advertising,
1829*9c5db199SXin Li                    desc='Bluetooth Daemon has advertising interface.',
1830*9c5db199SXin Li                    timeout=self.ADAPTER_TIMEOUT)
1831*9c5db199SXin Li        except utils.TimeoutError:
1832*9c5db199SXin Li            logging.error('timeout: error getting advertising interface')
1833*9c5db199SXin Li            return False
1834*9c5db199SXin Li
1835*9c5db199SXin Li        # Register the pairing agent so we can authorize connections
1836*9c5db199SXin Li        logging.debug('registering default pairing agent')
1837*9c5db199SXin Li        self._setup_pairing_agent(0)
1838*9c5db199SXin Li
1839*9c5db199SXin Li        return True
1840*9c5db199SXin Li
1841*9c5db199SXin Li    @dbus_safe(False)
1842*9c5db199SXin Li    def stop_bluetoothd(self):
1843*9c5db199SXin Li        """stop bluetoothd.
1844*9c5db199SXin Li
1845*9c5db199SXin Li        @returns: True if bluetoothd is stopped correctly.
1846*9c5db199SXin Li                  False otherwise.
1847*9c5db199SXin Li
1848*9c5db199SXin Li        """
1849*9c5db199SXin Li
1850*9c5db199SXin Li        def bluez_stopped():
1851*9c5db199SXin Li            """Checks the bluetooth daemon status.
1852*9c5db199SXin Li
1853*9c5db199SXin Li            @returns: True if bluez is stopped. False otherwise.
1854*9c5db199SXin Li
1855*9c5db199SXin Li            """
1856*9c5db199SXin Li            return not self._update_bluez()
1857*9c5db199SXin Li
1858*9c5db199SXin Li        # Stop the daemon and exit if that fails.
1859*9c5db199SXin Li        if not UpstartClient.stop(self.BLUETOOTHD_JOB):
1860*9c5db199SXin Li            return False
1861*9c5db199SXin Li
1862*9c5db199SXin Li        logging.debug('waiting for bluez stop')
1863*9c5db199SXin Li        try:
1864*9c5db199SXin Li            utils.poll_for_condition(condition=bluez_stopped,
1865*9c5db199SXin Li                                     desc='Bluetooth Daemon has stopped.',
1866*9c5db199SXin Li                                     timeout=self.ADAPTER_TIMEOUT)
1867*9c5db199SXin Li            bluetoothd_stopped = True
1868*9c5db199SXin Li        except Exception as e:
1869*9c5db199SXin Li            logging.error('timeout: error stopping bluetoothd: %s', e)
1870*9c5db199SXin Li            bluetoothd_stopped = False
1871*9c5db199SXin Li
1872*9c5db199SXin Li        return bluetoothd_stopped
1873*9c5db199SXin Li
1874*9c5db199SXin Li    def restart_cras(self):
1875*9c5db199SXin Li        """Restarts the cras daemon."""
1876*9c5db199SXin Li        return self._restart_cras()
1877*9c5db199SXin Li
1878*9c5db199SXin Li    def is_bluetoothd_running(self):
1879*9c5db199SXin Li        """Is bluetoothd running?
1880*9c5db199SXin Li
1881*9c5db199SXin Li        @returns: True if bluetoothd is running
1882*9c5db199SXin Li
1883*9c5db199SXin Li        """
1884*9c5db199SXin Li        return bool(self._get_dbus_proxy_for_bluetoothd())
1885*9c5db199SXin Li
1886*9c5db199SXin Li    def is_bluetoothd_proxy_valid(self):
1887*9c5db199SXin Li        """Checks whether the proxy object for bluetoothd is ok.
1888*9c5db199SXin Li
1889*9c5db199SXin Li        The dbus proxy object (self._bluez) can become unusable if bluetoothd
1890*9c5db199SXin Li        crashes or restarts for any reason. This method checks whether this has
1891*9c5db199SXin Li        happened by attempting to use the object proxy. If bluetoothd has
1892*9c5db199SXin Li        restarted (or is not available), then the session will no longer be
1893*9c5db199SXin Li        valid and this will result in a dbus exception (GLib.Error).
1894*9c5db199SXin Li
1895*9c5db199SXin Li        Returns:
1896*9c5db199SXin Li            True if the bluez proxy is still usable. False otherwise.
1897*9c5db199SXin Li        """
1898*9c5db199SXin Li
1899*9c5db199SXin Li        try:
1900*9c5db199SXin Li            return self.is_bluetoothd_running() and bool(
1901*9c5db199SXin Li                    self._objmgr_proxy) and bool(
1902*9c5db199SXin Li                            self._objmgr_proxy.GetManagedObjects())
1903*9c5db199SXin Li        except GLib.Error:
1904*9c5db199SXin Li            return False
1905*9c5db199SXin Li
1906*9c5db199SXin Li    def _update_bluez(self):
1907*9c5db199SXin Li        """Store a D-Bus proxy for the Bluetooth daemon in self._bluez.
1908*9c5db199SXin Li
1909*9c5db199SXin Li        This may be called in a loop until it returns True to wait for the
1910*9c5db199SXin Li        daemon to be ready after it has been started.
1911*9c5db199SXin Li
1912*9c5db199SXin Li        @return True on success, False otherwise.
1913*9c5db199SXin Li
1914*9c5db199SXin Li        """
1915*9c5db199SXin Li        self._bluez = self._get_dbus_proxy_for_bluetoothd()
1916*9c5db199SXin Li        return bool(self._bluez)
1917*9c5db199SXin Li
1918*9c5db199SXin Li    @property
1919*9c5db199SXin Li    def _objmgr_proxy(self):
1920*9c5db199SXin Li        """Returns proxy object to object manager if bluez is valid."""
1921*9c5db199SXin Li        if self._bluez:
1922*9c5db199SXin Li            return self._bluez[self.BLUEZ_MANAGER_IFACE]
1923*9c5db199SXin Li
1924*9c5db199SXin Li        return None
1925*9c5db199SXin Li
1926*9c5db199SXin Li    @dbus_safe(False)
1927*9c5db199SXin Li    def _get_dbus_proxy_for_bluetoothd(self):
1928*9c5db199SXin Li        """Get the D-Bus proxy for the Bluetooth daemon.
1929*9c5db199SXin Li
1930*9c5db199SXin Li        @return True on success, False otherwise.
1931*9c5db199SXin Li
1932*9c5db199SXin Li        """
1933*9c5db199SXin Li        bluez = None
1934*9c5db199SXin Li        try:
1935*9c5db199SXin Li            bluez = self.bus.get(self.BLUEZ_SERVICE_NAME,
1936*9c5db199SXin Li                                 self.BLUEZ_MANAGER_PATH)
1937*9c5db199SXin Li            logging.debug('bluetoothd is running')
1938*9c5db199SXin Li        except GLib.Error as e:
1939*9c5db199SXin Li            # When bluetoothd is not running, the exception looks like
1940*9c5db199SXin Li            #     org.freedesktop.DBus.Error.ServiceUnknown: The name org.bluez
1941*9c5db199SXin Li            #     was not provided by any .service files
1942*9c5db199SXin Li            if self.DBUS_ERROR_SERVICEUNKNOWN in str(e):
1943*9c5db199SXin Li                logging.debug('bluetoothd is not running')
1944*9c5db199SXin Li            else:
1945*9c5db199SXin Li                logging.error('Error getting dbus proxy for Bluez: %s', e)
1946*9c5db199SXin Li        return bluez
1947*9c5db199SXin Li
1948*9c5db199SXin Li    def _update_adapter(self):
1949*9c5db199SXin Li        """Store a D-Bus proxy for the local adapter in self._adapter.
1950*9c5db199SXin Li
1951*9c5db199SXin Li        This may be called in a loop until it returns True to wait for the
1952*9c5db199SXin Li        daemon to be ready, and have obtained the adapter information itself,
1953*9c5db199SXin Li        after it has been started.
1954*9c5db199SXin Li
1955*9c5db199SXin Li        Since not all devices will have adapters, this will also return True
1956*9c5db199SXin Li        in the case where we have obtained an empty adapter index list from the
1957*9c5db199SXin Li        kernel.
1958*9c5db199SXin Li
1959*9c5db199SXin Li        Note that this method does not power on the adapter.
1960*9c5db199SXin Li
1961*9c5db199SXin Li        @return True on success, including if there is no local adapter,
1962*9c5db199SXin Li            False otherwise.
1963*9c5db199SXin Li
1964*9c5db199SXin Li        """
1965*9c5db199SXin Li        self._adapter = None
1966*9c5db199SXin Li        self._adapter_path = None
1967*9c5db199SXin Li
1968*9c5db199SXin Li        # Re-check kernel to make sure adapter is available
1969*9c5db199SXin Li        self._has_adapter = len(self._control.read_index_list()) > 0
1970*9c5db199SXin Li
1971*9c5db199SXin Li        if self._bluez is None:
1972*9c5db199SXin Li            logging.warning('Bluez not found!')
1973*9c5db199SXin Li            return False
1974*9c5db199SXin Li        if not self._has_adapter:
1975*9c5db199SXin Li            logging.debug('Device has no adapter; returning')
1976*9c5db199SXin Li            return True
1977*9c5db199SXin Li        (self._adapter, self._adapter_path) = self._get_adapter()
1978*9c5db199SXin Li        return bool(self._adapter)
1979*9c5db199SXin Li
1980*9c5db199SXin Li    def _update_advertising(self):
1981*9c5db199SXin Li        """Store a D-Bus proxy for the local advertising interface manager.
1982*9c5db199SXin Li
1983*9c5db199SXin Li        This may be called repeatedly in a loop until True is returned;
1984*9c5db199SXin Li        otherwise we wait for bluetoothd to start. After bluetoothd starts, we
1985*9c5db199SXin Li        check the existence of a local adapter and proceed to get the
1986*9c5db199SXin Li        advertisement interface manager.
1987*9c5db199SXin Li
1988*9c5db199SXin Li        Since not all devices will have adapters, this will also return True
1989*9c5db199SXin Li        in the case where there is no adapter.
1990*9c5db199SXin Li
1991*9c5db199SXin Li        @return True on success, including if there is no local adapter,
1992*9c5db199SXin Li                False otherwise.
1993*9c5db199SXin Li
1994*9c5db199SXin Li        """
1995*9c5db199SXin Li        self._advertising = None
1996*9c5db199SXin Li        if self._bluez is None:
1997*9c5db199SXin Li            logging.warning('Bluez not found!')
1998*9c5db199SXin Li            return False
1999*9c5db199SXin Li        if not self._has_adapter:
2000*9c5db199SXin Li            logging.debug('Device has no adapter; returning')
2001*9c5db199SXin Li            return True
2002*9c5db199SXin Li        self._advertising = self._advertising_proxy
2003*9c5db199SXin Li        return bool(self._advertising)
2004*9c5db199SXin Li
2005*9c5db199SXin Li    def _update_adv_monitor_manager(self):
2006*9c5db199SXin Li        """Store a D-Bus proxy for the local advertisement monitor manager.
2007*9c5db199SXin Li
2008*9c5db199SXin Li        This may be called repeatedly in a loop until True is returned;
2009*9c5db199SXin Li        otherwise we wait for bluetoothd to start. After bluetoothd starts, we
2010*9c5db199SXin Li        check the existence of a local adapter and proceed to get the
2011*9c5db199SXin Li        advertisement monitor manager interface.
2012*9c5db199SXin Li
2013*9c5db199SXin Li        Since not all devices will have adapters, this will also return True
2014*9c5db199SXin Li        in the case where there is no adapter.
2015*9c5db199SXin Li
2016*9c5db199SXin Li        @return True on success, including if there is no local adapter,
2017*9c5db199SXin Li                False otherwise.
2018*9c5db199SXin Li
2019*9c5db199SXin Li        """
2020*9c5db199SXin Li        self._adv_monitor_manager = None
2021*9c5db199SXin Li        if self._bluez is None:
2022*9c5db199SXin Li            logging.warning('Bluez not found!')
2023*9c5db199SXin Li            return False
2024*9c5db199SXin Li        if not self._has_adapter:
2025*9c5db199SXin Li            logging.debug('Device has no adapter; returning without '
2026*9c5db199SXin Li                          'advertisement monitor manager')
2027*9c5db199SXin Li            return True
2028*9c5db199SXin Li        self._adv_monitor_manager = self._get_adv_monitor_manager()
2029*9c5db199SXin Li        return bool(self._adv_monitor_manager)
2030*9c5db199SXin Li
2031*9c5db199SXin Li    @dbus_safe(False)
2032*9c5db199SXin Li    def _get_adapter(self):
2033*9c5db199SXin Li        """Get the D-Bus proxy for the local adapter.
2034*9c5db199SXin Li
2035*9c5db199SXin Li        @return Tuple of (adapter, object_path) on success else (None, None).
2036*9c5db199SXin Li
2037*9c5db199SXin Li        """
2038*9c5db199SXin Li        objects = self._objmgr_proxy.GetManagedObjects()
2039*9c5db199SXin Li        for path, ifaces in six.iteritems(objects):
2040*9c5db199SXin Li            logging.debug('%s -> %r', path, list(ifaces.keys()))
2041*9c5db199SXin Li            if self.BLUEZ_ADAPTER_IFACE in ifaces:
2042*9c5db199SXin Li                logging.debug('using adapter %s', path)
2043*9c5db199SXin Li                adapter = self.bus.get(self.BLUEZ_SERVICE_NAME, path)
2044*9c5db199SXin Li                return (adapter, path)
2045*9c5db199SXin Li        else:
2046*9c5db199SXin Li            logging.warning('No adapter found in interface!')
2047*9c5db199SXin Li            return (None, None)
2048*9c5db199SXin Li
2049*9c5db199SXin Li    @property
2050*9c5db199SXin Li    def _adapter_proxy(self):
2051*9c5db199SXin Li        """Returns proxy object to adapter interface if adapter is valid."""
2052*9c5db199SXin Li        if self._adapter:
2053*9c5db199SXin Li            return self._adapter[self.BLUEZ_ADAPTER_IFACE]
2054*9c5db199SXin Li
2055*9c5db199SXin Li        return None
2056*9c5db199SXin Li
2057*9c5db199SXin Li    @property
2058*9c5db199SXin Li    def _property_proxy(self):
2059*9c5db199SXin Li        """Returns proxy object to adapter properties if adapter is valid."""
2060*9c5db199SXin Li        if self._adapter:
2061*9c5db199SXin Li            return self._adapter[self.DBUS_PROP_IFACE]
2062*9c5db199SXin Li
2063*9c5db199SXin Li        return None
2064*9c5db199SXin Li
2065*9c5db199SXin Li    @property
2066*9c5db199SXin Li    def _advertising_proxy(self):
2067*9c5db199SXin Li        """Returns proxy object to advertising interface if adapter is valid."""
2068*9c5db199SXin Li        if self._adapter:
2069*9c5db199SXin Li            return self._adapter[self.BLUEZ_LE_ADVERTISING_MANAGER_IFACE]
2070*9c5db199SXin Li
2071*9c5db199SXin Li        return None
2072*9c5db199SXin Li
2073*9c5db199SXin Li    @dbus_safe(False)
2074*9c5db199SXin Li    def _get_adv_monitor_manager(self):
2075*9c5db199SXin Li        """Get the D-Bus proxy for the local advertisement monitor manager.
2076*9c5db199SXin Li
2077*9c5db199SXin Li        @return the advertisement monitor manager interface object.
2078*9c5db199SXin Li
2079*9c5db199SXin Li        """
2080*9c5db199SXin Li        return self._adapter[self.BLUEZ_ADV_MONITOR_MANAGER_IFACE]
2081*9c5db199SXin Li
2082*9c5db199SXin Li    @dbus_safe(False)
2083*9c5db199SXin Li    def reset_on(self):
2084*9c5db199SXin Li        """Reset the adapter and settings and power up the adapter.
2085*9c5db199SXin Li
2086*9c5db199SXin Li        @return True on success, False otherwise.
2087*9c5db199SXin Li
2088*9c5db199SXin Li        """
2089*9c5db199SXin Li        return self._reset(set_power=True)
2090*9c5db199SXin Li
2091*9c5db199SXin Li    @dbus_safe(False)
2092*9c5db199SXin Li    def reset_off(self):
2093*9c5db199SXin Li        """Reset the adapter and settings, leave the adapter powered off.
2094*9c5db199SXin Li
2095*9c5db199SXin Li        @return True on success, False otherwise.
2096*9c5db199SXin Li
2097*9c5db199SXin Li        """
2098*9c5db199SXin Li        return self._reset(set_power=False)
2099*9c5db199SXin Li
2100*9c5db199SXin Li    def has_adapter(self):
2101*9c5db199SXin Li        """Return if an adapter is present.
2102*9c5db199SXin Li
2103*9c5db199SXin Li        This will only return True if we have determined both that there is
2104*9c5db199SXin Li        a Bluetooth adapter on this device (kernel adapter index list is not
2105*9c5db199SXin Li        empty) and that the Bluetooth daemon has exported an object for it.
2106*9c5db199SXin Li
2107*9c5db199SXin Li        @return True if an adapter is present, False if not.
2108*9c5db199SXin Li
2109*9c5db199SXin Li        """
2110*9c5db199SXin Li        return self._has_adapter and self._adapter is not None
2111*9c5db199SXin Li
2112*9c5db199SXin Li    def _reset(self, set_power=False):
2113*9c5db199SXin Li        """Remove remote devices and set adapter to set_power state.
2114*9c5db199SXin Li
2115*9c5db199SXin Li        Do not restart bluetoothd as this may incur a side effect.
2116*9c5db199SXin Li        The unhappy chrome may disable the adapter randomly.
2117*9c5db199SXin Li
2118*9c5db199SXin Li        @param set_power: adapter power state to set (True or False).
2119*9c5db199SXin Li
2120*9c5db199SXin Li        @return True on success, False otherwise.
2121*9c5db199SXin Li
2122*9c5db199SXin Li        """
2123*9c5db199SXin Li        logging.debug('_reset')
2124*9c5db199SXin Li
2125*9c5db199SXin Li        if not self._adapter:
2126*9c5db199SXin Li            logging.warning('Adapter not found!')
2127*9c5db199SXin Li            return False
2128*9c5db199SXin Li
2129*9c5db199SXin Li        objects = self._objmgr_proxy.GetManagedObjects()
2130*9c5db199SXin Li
2131*9c5db199SXin Li        devices = []
2132*9c5db199SXin Li        for path, ifaces in six.iteritems(objects):
2133*9c5db199SXin Li            if self.BLUEZ_DEVICE_IFACE in ifaces:
2134*9c5db199SXin Li                devices.append(objects[path][self.BLUEZ_DEVICE_IFACE])
2135*9c5db199SXin Li
2136*9c5db199SXin Li        # Turn on the adapter in order to remove all remote devices.
2137*9c5db199SXin Li        if not self.is_powered_on():
2138*9c5db199SXin Li            if not self.set_powered(True):
2139*9c5db199SXin Li                logging.warning('Unable to power on the adapter')
2140*9c5db199SXin Li                return False
2141*9c5db199SXin Li
2142*9c5db199SXin Li        for device in devices:
2143*9c5db199SXin Li            logging.debug('removing %s', device.get('Address'))
2144*9c5db199SXin Li            self.remove_device_object(device.get('Address'))
2145*9c5db199SXin Li
2146*9c5db199SXin Li        # Toggle power to the adapter.
2147*9c5db199SXin Li        if not self.set_powered(False):
2148*9c5db199SXin Li            logging.warning('Unable to power off adapter')
2149*9c5db199SXin Li            return False
2150*9c5db199SXin Li        if set_power and not self.set_powered(True):
2151*9c5db199SXin Li            logging.warning('Unable to power on adapter')
2152*9c5db199SXin Li            return False
2153*9c5db199SXin Li
2154*9c5db199SXin Li        return True
2155*9c5db199SXin Li
2156*9c5db199SXin Li    @dbus_safe(False)
2157*9c5db199SXin Li    def is_discoverable(self):
2158*9c5db199SXin Li        """Returns whether the adapter is discoverable."""
2159*9c5db199SXin Li        return bool(self._get_adapter_properties().get('Discoverable') == 1)
2160*9c5db199SXin Li
2161*9c5db199SXin Li    @dbus_safe(False)
2162*9c5db199SXin Li    def set_powered(self, powered):
2163*9c5db199SXin Li        """Set the adapter power state.
2164*9c5db199SXin Li
2165*9c5db199SXin Li        @param powered: adapter power state to set (True or False).
2166*9c5db199SXin Li
2167*9c5db199SXin Li        @return True on success, False otherwise.
2168*9c5db199SXin Li
2169*9c5db199SXin Li        """
2170*9c5db199SXin Li        if not self._adapter:
2171*9c5db199SXin Li            if not powered:
2172*9c5db199SXin Li                # Return success if we are trying to power off an adapter that's
2173*9c5db199SXin Li                # missing or gone away, since the expected result has happened.
2174*9c5db199SXin Li                return True
2175*9c5db199SXin Li            else:
2176*9c5db199SXin Li                logging.warning('Adapter not found!')
2177*9c5db199SXin Li                return False
2178*9c5db199SXin Li
2179*9c5db199SXin Li        logging.debug('_set_powered %r', powered)
2180*9c5db199SXin Li        self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 'Powered',
2181*9c5db199SXin Li                                 GLib.Variant('b', powered))
2182*9c5db199SXin Li
2183*9c5db199SXin Li        return True
2184*9c5db199SXin Li
2185*9c5db199SXin Li    @dbus_safe(False)
2186*9c5db199SXin Li    def set_discoverable(self, discoverable):
2187*9c5db199SXin Li        """Set the adapter discoverable state.
2188*9c5db199SXin Li
2189*9c5db199SXin Li        @param discoverable: adapter discoverable state to set (True or False).
2190*9c5db199SXin Li
2191*9c5db199SXin Li        @return True on success, False otherwise.
2192*9c5db199SXin Li
2193*9c5db199SXin Li        """
2194*9c5db199SXin Li        if not discoverable and not self._adapter:
2195*9c5db199SXin Li            # Return success if we are trying to make an adapter that's
2196*9c5db199SXin Li            # missing or gone away, undiscoverable, since the expected result
2197*9c5db199SXin Li            # has happened.
2198*9c5db199SXin Li            return True
2199*9c5db199SXin Li        self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 'Discoverable',
2200*9c5db199SXin Li                                 GLib.Variant('b', discoverable))
2201*9c5db199SXin Li        return True
2202*9c5db199SXin Li
2203*9c5db199SXin Li    @dbus_safe(False)
2204*9c5db199SXin Li    def get_discoverable_timeout(self):
2205*9c5db199SXin Li        """Get the adapter discoverable_timeout.
2206*9c5db199SXin Li
2207*9c5db199SXin Li        @return True on success, False otherwise.
2208*9c5db199SXin Li
2209*9c5db199SXin Li        """
2210*9c5db199SXin Li        return int(
2211*9c5db199SXin Li                self._property_proxy.Get(self.BLUEZ_ADAPTER_IFACE,
2212*9c5db199SXin Li                                         'DiscoverableTimeout'))
2213*9c5db199SXin Li
2214*9c5db199SXin Li    @dbus_safe(False)
2215*9c5db199SXin Li    def set_discoverable_timeout(self, discoverable_timeout):
2216*9c5db199SXin Li        """Set the adapter discoverable_timeout property.
2217*9c5db199SXin Li
2218*9c5db199SXin Li        @param discoverable_timeout: adapter discoverable_timeout value
2219*9c5db199SXin Li               in seconds to set (Integer).
2220*9c5db199SXin Li
2221*9c5db199SXin Li        @return True on success, False otherwise.
2222*9c5db199SXin Li
2223*9c5db199SXin Li        """
2224*9c5db199SXin Li        self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE,
2225*9c5db199SXin Li                                 'DiscoverableTimeout',
2226*9c5db199SXin Li                                 GLib.Variant('u', discoverable_timeout))
2227*9c5db199SXin Li        return True
2228*9c5db199SXin Li
2229*9c5db199SXin Li    @dbus_safe(False)
2230*9c5db199SXin Li    def get_pairable_timeout(self):
2231*9c5db199SXin Li        """Get the adapter pairable_timeout.
2232*9c5db199SXin Li
2233*9c5db199SXin Li        @return True on success, False otherwise.
2234*9c5db199SXin Li
2235*9c5db199SXin Li        """
2236*9c5db199SXin Li        return int(
2237*9c5db199SXin Li                self._property_proxy.Get(self.BLUEZ_ADAPTER_IFACE,
2238*9c5db199SXin Li                                         'PairableTimeout'))
2239*9c5db199SXin Li
2240*9c5db199SXin Li    @dbus_safe(False)
2241*9c5db199SXin Li    def set_pairable_timeout(self, pairable_timeout):
2242*9c5db199SXin Li        """Set the adapter pairable_timeout property.
2243*9c5db199SXin Li
2244*9c5db199SXin Li        @param pairable_timeout: adapter pairable_timeout value
2245*9c5db199SXin Li               in seconds to set (Integer).
2246*9c5db199SXin Li
2247*9c5db199SXin Li        @return True on success, False otherwise.
2248*9c5db199SXin Li
2249*9c5db199SXin Li        """
2250*9c5db199SXin Li        self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 'PairableTimeout',
2251*9c5db199SXin Li                                 GLib.Variant('u', pairable_timeout))
2252*9c5db199SXin Li        return True
2253*9c5db199SXin Li
2254*9c5db199SXin Li    @dbus_safe(False)
2255*9c5db199SXin Li    def get_pairable(self):
2256*9c5db199SXin Li        """Gets the adapter pairable state.
2257*9c5db199SXin Li
2258*9c5db199SXin Li        @return Pairable property value.
2259*9c5db199SXin Li        """
2260*9c5db199SXin Li        return bool(
2261*9c5db199SXin Li                self._property_proxy.Get(self.BLUEZ_ADAPTER_IFACE, 'Pairable'))
2262*9c5db199SXin Li
2263*9c5db199SXin Li    @dbus_safe(False)
2264*9c5db199SXin Li    def set_pairable(self, pairable):
2265*9c5db199SXin Li        """Set the adapter pairable state.
2266*9c5db199SXin Li
2267*9c5db199SXin Li        @param pairable: adapter pairable state to set (True or False).
2268*9c5db199SXin Li
2269*9c5db199SXin Li        @return True on success, False otherwise.
2270*9c5db199SXin Li
2271*9c5db199SXin Li        """
2272*9c5db199SXin Li        self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 'Pairable',
2273*9c5db199SXin Li                                 GLib.Variant('b', pairable))
2274*9c5db199SXin Li        return True
2275*9c5db199SXin Li
2276*9c5db199SXin Li    @dbus_safe(False)
2277*9c5db199SXin Li    def set_adapter_alias(self, alias):
2278*9c5db199SXin Li        """Set the adapter alias.
2279*9c5db199SXin Li
2280*9c5db199SXin Li        @param alias: adapter alias to set with type String
2281*9c5db199SXin Li
2282*9c5db199SXin Li        @return True on success, False otherwise.
2283*9c5db199SXin Li        """
2284*9c5db199SXin Li        self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 'Alias',
2285*9c5db199SXin Li                                 GLib.Variant('s', alias))
2286*9c5db199SXin Li        return True
2287*9c5db199SXin Li
2288*9c5db199SXin Li    def _get_adapter_properties(self):
2289*9c5db199SXin Li        """Read the adapter properties from the Bluetooth Daemon.
2290*9c5db199SXin Li
2291*9c5db199SXin Li        @return the properties as a JSON-encoded dictionary on success,
2292*9c5db199SXin Li            the value False otherwise.
2293*9c5db199SXin Li
2294*9c5db199SXin Li        """
2295*9c5db199SXin Li
2296*9c5db199SXin Li        @dbus_safe({})
2297*9c5db199SXin Li        def get_props():
2298*9c5db199SXin Li            """Get props from dbus."""
2299*9c5db199SXin Li            objects = self._objmgr_proxy.GetManagedObjects()
2300*9c5db199SXin Li            return objects[self._adapter_path][self.BLUEZ_ADAPTER_IFACE]
2301*9c5db199SXin Li
2302*9c5db199SXin Li        if self._bluez and self._adapter:
2303*9c5db199SXin Li            props = get_props().copy()
2304*9c5db199SXin Li        else:
2305*9c5db199SXin Li            props = {}
2306*9c5db199SXin Li        logging.debug('get_adapter_properties')
2307*9c5db199SXin Li        for i in props.items():
2308*9c5db199SXin Li            logging.debug(i)
2309*9c5db199SXin Li        return props
2310*9c5db199SXin Li
2311*9c5db199SXin Li    def get_adapter_properties(self):
2312*9c5db199SXin Li        return json.dumps(self._get_adapter_properties())
2313*9c5db199SXin Li
2314*9c5db199SXin Li    def is_powered_on(self):
2315*9c5db199SXin Li        """Checks whether the adapter is currently powered."""
2316*9c5db199SXin Li        return bool(self._get_adapter_properties().get('Powered'))
2317*9c5db199SXin Li
2318*9c5db199SXin Li    def get_address(self):
2319*9c5db199SXin Li        """Gets the current bluez adapter address."""
2320*9c5db199SXin Li        return str(self._get_adapter_properties()['Address'])
2321*9c5db199SXin Li
2322*9c5db199SXin Li    def get_bluez_version(self):
2323*9c5db199SXin Li        """Get the BlueZ version.
2324*9c5db199SXin Li
2325*9c5db199SXin Li        Returns:
2326*9c5db199SXin Li            Bluez version like 'BlueZ 5.39'.
2327*9c5db199SXin Li        """
2328*9c5db199SXin Li        return str(self._get_adapter_properties()['Name'])
2329*9c5db199SXin Li
2330*9c5db199SXin Li    def get_bluetooth_class(self):
2331*9c5db199SXin Li        """Get the bluetooth class of the adapter.
2332*9c5db199SXin Li
2333*9c5db199SXin Li        Example for Chromebook: 4718852
2334*9c5db199SXin Li
2335*9c5db199SXin Li        Returns:
2336*9c5db199SXin Li            Class of device for the adapter.
2337*9c5db199SXin Li        """
2338*9c5db199SXin Li        return str(self._get_adapter_properties()['Class'])
2339*9c5db199SXin Li
2340*9c5db199SXin Li    def read_version(self):
2341*9c5db199SXin Li        """Read the version of the management interface from the Kernel.
2342*9c5db199SXin Li
2343*9c5db199SXin Li        @return the information as a JSON-encoded tuple of:
2344*9c5db199SXin Li          ( version, revision )
2345*9c5db199SXin Li
2346*9c5db199SXin Li        """
2347*9c5db199SXin Li        #TODO(howardchung): resolve 'cannot allocate memory' error when
2348*9c5db199SXin Li        #                   BluetoothControlSocket idle too long(about 3 secs)
2349*9c5db199SXin Li        #                   (b:137603211)
2350*9c5db199SXin Li        _control = bluetooth_socket.BluetoothControlSocket()
2351*9c5db199SXin Li        return json.dumps(_control.read_version())
2352*9c5db199SXin Li
2353*9c5db199SXin Li    def read_supported_commands(self):
2354*9c5db199SXin Li        """Read the set of supported commands from the Kernel.
2355*9c5db199SXin Li
2356*9c5db199SXin Li        @return the information as a JSON-encoded tuple of:
2357*9c5db199SXin Li          ( commands, events )
2358*9c5db199SXin Li
2359*9c5db199SXin Li        """
2360*9c5db199SXin Li        #TODO(howardchung): resolve 'cannot allocate memory' error when
2361*9c5db199SXin Li        #                   BluetoothControlSocket idle too long(about 3 secs)
2362*9c5db199SXin Li        #                   (b:137603211)
2363*9c5db199SXin Li        _control = bluetooth_socket.BluetoothControlSocket()
2364*9c5db199SXin Li        return json.dumps(_control.read_supported_commands())
2365*9c5db199SXin Li
2366*9c5db199SXin Li    def read_index_list(self):
2367*9c5db199SXin Li        """Read the list of currently known controllers from the Kernel.
2368*9c5db199SXin Li
2369*9c5db199SXin Li        @return the information as a JSON-encoded array of controller indexes.
2370*9c5db199SXin Li
2371*9c5db199SXin Li        """
2372*9c5db199SXin Li        #TODO(howardchung): resolve 'cannot allocate memory' error when
2373*9c5db199SXin Li        #                   BluetoothControlSocket idle too long(about 3 secs)
2374*9c5db199SXin Li        #                   (b:137603211)
2375*9c5db199SXin Li        _control = bluetooth_socket.BluetoothControlSocket()
2376*9c5db199SXin Li        return json.dumps(_control.read_index_list())
2377*9c5db199SXin Li
2378*9c5db199SXin Li    def read_info(self):
2379*9c5db199SXin Li        """Read the adapter information from the Kernel.
2380*9c5db199SXin Li
2381*9c5db199SXin Li        @return the information as a JSON-encoded tuple of:
2382*9c5db199SXin Li          ( address, bluetooth_version, manufacturer_id,
2383*9c5db199SXin Li            supported_settings, current_settings, class_of_device,
2384*9c5db199SXin Li            name, short_name )
2385*9c5db199SXin Li
2386*9c5db199SXin Li        """
2387*9c5db199SXin Li        #TODO(howardchung): resolve 'cannot allocate memory' error when
2388*9c5db199SXin Li        #                   BluetoothControlSocket idle too long(about 3 secs)
2389*9c5db199SXin Li        #                   (b:137603211)
2390*9c5db199SXin Li        _control = bluetooth_socket.BluetoothControlSocket()
2391*9c5db199SXin Li        return json.dumps(_control.read_info(0))
2392*9c5db199SXin Li
2393*9c5db199SXin Li    def add_device(self, address, address_type, action):
2394*9c5db199SXin Li        """Add a device to the Kernel action list.
2395*9c5db199SXin Li
2396*9c5db199SXin Li        @param address: Address of the device to add.
2397*9c5db199SXin Li        @param address_type: Type of device in @address.
2398*9c5db199SXin Li        @param action: Action to take.
2399*9c5db199SXin Li
2400*9c5db199SXin Li        @return on success, a JSON-encoded typle of:
2401*9c5db199SXin Li          ( address, address_type ), None on failure.
2402*9c5db199SXin Li
2403*9c5db199SXin Li        """
2404*9c5db199SXin Li        #TODO(howardchung): resolve 'cannot allocate memory' error when
2405*9c5db199SXin Li        #                   BluetoothControlSocket idle too long(about 3 secs)
2406*9c5db199SXin Li        #                   (b:137603211)
2407*9c5db199SXin Li        _control = bluetooth_socket.BluetoothControlSocket()
2408*9c5db199SXin Li        return json.dumps(_control.add_device(0, address, address_type,
2409*9c5db199SXin Li                                              action))
2410*9c5db199SXin Li
2411*9c5db199SXin Li    def remove_device(self, address, address_type):
2412*9c5db199SXin Li        """Remove a device from the Kernel action list.
2413*9c5db199SXin Li
2414*9c5db199SXin Li        @param address: Address of the device to remove.
2415*9c5db199SXin Li        @param address_type: Type of device in @address.
2416*9c5db199SXin Li
2417*9c5db199SXin Li        @return on success, a JSON-encoded typle of:
2418*9c5db199SXin Li          ( address, address_type ), None on failure.
2419*9c5db199SXin Li
2420*9c5db199SXin Li        """
2421*9c5db199SXin Li        #TODO(howardchung): resolve 'cannot allocate memory' error when
2422*9c5db199SXin Li        #                   BluetoothControlSocket idle too long(about 3 secs)
2423*9c5db199SXin Li        #                   (b:137603211)
2424*9c5db199SXin Li        _control = bluetooth_socket.BluetoothControlSocket()
2425*9c5db199SXin Li        return json.dumps(_control.remove_device(0, address, address_type))
2426*9c5db199SXin Li
2427*9c5db199SXin Li    @dbus_safe(False)
2428*9c5db199SXin Li    def _get_devices(self):
2429*9c5db199SXin Li        """Read information about remote devices known to the adapter.
2430*9c5db199SXin Li
2431*9c5db199SXin Li        @return the properties of each device in a list
2432*9c5db199SXin Li
2433*9c5db199SXin Li        """
2434*9c5db199SXin Li        objects = self._objmgr_proxy.GetManagedObjects()
2435*9c5db199SXin Li        devices = []
2436*9c5db199SXin Li        for path, ifaces in six.iteritems(objects):
2437*9c5db199SXin Li            if self.BLUEZ_DEVICE_IFACE in ifaces:
2438*9c5db199SXin Li                devices.append(objects[path][self.BLUEZ_DEVICE_IFACE])
2439*9c5db199SXin Li        return devices
2440*9c5db199SXin Li
2441*9c5db199SXin Li    def _encode_json(self, data):
2442*9c5db199SXin Li        """Encodes input data as JSON object.
2443*9c5db199SXin Li
2444*9c5db199SXin Li        Note that for bytes elements in the input data, they are decoded as
2445*9c5db199SXin Li        unicode string.
2446*9c5db199SXin Li
2447*9c5db199SXin Li        @param data: data to be JSON encoded
2448*9c5db199SXin Li
2449*9c5db199SXin Li        @return: JSON encoded data
2450*9c5db199SXin Li        """
2451*9c5db199SXin Li        logging.debug('_encode_json raw data is %s', data)
2452*9c5db199SXin Li        str_data = utils.bytes_to_str_recursive(data)
2453*9c5db199SXin Li        json_encoded = json.dumps(str_data)
2454*9c5db199SXin Li        logging.debug('JSON encoded data is %s', json_encoded)
2455*9c5db199SXin Li        return json_encoded
2456*9c5db199SXin Li
2457*9c5db199SXin Li    def get_devices(self):
2458*9c5db199SXin Li        """Read information about remote devices known to the adapter.
2459*9c5db199SXin Li
2460*9c5db199SXin Li        @return the properties of each device as a JSON-encoded array of
2461*9c5db199SXin Li            dictionaries on success, the value False otherwise.
2462*9c5db199SXin Li
2463*9c5db199SXin Li        """
2464*9c5db199SXin Li        devices = self._get_devices()
2465*9c5db199SXin Li        # Note that bluetooth facade now runs in Python 3.
2466*9c5db199SXin Li        # Refer to crrev.com/c/3268347.
2467*9c5db199SXin Li        return self._encode_json(devices)
2468*9c5db199SXin Li
2469*9c5db199SXin Li    def get_num_connected_devices(self):
2470*9c5db199SXin Li        """ Return number of remote devices currently connected to the DUT.
2471*9c5db199SXin Li
2472*9c5db199SXin Li        @returns: The number of devices known to bluez with the Connected
2473*9c5db199SXin Li            property active
2474*9c5db199SXin Li        """
2475*9c5db199SXin Li        num_connected_devices = 0
2476*9c5db199SXin Li        for dev in self._get_devices():
2477*9c5db199SXin Li            if dev and dev.get('Connected', False):
2478*9c5db199SXin Li                num_connected_devices += 1
2479*9c5db199SXin Li
2480*9c5db199SXin Li        return num_connected_devices
2481*9c5db199SXin Li
2482*9c5db199SXin Li    @dbus_safe(None)
2483*9c5db199SXin Li    def get_device_property(self, address, prop_name):
2484*9c5db199SXin Li        """Read a property of BT device by directly querying device dbus object
2485*9c5db199SXin Li
2486*9c5db199SXin Li        @param address: Address of the device to query
2487*9c5db199SXin Li        @param prop_name: Property to be queried
2488*9c5db199SXin Li
2489*9c5db199SXin Li        @return Base 64 JSON repr of property if device is found and has
2490*9c5db199SXin Li                property, otherwise None on failure. JSON is a recursive
2491*9c5db199SXin Li                converter, automatically converting dbus types to python natives
2492*9c5db199SXin Li                and base64 allows us to pass special characters over xmlrpc.
2493*9c5db199SXin Li                Decode is done in bluetooth_device.py
2494*9c5db199SXin Li        """
2495*9c5db199SXin Li
2496*9c5db199SXin Li        prop_val = None
2497*9c5db199SXin Li
2498*9c5db199SXin Li        # Grab dbus object, _find_device will catch any thrown dbus error
2499*9c5db199SXin Li        device_obj = self._find_device(address)
2500*9c5db199SXin Li
2501*9c5db199SXin Li        if device_obj:
2502*9c5db199SXin Li            # Query dbus object for property
2503*9c5db199SXin Li            prop_val = unpack_if_variant(device_obj[self.DBUS_PROP_IFACE].Get(
2504*9c5db199SXin Li                    self.BLUEZ_DEVICE_IFACE, prop_name))
2505*9c5db199SXin Li
2506*9c5db199SXin Li        return self._encode_json(prop_val)
2507*9c5db199SXin Li
2508*9c5db199SXin Li    @dbus_safe(None)
2509*9c5db199SXin Li    def get_battery_property(self, address, prop_name):
2510*9c5db199SXin Li        """Read a property from Battery1 interface.
2511*9c5db199SXin Li
2512*9c5db199SXin Li        @param address: Address of the device to query
2513*9c5db199SXin Li        @param prop_name: Property to be queried
2514*9c5db199SXin Li
2515*9c5db199SXin Li        @return The battery percentage value, or None if does not exist.
2516*9c5db199SXin Li        """
2517*9c5db199SXin Li
2518*9c5db199SXin Li        prop_val = None
2519*9c5db199SXin Li
2520*9c5db199SXin Li        # Grab dbus object, _find_battery will catch any thrown dbus error
2521*9c5db199SXin Li        battery_obj = self._find_battery(address)
2522*9c5db199SXin Li
2523*9c5db199SXin Li        if battery_obj:
2524*9c5db199SXin Li            # Query dbus object for property
2525*9c5db199SXin Li            prop_val = unpack_if_variant(battery_obj[self.DBUS_PROP_IFACE].Get(
2526*9c5db199SXin Li                    self.BLUEZ_BATTERY_IFACE, prop_name))
2527*9c5db199SXin Li
2528*9c5db199SXin Li        return prop_val
2529*9c5db199SXin Li
2530*9c5db199SXin Li    @dbus_safe(False)
2531*9c5db199SXin Li    def set_discovery_filter(self, filter):
2532*9c5db199SXin Li        """Set the discovery filter.
2533*9c5db199SXin Li
2534*9c5db199SXin Li        @param filter: The discovery filter to set.
2535*9c5db199SXin Li
2536*9c5db199SXin Li        @return True on success, False otherwise.
2537*9c5db199SXin Li
2538*9c5db199SXin Li        """
2539*9c5db199SXin Li        if not self._adapter:
2540*9c5db199SXin Li            return False
2541*9c5db199SXin Li
2542*9c5db199SXin Li        converted_filter = {}
2543*9c5db199SXin Li        for key in filter:
2544*9c5db199SXin Li            converted_filter[key] = GLib.Variant('s', filter[key])
2545*9c5db199SXin Li
2546*9c5db199SXin Li        self._adapter_proxy.SetDiscoveryFilter(converted_filter)
2547*9c5db199SXin Li        return True
2548*9c5db199SXin Li
2549*9c5db199SXin Li    @dbus_safe(False, return_error=True)
2550*9c5db199SXin Li    def start_discovery(self):
2551*9c5db199SXin Li        """Start discovery of remote devices.
2552*9c5db199SXin Li
2553*9c5db199SXin Li        Obtain the discovered device information using get_devices(), called
2554*9c5db199SXin Li        stop_discovery() when done.
2555*9c5db199SXin Li
2556*9c5db199SXin Li        @return True on success, False otherwise.
2557*9c5db199SXin Li
2558*9c5db199SXin Li        """
2559*9c5db199SXin Li        if not self._adapter:
2560*9c5db199SXin Li            return (False, "Adapter Not Found")
2561*9c5db199SXin Li        self._adapter_proxy.StartDiscovery()
2562*9c5db199SXin Li        return (True, None)
2563*9c5db199SXin Li
2564*9c5db199SXin Li    @dbus_safe(False, return_error=True)
2565*9c5db199SXin Li    def stop_discovery(self):
2566*9c5db199SXin Li        """Stop discovery of remote devices.
2567*9c5db199SXin Li
2568*9c5db199SXin Li        @return True on success, False otherwise.
2569*9c5db199SXin Li
2570*9c5db199SXin Li        """
2571*9c5db199SXin Li        if not self._adapter:
2572*9c5db199SXin Li            return (False, "Adapter Not Found")
2573*9c5db199SXin Li        self._adapter_proxy.StopDiscovery()
2574*9c5db199SXin Li        return (True, None)
2575*9c5db199SXin Li
2576*9c5db199SXin Li    def is_discovering(self):
2577*9c5db199SXin Li        """Check if adapter is discovering."""
2578*9c5db199SXin Li        return self._get_adapter_properties().get('Discovering', 0) == 1
2579*9c5db199SXin Li
2580*9c5db199SXin Li    def get_dev_info(self):
2581*9c5db199SXin Li        """Read raw HCI device information.
2582*9c5db199SXin Li
2583*9c5db199SXin Li        @return JSON-encoded tuple of:
2584*9c5db199SXin Li                (index, name, address, flags, device_type, bus_type,
2585*9c5db199SXin Li                       features, pkt_type, link_policy, link_mode,
2586*9c5db199SXin Li                       acl_mtu, acl_pkts, sco_mtu, sco_pkts,
2587*9c5db199SXin Li                       err_rx, err_tx, cmd_tx, evt_rx, acl_tx, acl_rx,
2588*9c5db199SXin Li                       sco_tx, sco_rx, byte_rx, byte_tx) on success,
2589*9c5db199SXin Li                None on failure.
2590*9c5db199SXin Li
2591*9c5db199SXin Li        """
2592*9c5db199SXin Li        return json.dumps(self._raw.get_dev_info(0))
2593*9c5db199SXin Li
2594*9c5db199SXin Li    @dbus_safe(None, return_error=True)
2595*9c5db199SXin Li    def get_supported_capabilities(self):
2596*9c5db199SXin Li        """ Get supported capabilities of the adapter
2597*9c5db199SXin Li
2598*9c5db199SXin Li        @returns (capabilities, None) on Success. (None, <error>) on failure
2599*9c5db199SXin Li        """
2600*9c5db199SXin Li        value = self._adapter_proxy.GetSupportedCapabilities()
2601*9c5db199SXin Li        return (json.dumps(value), None)
2602*9c5db199SXin Li
2603*9c5db199SXin Li    @dbus_safe(False)
2604*9c5db199SXin Li    def register_profile(self, path, uuid, options):
2605*9c5db199SXin Li        """Register new profile (service).
2606*9c5db199SXin Li
2607*9c5db199SXin Li        @param path: Path to the profile object.
2608*9c5db199SXin Li        @param uuid: Service Class ID of the service as string.
2609*9c5db199SXin Li        @param options: Dictionary of options for the new service, compliant
2610*9c5db199SXin Li                        with BlueZ D-Bus Profile API standard.
2611*9c5db199SXin Li
2612*9c5db199SXin Li        @return True on success, False otherwise.
2613*9c5db199SXin Li
2614*9c5db199SXin Li        """
2615*9c5db199SXin Li        converted_options = {}
2616*9c5db199SXin Li        if 'ServiceRecord' in options:
2617*9c5db199SXin Li            converted_options['ServiceRecord'] = GLib.Variant(
2618*9c5db199SXin Li                    's', options['ServiceRecord'])
2619*9c5db199SXin Li
2620*9c5db199SXin Li        profile_manager = self.bus.get(
2621*9c5db199SXin Li                self.BLUEZ_SERVICE_NAME, self.BLUEZ_PROFILE_MANAGER_PATH)[
2622*9c5db199SXin Li                        self.BLUEZ_PROFILE_MANAGER_IFACE]
2623*9c5db199SXin Li        profile_manager.RegisterProfile(path, uuid, converted_options)
2624*9c5db199SXin Li        return True
2625*9c5db199SXin Li
2626*9c5db199SXin Li    def has_device(self, address):
2627*9c5db199SXin Li        """Checks if the device with a given address exists.
2628*9c5db199SXin Li
2629*9c5db199SXin Li        @param address: Address of the device.
2630*9c5db199SXin Li
2631*9c5db199SXin Li        @returns: True if there is an interface object with that address.
2632*9c5db199SXin Li                  False if the device is not found.
2633*9c5db199SXin Li
2634*9c5db199SXin Li        @raises: Exception if a D-Bus error is encountered.
2635*9c5db199SXin Li
2636*9c5db199SXin Li        """
2637*9c5db199SXin Li        result = self._find_device(address)
2638*9c5db199SXin Li        logging.debug('has_device result: %s', str(result))
2639*9c5db199SXin Li
2640*9c5db199SXin Li        # The result being False indicates that there is a D-Bus error.
2641*9c5db199SXin Li        if result is False:
2642*9c5db199SXin Li            raise Exception('dbus.Interface error')
2643*9c5db199SXin Li
2644*9c5db199SXin Li        # Return True if the result is not None, e.g. a D-Bus interface object;
2645*9c5db199SXin Li        # False otherwise.
2646*9c5db199SXin Li        return bool(result)
2647*9c5db199SXin Li
2648*9c5db199SXin Li    @dbus_safe(False)
2649*9c5db199SXin Li    def _find_device(self, address):
2650*9c5db199SXin Li        """Finds the device with a given address.
2651*9c5db199SXin Li
2652*9c5db199SXin Li        Find the device with a given address and returns the
2653*9c5db199SXin Li        device interface.
2654*9c5db199SXin Li
2655*9c5db199SXin Li        @param address: Address of the device.
2656*9c5db199SXin Li
2657*9c5db199SXin Li        @returns: An 'org.bluez.Device1' interface to the device.
2658*9c5db199SXin Li                  None if device can not be found.
2659*9c5db199SXin Li        """
2660*9c5db199SXin Li        path = self._get_device_path(address)
2661*9c5db199SXin Li        if path:
2662*9c5db199SXin Li            return self.bus.get(self.BLUEZ_SERVICE_NAME, path)
2663*9c5db199SXin Li        logging.info('Device not found')
2664*9c5db199SXin Li        return None
2665*9c5db199SXin Li
2666*9c5db199SXin Li    @dbus_safe(None)
2667*9c5db199SXin Li    def _find_battery(self, address):
2668*9c5db199SXin Li        """Finds the battery with a given address.
2669*9c5db199SXin Li
2670*9c5db199SXin Li        Find the battery with a given address and returns the
2671*9c5db199SXin Li        battery interface.
2672*9c5db199SXin Li
2673*9c5db199SXin Li        @param address: Address of the device.
2674*9c5db199SXin Li
2675*9c5db199SXin Li        @returns: An 'org.bluez.Battery1' interface to the device.
2676*9c5db199SXin Li                  None if device can not be found.
2677*9c5db199SXin Li        """
2678*9c5db199SXin Li        path = self._get_device_path(address)
2679*9c5db199SXin Li        if path:
2680*9c5db199SXin Li            try:
2681*9c5db199SXin Li                obj = self.bus.get(self.BLUEZ_SERVICE_NAME, path)
2682*9c5db199SXin Li                if obj[self.BLUEZ_BATTERY_IFACE] is not None:
2683*9c5db199SXin Li                    return obj
2684*9c5db199SXin Li            except:
2685*9c5db199SXin Li                pass
2686*9c5db199SXin Li        logging.info('Battery not found')
2687*9c5db199SXin Li        return None
2688*9c5db199SXin Li
2689*9c5db199SXin Li    @dbus_safe(False)
2690*9c5db199SXin Li    def _get_device_path(self, address):
2691*9c5db199SXin Li        """Gets the path for a device with a given address.
2692*9c5db199SXin Li
2693*9c5db199SXin Li        Find the device with a given address and returns the
2694*9c5db199SXin Li        the path for the device.
2695*9c5db199SXin Li
2696*9c5db199SXin Li        @param address: Address of the device.
2697*9c5db199SXin Li
2698*9c5db199SXin Li        @returns: The path to the address of the device, or None if device is
2699*9c5db199SXin Li            not found in the object tree.
2700*9c5db199SXin Li
2701*9c5db199SXin Li        """
2702*9c5db199SXin Li
2703*9c5db199SXin Li        # Create device path, i.e. '/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF' based
2704*9c5db199SXin Li        # on path assignment scheme used in bluez
2705*9c5db199SXin Li        address_up = address.replace(':', '_')
2706*9c5db199SXin Li        device_path = '{}/dev_{}'.format(self._adapter_path, address_up)
2707*9c5db199SXin Li
2708*9c5db199SXin Li        # Verify the Address property agrees to confirm we have the device
2709*9c5db199SXin Li        try:
2710*9c5db199SXin Li            device = self.bus.get(self.BLUEZ_SERVICE_NAME, device_path)
2711*9c5db199SXin Li            found_addr = device[self.DBUS_PROP_IFACE].Get(
2712*9c5db199SXin Li                    self.BLUEZ_DEVICE_IFACE, 'Address')
2713*9c5db199SXin Li
2714*9c5db199SXin Li            if found_addr == address:
2715*9c5db199SXin Li                logging.info('Device found at {}'.format(device_path))
2716*9c5db199SXin Li                return device_path
2717*9c5db199SXin Li
2718*9c5db199SXin Li        except KeyError as ke:
2719*9c5db199SXin Li            logging.debug('Couldn\'t reach device: %s: %s', address, ke)
2720*9c5db199SXin Li        except GLib.Error as e:
2721*9c5db199SXin Li            log_msg = 'Couldn\'t reach device: {}'.format(str(e))
2722*9c5db199SXin Li            logging.debug(log_msg)
2723*9c5db199SXin Li
2724*9c5db199SXin Li        logging.debug('No device found at {}'.format(device_path))
2725*9c5db199SXin Li        return None
2726*9c5db199SXin Li
2727*9c5db199SXin Li    @dbus_safe(False)
2728*9c5db199SXin Li    def _setup_pairing_agent(self, pin):
2729*9c5db199SXin Li        """Initializes and resiters a BluezPairingAgent to handle authentication.
2730*9c5db199SXin Li
2731*9c5db199SXin Li        @param pin: The pin code this agent will answer.
2732*9c5db199SXin Li
2733*9c5db199SXin Li        """
2734*9c5db199SXin Li        if self._pairing_agent:
2735*9c5db199SXin Li            logging.info(
2736*9c5db199SXin Li                    'Removing the old agent before initializing a new one')
2737*9c5db199SXin Li            self._pairing_agent.unregister()
2738*9c5db199SXin Li            self._pairing_agent = None
2739*9c5db199SXin Li
2740*9c5db199SXin Li        # Create and register pairing agent
2741*9c5db199SXin Li        self._pairing_agent = BluezPairingAgent(self.bus, self.AGENT_PATH, pin)
2742*9c5db199SXin Li
2743*9c5db199SXin Li        agent_manager = self.bus.get(
2744*9c5db199SXin Li                self.BLUEZ_SERVICE_NAME,
2745*9c5db199SXin Li                self.BLUEZ_AGENT_MANAGER_PATH)[self.BLUEZ_AGENT_MANAGER_IFACE]
2746*9c5db199SXin Li        try:
2747*9c5db199SXin Li            # Make sure agent is accessible on bus
2748*9c5db199SXin Li            #agent_obj = self.bus.get(self.BLUEZ_SERVICE_NAME, self.AGENT_PATH)
2749*9c5db199SXin Li            agent_manager.RegisterAgent(self.AGENT_PATH, str(self._capability))
2750*9c5db199SXin Li        except GLib.Error as e:
2751*9c5db199SXin Li            if self.BLUEZ_ERROR_ALREADY_EXISTS in str(e):
2752*9c5db199SXin Li                logging.info('Unregistering old agent and registering the new')
2753*9c5db199SXin Li                agent_manager.UnregisterAgent(self.AGENT_PATH)
2754*9c5db199SXin Li                agent_manager.RegisterAgent(self.AGENT_PATH,
2755*9c5db199SXin Li                                            str(self._capability))
2756*9c5db199SXin Li            else:
2757*9c5db199SXin Li                logging.error('Error setting up pin agent: %s', e)
2758*9c5db199SXin Li                raise
2759*9c5db199SXin Li        except Exception as e:
2760*9c5db199SXin Li            logging.debug('Setup pairing agent: %s', str(e))
2761*9c5db199SXin Li            raise
2762*9c5db199SXin Li        logging.info('Agent registered: %s', self.AGENT_PATH)
2763*9c5db199SXin Li
2764*9c5db199SXin Li    @dbus_safe(False)
2765*9c5db199SXin Li    def _is_paired(self, device):
2766*9c5db199SXin Li        """Checks if a device is paired.
2767*9c5db199SXin Li
2768*9c5db199SXin Li        @param device: An 'org.bluez.Device1' interface to the device.
2769*9c5db199SXin Li
2770*9c5db199SXin Li        @returns: True if device is paired. False otherwise.
2771*9c5db199SXin Li
2772*9c5db199SXin Li        """
2773*9c5db199SXin Li        props = device[self.DBUS_PROP_IFACE]
2774*9c5db199SXin Li        paired = props.Get(self.BLUEZ_DEVICE_IFACE, 'Paired')
2775*9c5db199SXin Li        return bool(paired)
2776*9c5db199SXin Li
2777*9c5db199SXin Li    @dbus_safe(False)
2778*9c5db199SXin Li    def device_is_paired(self, address):
2779*9c5db199SXin Li        """Checks if a device is paired.
2780*9c5db199SXin Li
2781*9c5db199SXin Li        @param address: address of the device.
2782*9c5db199SXin Li
2783*9c5db199SXin Li        @returns: True if device is paired. False otherwise.
2784*9c5db199SXin Li
2785*9c5db199SXin Li        """
2786*9c5db199SXin Li        device = self._find_device(address)
2787*9c5db199SXin Li        if not device:
2788*9c5db199SXin Li            logging.error('Device not found')
2789*9c5db199SXin Li            return False
2790*9c5db199SXin Li        return self._is_paired(device)
2791*9c5db199SXin Li
2792*9c5db199SXin Li    @dbus_safe(False)
2793*9c5db199SXin Li    def _is_connected(self, device):
2794*9c5db199SXin Li        """Checks if a device is connected.
2795*9c5db199SXin Li
2796*9c5db199SXin Li        @param device: An 'org.bluez.Device1' interface to the device.
2797*9c5db199SXin Li
2798*9c5db199SXin Li        @returns: True if device is connected. False otherwise.
2799*9c5db199SXin Li
2800*9c5db199SXin Li        """
2801*9c5db199SXin Li        props = device[self.DBUS_PROP_IFACE]
2802*9c5db199SXin Li        connected = props.Get(self.BLUEZ_DEVICE_IFACE, 'Connected')
2803*9c5db199SXin Li        logging.info('Got connected = %r', connected)
2804*9c5db199SXin Li        return bool(connected)
2805*9c5db199SXin Li
2806*9c5db199SXin Li    @dbus_safe(False)
2807*9c5db199SXin Li    def _set_trusted_by_device(self, device, trusted=True):
2808*9c5db199SXin Li        """Set the device trusted by device object.
2809*9c5db199SXin Li
2810*9c5db199SXin Li        @param device: the device object to set trusted.
2811*9c5db199SXin Li        @param trusted: True or False indicating whether to set trusted or not.
2812*9c5db199SXin Li
2813*9c5db199SXin Li        @returns: True if successful. False otherwise.
2814*9c5db199SXin Li
2815*9c5db199SXin Li        """
2816*9c5db199SXin Li        try:
2817*9c5db199SXin Li            properties = device[self.DBUS_PROP_IFACE]
2818*9c5db199SXin Li            properties.Set(self.BLUEZ_DEVICE_IFACE, 'Trusted',
2819*9c5db199SXin Li                           GLib.Variant('b', trusted))
2820*9c5db199SXin Li            return True
2821*9c5db199SXin Li        except Exception as e:
2822*9c5db199SXin Li            logging.error('_set_trusted_by_device: %s', e)
2823*9c5db199SXin Li        except:
2824*9c5db199SXin Li            logging.error('_set_trusted_by_device: unexpected error')
2825*9c5db199SXin Li        return False
2826*9c5db199SXin Li
2827*9c5db199SXin Li    @dbus_safe(False)
2828*9c5db199SXin Li    def _set_trusted_by_path(self, device_path, trusted=True):
2829*9c5db199SXin Li        """Set the device trusted by the device path.
2830*9c5db199SXin Li
2831*9c5db199SXin Li        @param device_path: the object path of the device.
2832*9c5db199SXin Li        @param trusted: True or False indicating whether to set trusted or not.
2833*9c5db199SXin Li
2834*9c5db199SXin Li        @returns: True if successful. False otherwise.
2835*9c5db199SXin Li
2836*9c5db199SXin Li        """
2837*9c5db199SXin Li        try:
2838*9c5db199SXin Li            device = self.bus.get(self.BLUEZ_SERVICE_NAME, device_path)
2839*9c5db199SXin Li            return self._set_trusted_by_device(device, trusted)
2840*9c5db199SXin Li        except Exception as e:
2841*9c5db199SXin Li            logging.error('_set_trusted_by_path: %s', e)
2842*9c5db199SXin Li        except:
2843*9c5db199SXin Li            logging.error('_set_trusted_by_path: unexpected error')
2844*9c5db199SXin Li        return False
2845*9c5db199SXin Li
2846*9c5db199SXin Li    @dbus_safe(False)
2847*9c5db199SXin Li    def set_trusted(self, address, trusted=True):
2848*9c5db199SXin Li        """Set the device trusted by address.
2849*9c5db199SXin Li
2850*9c5db199SXin Li        @param address: The bluetooth address of the device.
2851*9c5db199SXin Li        @param trusted: True or False indicating whether to set trusted or not.
2852*9c5db199SXin Li
2853*9c5db199SXin Li        @returns: True if successful. False otherwise.
2854*9c5db199SXin Li
2855*9c5db199SXin Li        """
2856*9c5db199SXin Li        try:
2857*9c5db199SXin Li            device = self._find_device(address)
2858*9c5db199SXin Li            return self._set_trusted_by_device(device, trusted)
2859*9c5db199SXin Li        except Exception as e:
2860*9c5db199SXin Li            logging.error('set_trusted: %s', e)
2861*9c5db199SXin Li        except:
2862*9c5db199SXin Li            logging.error('set_trusted: unexpected error')
2863*9c5db199SXin Li        return False
2864*9c5db199SXin Li
2865*9c5db199SXin Li    @dbus_safe(False)
2866*9c5db199SXin Li    def pair_legacy_device(self, address, pin, trusted, timeout=60):
2867*9c5db199SXin Li        """Pairs a device with a given pin code.
2868*9c5db199SXin Li
2869*9c5db199SXin Li        Registers a agent who handles pin code request and
2870*9c5db199SXin Li        pairs a device with known pin code. After pairing, this function will
2871*9c5db199SXin Li        automatically connect to the device as well (prevents timing issues
2872*9c5db199SXin Li        between pairing and connect and reduces overall test execution time).
2873*9c5db199SXin Li
2874*9c5db199SXin Li        @param address: Address of the device to pair.
2875*9c5db199SXin Li        @param pin: The pin code of the device to pair.
2876*9c5db199SXin Li        @param trusted: indicating whether to set the device trusted.
2877*9c5db199SXin Li        @param timeout: The timeout in seconds for pairing.
2878*9c5db199SXin Li
2879*9c5db199SXin Li        @returns: True on success. False otherwise.
2880*9c5db199SXin Li
2881*9c5db199SXin Li        """
2882*9c5db199SXin Li
2883*9c5db199SXin Li        def connect_reply():
2884*9c5db199SXin Li            """Handler when connect succeeded."""
2885*9c5db199SXin Li            logging.info('Device connected: %s', device_path)
2886*9c5db199SXin Li
2887*9c5db199SXin Li        def connect_error(error):
2888*9c5db199SXin Li            """Handler when connect failed.
2889*9c5db199SXin Li
2890*9c5db199SXin Li            @param error: one of the errors defined in org.bluez.Error
2891*9c5db199SXin Li            representing the error in connect.
2892*9c5db199SXin Li            """
2893*9c5db199SXin Li            logging.error('Connect device failed: %s', error)
2894*9c5db199SXin Li
2895*9c5db199SXin Li        def pair_reply():
2896*9c5db199SXin Li            """Handler when pairing succeeded."""
2897*9c5db199SXin Li            logging.info('Device paired: %s', device_path)
2898*9c5db199SXin Li            if trusted:
2899*9c5db199SXin Li                self._set_trusted_by_path(device_path, trusted=True)
2900*9c5db199SXin Li                logging.info('Device trusted: %s', device_path)
2901*9c5db199SXin Li
2902*9c5db199SXin Li            # On finishing pairing, also connect
2903*9c5db199SXin Li            self.dbus_method_with_handlers(device.Connect,
2904*9c5db199SXin Li                                           connect_reply,
2905*9c5db199SXin Li                                           connect_error,
2906*9c5db199SXin Li                                           timeout=timeout * 1000)
2907*9c5db199SXin Li
2908*9c5db199SXin Li        def pair_error(error):
2909*9c5db199SXin Li            """Handler when pairing failed.
2910*9c5db199SXin Li
2911*9c5db199SXin Li            @param error: one of errors defined in org.bluez.Error representing
2912*9c5db199SXin Li                          the error in pairing.
2913*9c5db199SXin Li
2914*9c5db199SXin Li            """
2915*9c5db199SXin Li            if 'org.freedesktop.DBus.Error.NoReply' in str(error):
2916*9c5db199SXin Li                logging.error('Timed out after %d ms. Cancelling pairing.',
2917*9c5db199SXin Li                              timeout)
2918*9c5db199SXin Li                device.CancelPairing()
2919*9c5db199SXin Li            else:
2920*9c5db199SXin Li                logging.error('Pairing device failed: %s', error)
2921*9c5db199SXin Li
2922*9c5db199SXin Li        device = self._find_device(address)
2923*9c5db199SXin Li        if not device:
2924*9c5db199SXin Li            logging.error('Device not found')
2925*9c5db199SXin Li            return False
2926*9c5db199SXin Li
2927*9c5db199SXin Li        device_path = self._get_device_path(address)
2928*9c5db199SXin Li        logging.info('Device %s is found.', device_path)
2929*9c5db199SXin Li
2930*9c5db199SXin Li        self._setup_pairing_agent(pin)
2931*9c5db199SXin Li
2932*9c5db199SXin Li        try:
2933*9c5db199SXin Li            if not self._is_paired(device):
2934*9c5db199SXin Li                logging.info('Device is not paired. Pair and Connect.')
2935*9c5db199SXin Li                self.dbus_method_with_handlers(device.Pair,
2936*9c5db199SXin Li                                               pair_reply,
2937*9c5db199SXin Li                                               pair_error,
2938*9c5db199SXin Li                                               timeout=timeout * 1000)
2939*9c5db199SXin Li            elif not self._is_connected(device):
2940*9c5db199SXin Li                logging.info('Device is already paired. Connect.')
2941*9c5db199SXin Li                self.dbus_method_with_handlers(device.Connect,
2942*9c5db199SXin Li                                               connect_reply,
2943*9c5db199SXin Li                                               connect_error,
2944*9c5db199SXin Li                                               tiemout=timeout * 1000)
2945*9c5db199SXin Li        except Exception as e:
2946*9c5db199SXin Li            logging.error('Exception %s in pair_legacy_device', e)
2947*9c5db199SXin Li            return False
2948*9c5db199SXin Li
2949*9c5db199SXin Li        return self._is_paired(device) and self._is_connected(device)
2950*9c5db199SXin Li
2951*9c5db199SXin Li    @dbus_safe(False)
2952*9c5db199SXin Li    def remove_device_object(self, address):
2953*9c5db199SXin Li        """Removes a device object and the pairing information.
2954*9c5db199SXin Li
2955*9c5db199SXin Li        Calls RemoveDevice method to remove remote device
2956*9c5db199SXin Li        object and the pairing information.
2957*9c5db199SXin Li
2958*9c5db199SXin Li        @param address: Address of the device to unpair.
2959*9c5db199SXin Li
2960*9c5db199SXin Li        @returns: True on success. False otherwise.
2961*9c5db199SXin Li
2962*9c5db199SXin Li        """
2963*9c5db199SXin Li        device = self._find_device(address)
2964*9c5db199SXin Li        if not device:
2965*9c5db199SXin Li            logging.error('Device not found')
2966*9c5db199SXin Li            return False
2967*9c5db199SXin Li        self._adapter_proxy.RemoveDevice(self._get_device_path(address))
2968*9c5db199SXin Li        return True
2969*9c5db199SXin Li
2970*9c5db199SXin Li    @dbus_safe(False)
2971*9c5db199SXin Li    def connect_device(self, address):
2972*9c5db199SXin Li        """Connects a device.
2973*9c5db199SXin Li
2974*9c5db199SXin Li        Connects a device if it is not connected.
2975*9c5db199SXin Li
2976*9c5db199SXin Li        @param address: Address of the device to connect.
2977*9c5db199SXin Li
2978*9c5db199SXin Li        @returns: True on success. False otherwise.
2979*9c5db199SXin Li
2980*9c5db199SXin Li        """
2981*9c5db199SXin Li        device = self._find_device(address)
2982*9c5db199SXin Li        if not device:
2983*9c5db199SXin Li            logging.error('Device not found')
2984*9c5db199SXin Li            return False
2985*9c5db199SXin Li        if self._is_connected(device):
2986*9c5db199SXin Li            logging.info('Device is already connected')
2987*9c5db199SXin Li            return True
2988*9c5db199SXin Li        device.Connect()
2989*9c5db199SXin Li        return self._is_connected(device)
2990*9c5db199SXin Li
2991*9c5db199SXin Li    @dbus_safe(False)
2992*9c5db199SXin Li    def device_is_connected(self, address):
2993*9c5db199SXin Li        """Checks if a device is connected.
2994*9c5db199SXin Li
2995*9c5db199SXin Li        @param address: Address of the device to connect.
2996*9c5db199SXin Li
2997*9c5db199SXin Li        @returns: True if device is connected. False otherwise.
2998*9c5db199SXin Li
2999*9c5db199SXin Li        """
3000*9c5db199SXin Li        device = self._find_device(address)
3001*9c5db199SXin Li        if not device:
3002*9c5db199SXin Li            logging.error('Device not found')
3003*9c5db199SXin Li            return False
3004*9c5db199SXin Li        return self._is_connected(device)
3005*9c5db199SXin Li
3006*9c5db199SXin Li    @dbus_safe(False)
3007*9c5db199SXin Li    def disconnect_device(self, address):
3008*9c5db199SXin Li        """Disconnects a device.
3009*9c5db199SXin Li
3010*9c5db199SXin Li        Disconnects a device if it is connected.
3011*9c5db199SXin Li
3012*9c5db199SXin Li        @param address: Address of the device to disconnect.
3013*9c5db199SXin Li
3014*9c5db199SXin Li        @returns: True on success. False otherwise.
3015*9c5db199SXin Li
3016*9c5db199SXin Li        """
3017*9c5db199SXin Li        device = self._find_device(address)
3018*9c5db199SXin Li        if not device:
3019*9c5db199SXin Li            logging.error('Device not found')
3020*9c5db199SXin Li            return False
3021*9c5db199SXin Li        if not self._is_connected(device):
3022*9c5db199SXin Li            logging.info('Device is not connected')
3023*9c5db199SXin Li            return True
3024*9c5db199SXin Li        device.Disconnect()
3025*9c5db199SXin Li        return not self._is_connected(device)
3026*9c5db199SXin Li
3027*9c5db199SXin Li    @dbus_safe(False)
3028*9c5db199SXin Li    def _device_services_resolved(self, device):
3029*9c5db199SXin Li        """Checks if services are resolved.
3030*9c5db199SXin Li
3031*9c5db199SXin Li        @param device: An 'org.bluez.Device1' interface to the device.
3032*9c5db199SXin Li
3033*9c5db199SXin Li        @returns: True if device is connected. False otherwise.
3034*9c5db199SXin Li
3035*9c5db199SXin Li        """
3036*9c5db199SXin Li        logging.info('device for services resolved: %s', device)
3037*9c5db199SXin Li        props = device[self.DBUS_PROP_IFACE]
3038*9c5db199SXin Li        resolved = props.Get(self.BLUEZ_DEVICE_IFACE, 'ServicesResolved')
3039*9c5db199SXin Li        logging.info('Services resolved = %r', resolved)
3040*9c5db199SXin Li        return bool(resolved)
3041*9c5db199SXin Li
3042*9c5db199SXin Li    @dbus_safe(False)
3043*9c5db199SXin Li    def device_services_resolved(self, address):
3044*9c5db199SXin Li        """Checks if service discovery is complete on a device.
3045*9c5db199SXin Li
3046*9c5db199SXin Li        Checks whether service discovery has been completed..
3047*9c5db199SXin Li
3048*9c5db199SXin Li        @param address: Address of the remote device.
3049*9c5db199SXin Li
3050*9c5db199SXin Li        @returns: True on success. False otherwise.
3051*9c5db199SXin Li
3052*9c5db199SXin Li        """
3053*9c5db199SXin Li        device = self._find_device(address)
3054*9c5db199SXin Li        if not device:
3055*9c5db199SXin Li            logging.error('Device not found')
3056*9c5db199SXin Li            return False
3057*9c5db199SXin Li
3058*9c5db199SXin Li        if not self._is_connected(device):
3059*9c5db199SXin Li            logging.info('Device is not connected')
3060*9c5db199SXin Li            return False
3061*9c5db199SXin Li
3062*9c5db199SXin Li        return self._device_services_resolved(device)
3063*9c5db199SXin Li
3064*9c5db199SXin Li    def btmon_start(self):
3065*9c5db199SXin Li        """Start btmon monitoring."""
3066*9c5db199SXin Li        self.btmon.start()
3067*9c5db199SXin Li
3068*9c5db199SXin Li    def btmon_stop(self):
3069*9c5db199SXin Li        """Stop btmon monitoring."""
3070*9c5db199SXin Li        self.btmon.stop()
3071*9c5db199SXin Li
3072*9c5db199SXin Li    def btmon_get(self, search_str, start_str):
3073*9c5db199SXin Li        """Get btmon output contents.
3074*9c5db199SXin Li
3075*9c5db199SXin Li        @param search_str: only lines with search_str would be kept.
3076*9c5db199SXin Li        @param start_str: all lines before the occurrence of start_str would be
3077*9c5db199SXin Li                filtered.
3078*9c5db199SXin Li
3079*9c5db199SXin Li        @returns: the recorded btmon output.
3080*9c5db199SXin Li
3081*9c5db199SXin Li        """
3082*9c5db199SXin Li        return self.btmon.get_contents(search_str=search_str,
3083*9c5db199SXin Li                                       start_str=start_str)
3084*9c5db199SXin Li
3085*9c5db199SXin Li    def btmon_find(self, pattern_str):
3086*9c5db199SXin Li        """Find if a pattern string exists in btmon output.
3087*9c5db199SXin Li
3088*9c5db199SXin Li        @param pattern_str: the pattern string to find.
3089*9c5db199SXin Li
3090*9c5db199SXin Li        @returns: True on success. False otherwise.
3091*9c5db199SXin Li
3092*9c5db199SXin Li        """
3093*9c5db199SXin Li        return self.btmon.find(pattern_str)
3094*9c5db199SXin Li
3095*9c5db199SXin Li    def dbus_method_with_handlers(self, dbus_method, reply_handler,
3096*9c5db199SXin Li                                  error_handler, *args, **kwargs):
3097*9c5db199SXin Li        """Run an async dbus method.
3098*9c5db199SXin Li
3099*9c5db199SXin Li        @param dbus_method: the dbus async method to invoke.
3100*9c5db199SXin Li        @param reply_handler: the reply handler for the dbus method.
3101*9c5db199SXin Li        @param error_handler: the error handler for the dbus method.
3102*9c5db199SXin Li        @param *args: additional arguments for the dbus method.
3103*9c5db199SXin Li        @param **kwargs: additional keyword arguments for the dbus method.
3104*9c5db199SXin Li
3105*9c5db199SXin Li        @returns: an empty string '' on success;
3106*9c5db199SXin Li                  None if there is no _advertising interface manager; and
3107*9c5db199SXin Li                  an error string if the dbus method fails or exception occurs
3108*9c5db199SXin Li
3109*9c5db199SXin Li        """
3110*9c5db199SXin Li
3111*9c5db199SXin Li        def successful_cb():
3112*9c5db199SXin Li            """Called when the dbus_method completed successfully."""
3113*9c5db199SXin Li            reply_handler()
3114*9c5db199SXin Li            self.dbus_cb_msg = ''
3115*9c5db199SXin Li
3116*9c5db199SXin Li        def error_cb(error):
3117*9c5db199SXin Li            """Called when the dbus_method failed."""
3118*9c5db199SXin Li            error_handler(error)
3119*9c5db199SXin Li            self.dbus_cb_msg = str(error)
3120*9c5db199SXin Li
3121*9c5db199SXin Li        # Successful dbus calls will have a non-throwing result and error
3122*9c5db199SXin Li        # results will throw GLib.Error.
3123*9c5db199SXin Li        try:
3124*9c5db199SXin Li            _ = dbus_method(*args, **kwargs)
3125*9c5db199SXin Li            successful_cb()
3126*9c5db199SXin Li        except GLib.Error as e:
3127*9c5db199SXin Li            error_cb(e)
3128*9c5db199SXin Li        except Exception as e:
3129*9c5db199SXin Li            logging.error('Exception %s in dbus_method_with_handlers ', e)
3130*9c5db199SXin Li            return str(e)
3131*9c5db199SXin Li
3132*9c5db199SXin Li        return self.dbus_cb_msg
3133*9c5db199SXin Li
3134*9c5db199SXin Li    def advmon_check_manager_interface_exist(self):
3135*9c5db199SXin Li        """Check if AdvertisementMonitorManager1 interface is available.
3136*9c5db199SXin Li
3137*9c5db199SXin Li        @returns: True if Manager interface is available, False otherwise.
3138*9c5db199SXin Li
3139*9c5db199SXin Li        """
3140*9c5db199SXin Li        objects = self._objmgr_proxy.GetManagedObjects()
3141*9c5db199SXin Li        for _, ifaces in six.iteritems(objects):
3142*9c5db199SXin Li            if self.BLUEZ_ADV_MONITOR_MANAGER_IFACE in ifaces:
3143*9c5db199SXin Li                return True
3144*9c5db199SXin Li
3145*9c5db199SXin Li        return False
3146*9c5db199SXin Li
3147*9c5db199SXin Li    def advmon_read_supported_types(self):
3148*9c5db199SXin Li        """Read the Advertisement Monitor supported monitor types.
3149*9c5db199SXin Li
3150*9c5db199SXin Li        Reads the value of 'SupportedMonitorTypes' property of the
3151*9c5db199SXin Li        AdvertisementMonitorManager1 interface on the adapter.
3152*9c5db199SXin Li
3153*9c5db199SXin Li        @returns: the list of the supported monitor types.
3154*9c5db199SXin Li
3155*9c5db199SXin Li        """
3156*9c5db199SXin Li        return unpack_if_variant(
3157*9c5db199SXin Li                self._property_proxy.Get(self.BLUEZ_ADV_MONITOR_MANAGER_IFACE,
3158*9c5db199SXin Li                                         'SupportedMonitorTypes'))
3159*9c5db199SXin Li
3160*9c5db199SXin Li    def advmon_read_supported_features(self):
3161*9c5db199SXin Li        """Read the Advertisement Monitor supported features.
3162*9c5db199SXin Li
3163*9c5db199SXin Li        Reads the value of 'SupportedFeatures' property of the
3164*9c5db199SXin Li        AdvertisementMonitorManager1 interface on the adapter.
3165*9c5db199SXin Li
3166*9c5db199SXin Li        @returns: the list of the supported features.
3167*9c5db199SXin Li
3168*9c5db199SXin Li        """
3169*9c5db199SXin Li        return unpack_if_variant(
3170*9c5db199SXin Li                self._property_proxy.Get(self.BLUEZ_ADV_MONITOR_MANAGER_IFACE,
3171*9c5db199SXin Li                                         'SupportedFeatures'))
3172*9c5db199SXin Li
3173*9c5db199SXin Li    def advmon_create_app(self):
3174*9c5db199SXin Li        """Create an advertisement monitor app.
3175*9c5db199SXin Li
3176*9c5db199SXin Li        @returns: app id, once the app is created.
3177*9c5db199SXin Li
3178*9c5db199SXin Li        """
3179*9c5db199SXin Li        return self.advmon_appmgr.create_app()
3180*9c5db199SXin Li
3181*9c5db199SXin Li    def advmon_exit_app(self, app_id):
3182*9c5db199SXin Li        """Exit an advertisement monitor app.
3183*9c5db199SXin Li
3184*9c5db199SXin Li        @param app_id: the app id.
3185*9c5db199SXin Li
3186*9c5db199SXin Li        @returns: True on success, False otherwise.
3187*9c5db199SXin Li
3188*9c5db199SXin Li        """
3189*9c5db199SXin Li        return self.advmon_appmgr.exit_app(app_id)
3190*9c5db199SXin Li
3191*9c5db199SXin Li    def advmon_kill_app(self, app_id):
3192*9c5db199SXin Li        """Kill an advertisement monitor app by sending SIGKILL.
3193*9c5db199SXin Li
3194*9c5db199SXin Li        @param app_id: the app id.
3195*9c5db199SXin Li
3196*9c5db199SXin Li        @returns: True on success, False otherwise.
3197*9c5db199SXin Li
3198*9c5db199SXin Li        """
3199*9c5db199SXin Li        return self.advmon_appmgr.kill_app(app_id)
3200*9c5db199SXin Li
3201*9c5db199SXin Li    def advmon_register_app(self, app_id):
3202*9c5db199SXin Li        """Register an advertisement monitor app.
3203*9c5db199SXin Li
3204*9c5db199SXin Li        @param app_id: the app id.
3205*9c5db199SXin Li
3206*9c5db199SXin Li        @returns: True on success, False otherwise.
3207*9c5db199SXin Li
3208*9c5db199SXin Li        """
3209*9c5db199SXin Li        return self.advmon_appmgr.register_app(app_id)
3210*9c5db199SXin Li
3211*9c5db199SXin Li    def advmon_unregister_app(self, app_id):
3212*9c5db199SXin Li        """Unregister an advertisement monitor app.
3213*9c5db199SXin Li
3214*9c5db199SXin Li        @param app_id: the app id.
3215*9c5db199SXin Li
3216*9c5db199SXin Li        @returns: True on success, False otherwise.
3217*9c5db199SXin Li
3218*9c5db199SXin Li        """
3219*9c5db199SXin Li        return self.advmon_appmgr.unregister_app(app_id)
3220*9c5db199SXin Li
3221*9c5db199SXin Li    def advmon_add_monitor(self, app_id, monitor_data):
3222*9c5db199SXin Li        """Create an Advertisement Monitor object.
3223*9c5db199SXin Li
3224*9c5db199SXin Li        @param app_id: the app id.
3225*9c5db199SXin Li        @param monitor_data: the list containing monitor type, RSSI filter
3226*9c5db199SXin Li                             values and patterns.
3227*9c5db199SXin Li
3228*9c5db199SXin Li        @returns: monitor id, once the monitor is created, None otherwise.
3229*9c5db199SXin Li
3230*9c5db199SXin Li        """
3231*9c5db199SXin Li        return self.advmon_appmgr.add_monitor(app_id, monitor_data)
3232*9c5db199SXin Li
3233*9c5db199SXin Li    def advmon_remove_monitor(self, app_id, monitor_id):
3234*9c5db199SXin Li        """Remove the Advertisement Monitor object.
3235*9c5db199SXin Li
3236*9c5db199SXin Li        @param app_id: the app id.
3237*9c5db199SXin Li        @param monitor_id: the monitor id.
3238*9c5db199SXin Li
3239*9c5db199SXin Li        @returns: True on success, False otherwise.
3240*9c5db199SXin Li
3241*9c5db199SXin Li        """
3242*9c5db199SXin Li        return self.advmon_appmgr.remove_monitor(app_id, monitor_id)
3243*9c5db199SXin Li
3244*9c5db199SXin Li    def advmon_get_event_count(self, app_id, monitor_id, event):
3245*9c5db199SXin Li        """Read the count of a particular event on the given monitor.
3246*9c5db199SXin Li
3247*9c5db199SXin Li        @param app_id: the app id.
3248*9c5db199SXin Li        @param monitor_id: the monitor id.
3249*9c5db199SXin Li        @param event: name of the specific event or 'All' for all events.
3250*9c5db199SXin Li
3251*9c5db199SXin Li        @returns: count of the specific event or dict of counts of all events.
3252*9c5db199SXin Li
3253*9c5db199SXin Li        """
3254*9c5db199SXin Li        return self.advmon_appmgr.get_event_count(app_id, monitor_id, event)
3255*9c5db199SXin Li
3256*9c5db199SXin Li    def advmon_reset_event_count(self, app_id, monitor_id, event):
3257*9c5db199SXin Li        """Reset the count of a particular event on the given monitor.
3258*9c5db199SXin Li
3259*9c5db199SXin Li        @param app_id: the app id.
3260*9c5db199SXin Li        @param monitor_id: the monitor id.
3261*9c5db199SXin Li        @param event: name of the specific event or 'All' for all events.
3262*9c5db199SXin Li
3263*9c5db199SXin Li        @returns: True on success, False otherwise.
3264*9c5db199SXin Li
3265*9c5db199SXin Li        """
3266*9c5db199SXin Li        return self.advmon_appmgr.reset_event_count(app_id, monitor_id, event)
3267*9c5db199SXin Li
3268*9c5db199SXin Li    def advmon_set_target_devices(self, app_id, monitor_id, devices):
3269*9c5db199SXin Li        """Set the target devices to the given monitor.
3270*9c5db199SXin Li
3271*9c5db199SXin Li        DeviceFound and DeviceLost will only be counted if it is triggered by a
3272*9c5db199SXin Li        target device.
3273*9c5db199SXin Li
3274*9c5db199SXin Li        @param app_id: the app id.
3275*9c5db199SXin Li        @param monitor_id: the monitor id.
3276*9c5db199SXin Li        @param devices: a list of devices in MAC address
3277*9c5db199SXin Li
3278*9c5db199SXin Li        @returns: True on success, False otherwise.
3279*9c5db199SXin Li
3280*9c5db199SXin Li        """
3281*9c5db199SXin Li        paths = []
3282*9c5db199SXin Li        for addr in devices:
3283*9c5db199SXin Li            paths.append('{}/dev_{}'.format(self._adapter_path,
3284*9c5db199SXin Li                                            addr.replace(':', '_')))
3285*9c5db199SXin Li
3286*9c5db199SXin Li        return self.advmon_appmgr.set_target_devices(app_id, monitor_id, paths)
3287*9c5db199SXin Li
3288*9c5db199SXin Li    def advmon_interleave_scan_logger_start(self):
3289*9c5db199SXin Li        """ Start interleave logger recording
3290*9c5db199SXin Li        """
3291*9c5db199SXin Li        self.advmon_interleave_logger.StartRecording()
3292*9c5db199SXin Li
3293*9c5db199SXin Li    def advmon_interleave_scan_logger_stop(self):
3294*9c5db199SXin Li        """ Stop interleave logger recording
3295*9c5db199SXin Li
3296*9c5db199SXin Li        @returns: True if logs were successfully collected,
3297*9c5db199SXin Li                  False otherwise.
3298*9c5db199SXin Li
3299*9c5db199SXin Li        """
3300*9c5db199SXin Li        return self.advmon_interleave_logger.StopRecording()
3301*9c5db199SXin Li
3302*9c5db199SXin Li    def advmon_interleave_scan_logger_get_records(self):
3303*9c5db199SXin Li        """ Get records in previous log collections
3304*9c5db199SXin Li
3305*9c5db199SXin Li        @returns: a list of records, where each item is a record of
3306*9c5db199SXin Li                  interleave |state| and the |time| the state starts.
3307*9c5db199SXin Li                  |state| could be {'no filter', 'allowlist'}
3308*9c5db199SXin Li                  |time| is system time in sec
3309*9c5db199SXin Li
3310*9c5db199SXin Li        """
3311*9c5db199SXin Li        return self.advmon_interleave_logger.records
3312*9c5db199SXin Li
3313*9c5db199SXin Li    def advmon_interleave_scan_logger_get_cancel_events(self):
3314*9c5db199SXin Li        """ Get cancel events in previous log collections
3315*9c5db199SXin Li
3316*9c5db199SXin Li        @returns: a list of cancel |time| when a interleave cancel event log
3317*9c5db199SXin Li                  was found.
3318*9c5db199SXin Li                  |time| is system time in sec
3319*9c5db199SXin Li
3320*9c5db199SXin Li        """
3321*9c5db199SXin Li        return self.advmon_interleave_logger.cancel_events
3322*9c5db199SXin Li
3323*9c5db199SXin Li    def register_advertisement(self, advertisement_data):
3324*9c5db199SXin Li        """Register an advertisement.
3325*9c5db199SXin Li
3326*9c5db199SXin Li        Note that rpc supports only conformable types. Hence, a
3327*9c5db199SXin Li        dict about the advertisement is passed as a parameter such
3328*9c5db199SXin Li        that the advertisement object could be constructed on the host.
3329*9c5db199SXin Li
3330*9c5db199SXin Li        @param advertisement_data: a dict of the advertisement to register.
3331*9c5db199SXin Li
3332*9c5db199SXin Li        @returns: True on success. False otherwise.
3333*9c5db199SXin Li
3334*9c5db199SXin Li        """
3335*9c5db199SXin Li        adv = advertisement.Advertisement(self.bus, advertisement_data)
3336*9c5db199SXin Li        self.advertisements.append(adv)
3337*9c5db199SXin Li        return self.dbus_method_with_handlers(
3338*9c5db199SXin Li                self._advertising.RegisterAdvertisement,
3339*9c5db199SXin Li                # reply handler
3340*9c5db199SXin Li                lambda: logging.info('register_advertisement: succeeded.'),
3341*9c5db199SXin Li                # error handler
3342*9c5db199SXin Li                lambda error: logging.error(
3343*9c5db199SXin Li                        'register_advertisement: failed: %s', str(error)),
3344*9c5db199SXin Li                # other arguments
3345*9c5db199SXin Li                adv.get_path(),
3346*9c5db199SXin Li                {})
3347*9c5db199SXin Li
3348*9c5db199SXin Li    def unregister_advertisement(self, advertisement_data):
3349*9c5db199SXin Li        """Unregister an advertisement.
3350*9c5db199SXin Li
3351*9c5db199SXin Li        Note that to unregister an advertisement, it is required to use
3352*9c5db199SXin Li        the same self._advertising interface manager. This is because
3353*9c5db199SXin Li        bluez only allows the same sender to invoke UnregisterAdvertisement
3354*9c5db199SXin Li        method. Hence, watch out that the bluetoothd is not restarted or
3355*9c5db199SXin Li        self.start_bluetoothd() is not executed between the time span that
3356*9c5db199SXin Li        an advertisement is registered and unregistered.
3357*9c5db199SXin Li
3358*9c5db199SXin Li        @param advertisement_data: a dict of the advertisements to unregister.
3359*9c5db199SXin Li
3360*9c5db199SXin Li        @returns: True on success. False otherwise.
3361*9c5db199SXin Li
3362*9c5db199SXin Li        """
3363*9c5db199SXin Li        path = advertisement_data.get('Path')
3364*9c5db199SXin Li        for index, adv in enumerate(self.advertisements):
3365*9c5db199SXin Li            if adv.get_path() == path:
3366*9c5db199SXin Li                break
3367*9c5db199SXin Li        else:
3368*9c5db199SXin Li            logging.error('Fail to find the advertisement under the path: %s',
3369*9c5db199SXin Li                          path)
3370*9c5db199SXin Li            return False
3371*9c5db199SXin Li
3372*9c5db199SXin Li        result = self.dbus_method_with_handlers(
3373*9c5db199SXin Li                self._advertising.UnregisterAdvertisement,
3374*9c5db199SXin Li                # reply handler
3375*9c5db199SXin Li                lambda: logging.info('unregister_advertisement: succeeded.'),
3376*9c5db199SXin Li                # error handler
3377*9c5db199SXin Li                lambda error: logging.error(
3378*9c5db199SXin Li                        'unregister_advertisement: failed: %s', str(error)),
3379*9c5db199SXin Li                # other arguments
3380*9c5db199SXin Li                adv.get_path())
3381*9c5db199SXin Li
3382*9c5db199SXin Li        # Call unregister() so that the same path could be reused.
3383*9c5db199SXin Li        adv.unregister()
3384*9c5db199SXin Li        del self.advertisements[index]
3385*9c5db199SXin Li
3386*9c5db199SXin Li        return result
3387*9c5db199SXin Li
3388*9c5db199SXin Li    def set_advertising_intervals(self, min_adv_interval_ms,
3389*9c5db199SXin Li                                  max_adv_interval_ms):
3390*9c5db199SXin Li        """Set advertising intervals.
3391*9c5db199SXin Li
3392*9c5db199SXin Li        @param min_adv_interval_ms: the min advertising interval in ms.
3393*9c5db199SXin Li        @param max_adv_interval_ms: the max advertising interval in ms.
3394*9c5db199SXin Li
3395*9c5db199SXin Li        @returns: True on success. False otherwise.
3396*9c5db199SXin Li
3397*9c5db199SXin Li        """
3398*9c5db199SXin Li        return self.dbus_method_with_handlers(
3399*9c5db199SXin Li                self._advertising.SetAdvertisingIntervals,
3400*9c5db199SXin Li                # reply handler
3401*9c5db199SXin Li                lambda: logging.info('set_advertising_intervals: succeeded.'),
3402*9c5db199SXin Li                # error handler
3403*9c5db199SXin Li                lambda error: logging.error(
3404*9c5db199SXin Li                        'set_advertising_intervals: failed: %s', str(error)),
3405*9c5db199SXin Li                # other arguments
3406*9c5db199SXin Li                min_adv_interval_ms,
3407*9c5db199SXin Li                max_adv_interval_ms)
3408*9c5db199SXin Li
3409*9c5db199SXin Li    def get_advertisement_property(self, adv_path, prop_name):
3410*9c5db199SXin Li        """Grab property of an advertisement registered on the DUT
3411*9c5db199SXin Li
3412*9c5db199SXin Li        The service on the DUT registers a dbus object and holds it. During the
3413*9c5db199SXin Li        test, some properties on the object may change, so this allows the test
3414*9c5db199SXin Li        access to the properties at run-time.
3415*9c5db199SXin Li
3416*9c5db199SXin Li        @param adv_path: string path of the dbus object
3417*9c5db199SXin Li        @param prop_name: string name of the property required
3418*9c5db199SXin Li
3419*9c5db199SXin Li        @returns: the value of the property in standard (non-dbus) type if the
3420*9c5db199SXin Li                    property exists, else None
3421*9c5db199SXin Li        """
3422*9c5db199SXin Li        for adv in self.advertisements:
3423*9c5db199SXin Li            if str(adv.get_path()) == adv_path:
3424*9c5db199SXin Li                adv_props = adv.GetAll('org.bluez.LEAdvertisement1')
3425*9c5db199SXin Li                return unpack_if_variant(adv_props.get(prop_name, None))
3426*9c5db199SXin Li
3427*9c5db199SXin Li        return None
3428*9c5db199SXin Li
3429*9c5db199SXin Li    def get_advertising_manager_property(self, prop_name):
3430*9c5db199SXin Li        """Grab property of the bluez advertising manager
3431*9c5db199SXin Li
3432*9c5db199SXin Li        This allows us to understand the DUT's advertising capabilities, for
3433*9c5db199SXin Li        instance the maximum number of advertising instances supported, so that
3434*9c5db199SXin Li        we can test these capabilities.
3435*9c5db199SXin Li
3436*9c5db199SXin Li        @param adv_path: string path of the dbus object
3437*9c5db199SXin Li        @param prop_name: string name of the property required
3438*9c5db199SXin Li
3439*9c5db199SXin Li        @returns: the value of the property in standard (non-dbus) type if the
3440*9c5db199SXin Li                    property exists, else None
3441*9c5db199SXin Li        """
3442*9c5db199SXin Li
3443*9c5db199SXin Li        return unpack_if_variant(
3444*9c5db199SXin Li                self._property_proxy.Get(
3445*9c5db199SXin Li                        self.BLUEZ_LE_ADVERTISING_MANAGER_IFACE, prop_name))
3446*9c5db199SXin Li
3447*9c5db199SXin Li    def reset_advertising(self):
3448*9c5db199SXin Li        """Reset advertising.
3449*9c5db199SXin Li
3450*9c5db199SXin Li        This includes un-registering all advertisements, reset advertising
3451*9c5db199SXin Li        intervals, and disable advertising.
3452*9c5db199SXin Li
3453*9c5db199SXin Li        @returns: True on success. False otherwise.
3454*9c5db199SXin Li
3455*9c5db199SXin Li        """
3456*9c5db199SXin Li        # It is required to execute unregister() to unregister the
3457*9c5db199SXin Li        # object-path handler of each advertisement. In this way, we could
3458*9c5db199SXin Li        # register an advertisement with the same path repeatedly.
3459*9c5db199SXin Li        for adv in self.advertisements:
3460*9c5db199SXin Li            adv.unregister()
3461*9c5db199SXin Li        del self.advertisements[:]
3462*9c5db199SXin Li
3463*9c5db199SXin Li        return self.dbus_method_with_handlers(
3464*9c5db199SXin Li                self._advertising.ResetAdvertising,
3465*9c5db199SXin Li                # reply handler
3466*9c5db199SXin Li                lambda: logging.info('reset_advertising: succeeded.'),
3467*9c5db199SXin Li                # error handler
3468*9c5db199SXin Li                lambda error: logging.error('reset_advertising: failed: %s',
3469*9c5db199SXin Li                                            str(error)))
3470*9c5db199SXin Li
3471*9c5db199SXin Li    def get_gatt_attributes_map(self, address):
3472*9c5db199SXin Li        """Return a JSON formatted string of the GATT attributes of a device,
3473*9c5db199SXin Li        keyed by UUID
3474*9c5db199SXin Li        @param address: a string of the MAC address of the device
3475*9c5db199SXin Li
3476*9c5db199SXin Li        @return: JSON formated string, stored the nested structure of the
3477*9c5db199SXin Li        attributes. Each attribute has 'path' and
3478*9c5db199SXin Li        ['characteristics' | 'descriptors'], which store their object path and
3479*9c5db199SXin Li        children respectively.
3480*9c5db199SXin Li
3481*9c5db199SXin Li        """
3482*9c5db199SXin Li        attribute_map = dict()
3483*9c5db199SXin Li
3484*9c5db199SXin Li        device_object_path = self._get_device_path(address)
3485*9c5db199SXin Li        objects = self._objmgr_proxy.GetManagedObjects()
3486*9c5db199SXin Li        service_map = self._get_service_map(device_object_path, objects)
3487*9c5db199SXin Li
3488*9c5db199SXin Li        servs = dict()
3489*9c5db199SXin Li        attribute_map['services'] = servs
3490*9c5db199SXin Li
3491*9c5db199SXin Li        for uuid, path in service_map.items():
3492*9c5db199SXin Li
3493*9c5db199SXin Li            servs[uuid] = dict()
3494*9c5db199SXin Li            serv = servs[uuid]
3495*9c5db199SXin Li
3496*9c5db199SXin Li            serv['path'] = path
3497*9c5db199SXin Li            serv['characteristics'] = dict()
3498*9c5db199SXin Li            chrcs = serv['characteristics']
3499*9c5db199SXin Li
3500*9c5db199SXin Li            chrcs_map = self._get_characteristic_map(path, objects)
3501*9c5db199SXin Li            for uuid, path in chrcs_map.items():
3502*9c5db199SXin Li                chrcs[uuid] = dict()
3503*9c5db199SXin Li                chrc = chrcs[uuid]
3504*9c5db199SXin Li
3505*9c5db199SXin Li                chrc['path'] = path
3506*9c5db199SXin Li                chrc['descriptors'] = dict()
3507*9c5db199SXin Li                descs = chrc['descriptors']
3508*9c5db199SXin Li
3509*9c5db199SXin Li                descs_map = self._get_descriptor_map(path, objects)
3510*9c5db199SXin Li
3511*9c5db199SXin Li                for uuid, path in descs_map.items():
3512*9c5db199SXin Li                    descs[uuid] = dict()
3513*9c5db199SXin Li                    desc = descs[uuid]
3514*9c5db199SXin Li
3515*9c5db199SXin Li                    desc['path'] = path
3516*9c5db199SXin Li
3517*9c5db199SXin Li        return json.dumps(attribute_map)
3518*9c5db199SXin Li
3519*9c5db199SXin Li    def _get_gatt_interface(self, uuid, object_path, interface):
3520*9c5db199SXin Li        """Get dbus interface by uuid
3521*9c5db199SXin Li        @param uuid: a string of uuid
3522*9c5db199SXin Li        @param object_path: a string of the object path of the service
3523*9c5db199SXin Li
3524*9c5db199SXin Li        @return: a dbus interface
3525*9c5db199SXin Li        """
3526*9c5db199SXin Li
3527*9c5db199SXin Li        return self.bus.get(self.BLUEZ_SERVICE_NAME, object_path)[interface]
3528*9c5db199SXin Li
3529*9c5db199SXin Li    def get_gatt_service_property(self, object_path, property_name):
3530*9c5db199SXin Li        """Get property from a service attribute
3531*9c5db199SXin Li        @param object_path: a string of the object path of the service
3532*9c5db199SXin Li        @param property_name: a string of a property, ex: 'Value', 'UUID'
3533*9c5db199SXin Li
3534*9c5db199SXin Li        @return: the property if success,
3535*9c5db199SXin Li                 none otherwise
3536*9c5db199SXin Li
3537*9c5db199SXin Li        """
3538*9c5db199SXin Li        return self.get_gatt_attribute_property(object_path,
3539*9c5db199SXin Li                                                self.BLUEZ_GATT_SERV_IFACE,
3540*9c5db199SXin Li                                                property_name)
3541*9c5db199SXin Li
3542*9c5db199SXin Li    def get_gatt_characteristic_property(self, object_path, property_name):
3543*9c5db199SXin Li        """Get property from a characteristic attribute
3544*9c5db199SXin Li        @param object_path: a string of the object path of the characteristic
3545*9c5db199SXin Li        @param property_name: a string of a property, ex: 'Value', 'UUID'
3546*9c5db199SXin Li
3547*9c5db199SXin Li        @return: the property if success,
3548*9c5db199SXin Li                 none otherwise
3549*9c5db199SXin Li
3550*9c5db199SXin Li        """
3551*9c5db199SXin Li        return self.get_gatt_attribute_property(object_path,
3552*9c5db199SXin Li                                                self.BLUEZ_GATT_CHAR_IFACE,
3553*9c5db199SXin Li                                                property_name)
3554*9c5db199SXin Li
3555*9c5db199SXin Li    def get_gatt_descriptor_property(self, object_path, property_name):
3556*9c5db199SXin Li        """Get property from descriptor attribute
3557*9c5db199SXin Li        @param object_path: a string of the object path of the descriptor
3558*9c5db199SXin Li        @param property_name: a string of a property, ex: 'Value', 'UUID'
3559*9c5db199SXin Li
3560*9c5db199SXin Li        @return: the property if success,
3561*9c5db199SXin Li                 none otherwise
3562*9c5db199SXin Li
3563*9c5db199SXin Li        """
3564*9c5db199SXin Li        return self.get_gatt_attribute_property(object_path,
3565*9c5db199SXin Li                                                self.BLUEZ_GATT_DESC_IFACE,
3566*9c5db199SXin Li                                                property_name)
3567*9c5db199SXin Li
3568*9c5db199SXin Li    @dbus_safe(None)
3569*9c5db199SXin Li    def get_gatt_attribute_property(self, object_path, interface,
3570*9c5db199SXin Li                                    property_name):
3571*9c5db199SXin Li        """Get property from attribute
3572*9c5db199SXin Li        @param object_path: a string of the bject path
3573*9c5db199SXin Li        @param property_name: a string of a property, ex: 'Value', 'UUID'
3574*9c5db199SXin Li
3575*9c5db199SXin Li        @return: the property if success,
3576*9c5db199SXin Li                 none otherwise
3577*9c5db199SXin Li
3578*9c5db199SXin Li        """
3579*9c5db199SXin Li        gatt_object = self.bus.get(self.BLUEZ_SERVICE_NAME, object_path)
3580*9c5db199SXin Li        prop = self._get_dbus_object_property(gatt_object, interface,
3581*9c5db199SXin Li                                              property_name)
3582*9c5db199SXin Li        logging.info(prop)
3583*9c5db199SXin Li        if isinstance(prop, bytearray):
3584*9c5db199SXin Li            return _dbus_byte_array_to_b64_string(prop)
3585*9c5db199SXin Li        if isinstance(prop, bool):
3586*9c5db199SXin Li            return bool(prop)
3587*9c5db199SXin Li        if isinstance(prop, list):
3588*9c5db199SXin Li            return list(map(str, prop))
3589*9c5db199SXin Li        return prop
3590*9c5db199SXin Li
3591*9c5db199SXin Li    @dbus_safe(None)
3592*9c5db199SXin Li    def gatt_characteristic_read_value(self, uuid, object_path):
3593*9c5db199SXin Li        """Perform method ReadValue on a characteristic attribute
3594*9c5db199SXin Li        @param uuid: a string of uuid
3595*9c5db199SXin Li        @param object_path: a string of the object path of the characteristic
3596*9c5db199SXin Li
3597*9c5db199SXin Li        @return: base64 string of dbus bytearray
3598*9c5db199SXin Li        """
3599*9c5db199SXin Li
3600*9c5db199SXin Li        dbus_interface = self._get_gatt_interface(uuid, object_path,
3601*9c5db199SXin Li                                                  self.BLUEZ_GATT_CHAR_IFACE)
3602*9c5db199SXin Li        value = dbus_interface.ReadValue({})
3603*9c5db199SXin Li        return _dbus_byte_array_to_b64_string(value)
3604*9c5db199SXin Li
3605*9c5db199SXin Li    @dbus_safe(None)
3606*9c5db199SXin Li    def gatt_descriptor_read_value(self, uuid, object_path):
3607*9c5db199SXin Li        """Perform method ReadValue on a descriptor attribute
3608*9c5db199SXin Li        @param uuid: a string of uuid
3609*9c5db199SXin Li        @param object_path: a string of the object path of the descriptor
3610*9c5db199SXin Li
3611*9c5db199SXin Li        @return: base64 string of dbus bytearray
3612*9c5db199SXin Li        """
3613*9c5db199SXin Li
3614*9c5db199SXin Li        dbus_interface = self._get_gatt_interface(uuid, object_path,
3615*9c5db199SXin Li                                                  self.BLUEZ_GATT_DESC_IFACE)
3616*9c5db199SXin Li        value = dbus_interface.ReadValue({})
3617*9c5db199SXin Li        return _dbus_byte_array_to_b64_string(value)
3618*9c5db199SXin Li
3619*9c5db199SXin Li    @dbus_safe(False)
3620*9c5db199SXin Li    def _get_attribute_map(self, object_path, dbus_interface, objects):
3621*9c5db199SXin Li        """Gets a map of object paths under an object path.
3622*9c5db199SXin Li
3623*9c5db199SXin Li        Walks the object tree, and returns a map of UUIDs to object paths for
3624*9c5db199SXin Li        all resolved gatt object.
3625*9c5db199SXin Li
3626*9c5db199SXin Li        @param object_path: The object path of the attribute to retrieve
3627*9c5db199SXin Li            gatt  UUIDs and paths from.
3628*9c5db199SXin Li        @param objects: The managed objects.
3629*9c5db199SXin Li
3630*9c5db199SXin Li        @returns: A dictionary of object paths, keyed by UUID.
3631*9c5db199SXin Li
3632*9c5db199SXin Li        """
3633*9c5db199SXin Li        attr_map = {}
3634*9c5db199SXin Li
3635*9c5db199SXin Li        if object_path:
3636*9c5db199SXin Li            for path, ifaces in six.iteritems(objects):
3637*9c5db199SXin Li                if (dbus_interface in ifaces and path.startswith(object_path)):
3638*9c5db199SXin Li                    uuid = ifaces[dbus_interface]['UUID'].lower()
3639*9c5db199SXin Li                    attr_map[uuid] = path
3640*9c5db199SXin Li
3641*9c5db199SXin Li        else:
3642*9c5db199SXin Li            logging.warning('object_path %s is not valid', object_path)
3643*9c5db199SXin Li
3644*9c5db199SXin Li        return attr_map
3645*9c5db199SXin Li
3646*9c5db199SXin Li    def _get_service_map(self, device_path, objects):
3647*9c5db199SXin Li        """Gets a map of service paths for a device.
3648*9c5db199SXin Li
3649*9c5db199SXin Li        @param device_path: the object path of the device.
3650*9c5db199SXin Li        @param objects: The managed objects.
3651*9c5db199SXin Li        """
3652*9c5db199SXin Li        return self._get_attribute_map(device_path, self.BLUEZ_GATT_SERV_IFACE,
3653*9c5db199SXin Li                                       objects)
3654*9c5db199SXin Li
3655*9c5db199SXin Li    def _get_characteristic_map(self, serv_path, objects):
3656*9c5db199SXin Li        """Gets a map of characteristic paths for a service.
3657*9c5db199SXin Li
3658*9c5db199SXin Li        @param serv_path: the object path of the service.
3659*9c5db199SXin Li        @param objects: The managed objects.
3660*9c5db199SXin Li        """
3661*9c5db199SXin Li        return self._get_attribute_map(serv_path, self.BLUEZ_GATT_CHAR_IFACE,
3662*9c5db199SXin Li                                       objects)
3663*9c5db199SXin Li
3664*9c5db199SXin Li    def _get_descriptor_map(self, chrc_path, objects):
3665*9c5db199SXin Li        """Gets a map of descriptor paths for a characteristic.
3666*9c5db199SXin Li
3667*9c5db199SXin Li        @param chrc_path: the object path of the characteristic.
3668*9c5db199SXin Li        @param objects: The managed objects.
3669*9c5db199SXin Li        """
3670*9c5db199SXin Li        return self._get_attribute_map(chrc_path, self.BLUEZ_GATT_DESC_IFACE,
3671*9c5db199SXin Li                                       objects)
3672*9c5db199SXin Li
3673*9c5db199SXin Li    @dbus_safe(None)
3674*9c5db199SXin Li    def _get_dbus_object_property(self, dbus_object, dbus_interface,
3675*9c5db199SXin Li                                  dbus_property):
3676*9c5db199SXin Li        """Get the property in an object.
3677*9c5db199SXin Li
3678*9c5db199SXin Li        @param dbus_object: a dbus object
3679*9c5db199SXin Li        @param dbus_interface: a dbus interface where the property exists
3680*9c5db199SXin Li        @param dbus_property: a dbus property of the dbus object, as a string
3681*9c5db199SXin Li
3682*9c5db199SXin Li        @return: dbus type object if it success, e.g. dbus.Boolean, dbus.String,
3683*9c5db199SXin Li                 none otherwise
3684*9c5db199SXin Li
3685*9c5db199SXin Li        """
3686*9c5db199SXin Li        return dbus_object[self.DBUS_PROP_IFACE].Get(dbus_interface,
3687*9c5db199SXin Li                                                     dbus_property)
3688*9c5db199SXin Li
3689*9c5db199SXin Li    @dbus_safe(False)
3690*9c5db199SXin Li    def get_characteristic_map(self, address):
3691*9c5db199SXin Li        """Gets a map of characteristic paths for a device.
3692*9c5db199SXin Li
3693*9c5db199SXin Li        Walks the object tree, and returns a map of uuids to object paths for
3694*9c5db199SXin Li        all resolved gatt characteristics.
3695*9c5db199SXin Li
3696*9c5db199SXin Li        @param address: The MAC address of the device to retrieve
3697*9c5db199SXin Li            gatt characteristic uuids and paths from.
3698*9c5db199SXin Li
3699*9c5db199SXin Li        @returns: A dictionary of characteristic paths, keyed by uuid.
3700*9c5db199SXin Li
3701*9c5db199SXin Li        """
3702*9c5db199SXin Li        device_path = self._get_device_path(address)
3703*9c5db199SXin Li        char_map = {}
3704*9c5db199SXin Li
3705*9c5db199SXin Li        if device_path:
3706*9c5db199SXin Li            objects = self._objmgr_proxy.GetManagedObjects()
3707*9c5db199SXin Li
3708*9c5db199SXin Li            for path, ifaces in six.iteritems(objects):
3709*9c5db199SXin Li                if (self.BLUEZ_GATT_CHAR_IFACE in ifaces
3710*9c5db199SXin Li                            and path.startswith(device_path)):
3711*9c5db199SXin Li                    uuid = ifaces[self.BLUEZ_GATT_CHAR_IFACE]['UUID'].lower()
3712*9c5db199SXin Li                    char_map[uuid] = path
3713*9c5db199SXin Li        else:
3714*9c5db199SXin Li            logging.warning('Device %s not in object tree.', address)
3715*9c5db199SXin Li
3716*9c5db199SXin Li        return char_map
3717*9c5db199SXin Li
3718*9c5db199SXin Li    @dbus_safe(None)
3719*9c5db199SXin Li    def _get_char_object(self, uuid, address):
3720*9c5db199SXin Li        """Gets a characteristic object.
3721*9c5db199SXin Li
3722*9c5db199SXin Li        Gets a characteristic object for a given UUID and address.
3723*9c5db199SXin Li
3724*9c5db199SXin Li        @param uuid: The UUID of the characteristic, as a string.
3725*9c5db199SXin Li        @param address: The MAC address of the remote device.
3726*9c5db199SXin Li
3727*9c5db199SXin Li        @returns: A dbus interface for the characteristic if the uuid/address
3728*9c5db199SXin Li                      is in the object tree.
3729*9c5db199SXin Li                  None if the address/uuid is not found in the object tree.
3730*9c5db199SXin Li
3731*9c5db199SXin Li        """
3732*9c5db199SXin Li        path = self.get_characteristic_map(address).get(uuid)
3733*9c5db199SXin Li        if not path:
3734*9c5db199SXin Li            logging.error("path not found: %s %s", uuid, address)
3735*9c5db199SXin Li            return None
3736*9c5db199SXin Li        return self.bus.get(self.BLUEZ_SERVICE_NAME,
3737*9c5db199SXin Li                            path)[self.BLUEZ_GATT_CHAR_IFACE]
3738*9c5db199SXin Li
3739*9c5db199SXin Li    @dbus_safe(None)
3740*9c5db199SXin Li    def read_characteristic(self, uuid, address):
3741*9c5db199SXin Li        """Reads the value of a gatt characteristic.
3742*9c5db199SXin Li
3743*9c5db199SXin Li        Reads the current value of a gatt characteristic. Base64 endcoding is
3744*9c5db199SXin Li        used for compatibility with the XML RPC interface.
3745*9c5db199SXin Li
3746*9c5db199SXin Li        @param uuid: The uuid of the characteristic to read, as a string.
3747*9c5db199SXin Li        @param address: The MAC address of the remote device.
3748*9c5db199SXin Li
3749*9c5db199SXin Li        @returns: A b64 encoded version of a byte array containing the value
3750*9c5db199SXin Li                      if the uuid/address is in the object tree.
3751*9c5db199SXin Li                  None if the uuid/address was not found in the object tree, or
3752*9c5db199SXin Li                      if a DBus exception was raised by the read operation.
3753*9c5db199SXin Li
3754*9c5db199SXin Li        """
3755*9c5db199SXin Li        char_obj = self._get_char_object(uuid, address)
3756*9c5db199SXin Li        if char_obj is None:
3757*9c5db199SXin Li            return None
3758*9c5db199SXin Li        value = char_obj.ReadValue({})
3759*9c5db199SXin Li        return _dbus_byte_array_to_b64_string(value)
3760*9c5db199SXin Li
3761*9c5db199SXin Li    @dbus_safe(None)
3762*9c5db199SXin Li    def write_characteristic(self, uuid, address, value):
3763*9c5db199SXin Li        """Performs a write operation on a gatt characteristic.
3764*9c5db199SXin Li
3765*9c5db199SXin Li        Writes to a GATT characteristic on a remote device. Base64 endcoding is
3766*9c5db199SXin Li        used for compatibility with the XML RPC interface.
3767*9c5db199SXin Li
3768*9c5db199SXin Li        @param uuid: The uuid of the characteristic to write to, as a string.
3769*9c5db199SXin Li        @param address: The MAC address of the remote device, as a string.
3770*9c5db199SXin Li        @param value: A byte array containing the data to write.
3771*9c5db199SXin Li
3772*9c5db199SXin Li        @returns: True if the write operation does not raise an exception.
3773*9c5db199SXin Li                  None if the uuid/address was not found in the object tree, or
3774*9c5db199SXin Li                      if a DBus exception was raised by the write operation.
3775*9c5db199SXin Li
3776*9c5db199SXin Li        """
3777*9c5db199SXin Li        char_obj = self._get_char_object(uuid, address)
3778*9c5db199SXin Li        if char_obj is None:
3779*9c5db199SXin Li            return None
3780*9c5db199SXin Li        dbus_value = _b64_string_to_dbus_byte_array(value)
3781*9c5db199SXin Li        char_obj.WriteValue(dbus_value, {})
3782*9c5db199SXin Li        return True
3783*9c5db199SXin Li
3784*9c5db199SXin Li    @dbus_safe(None)
3785*9c5db199SXin Li    def exchange_messages(self, tx_object_path, rx_object_path, value):
3786*9c5db199SXin Li        """Performs a write operation on a gatt characteristic and wait for
3787*9c5db199SXin Li        the response on another characteristic.
3788*9c5db199SXin Li
3789*9c5db199SXin Li        @param tx_object_path: the object path of the characteristic to write.
3790*9c5db199SXin Li        @param rx_object_path: the object path of the characteristic to read.
3791*9c5db199SXin Li        @param value: A byte array containing the data to write.
3792*9c5db199SXin Li
3793*9c5db199SXin Li        @returns: The value of the characteristic to read from.
3794*9c5db199SXin Li                  None if the uuid/address was not found in the object tree, or
3795*9c5db199SXin Li                      if a DBus exception was raised by the write operation.
3796*9c5db199SXin Li
3797*9c5db199SXin Li        """
3798*9c5db199SXin Li        tx_obj = self._get_gatt_characteristic_object(tx_object_path)
3799*9c5db199SXin Li
3800*9c5db199SXin Li        if tx_obj is None:
3801*9c5db199SXin Li            return None
3802*9c5db199SXin Li
3803*9c5db199SXin Li        self._chrc_property = ''.encode('utf-8')
3804*9c5db199SXin Li
3805*9c5db199SXin Li        value = str(value)
3806*9c5db199SXin Li        proxy = self.bus.get(self.BLUEZ_SERVICE_NAME, rx_object_path)[self.DBUS_PROP_IFACE]
3807*9c5db199SXin Li        self._signal_watch = proxy.PropertiesChanged.connect(self._property_changed)
3808*9c5db199SXin Li
3809*9c5db199SXin Li        # Start timeout source
3810*9c5db199SXin Li        self._timeout_start = time.time()
3811*9c5db199SXin Li        self._timeout_early = False
3812*9c5db199SXin Li        self._timeout_id = GObject.timeout_add(
3813*9c5db199SXin Li                self.PROPERTY_UPDATE_CHECK_MILLI_SECS,
3814*9c5db199SXin Li                self._property_wait_timeout)
3815*9c5db199SXin Li
3816*9c5db199SXin Li        write_value = _b64_string_to_dbus_byte_array(value)
3817*9c5db199SXin Li        tx_obj.WriteValue(write_value, {})
3818*9c5db199SXin Li
3819*9c5db199SXin Li        self._dbus_mainloop.run()
3820*9c5db199SXin Li
3821*9c5db199SXin Li        return _dbus_byte_array_to_b64_string(self._chrc_property)
3822*9c5db199SXin Li
3823*9c5db199SXin Li    def _property_changed(self, *args, **kwargs):
3824*9c5db199SXin Li        """Handler for properties changed signal."""
3825*9c5db199SXin Li        # We don't cancel the timeout here due to a problem with the GLib
3826*9c5db199SXin Li        # mainloop. See |_property_wait_timeout| for a full explanation.
3827*9c5db199SXin Li        self._timeout_early = True
3828*9c5db199SXin Li        self._signal_watch.disconnect()
3829*9c5db199SXin Li        changed_prop = args
3830*9c5db199SXin Li
3831*9c5db199SXin Li        logging.info(changed_prop)
3832*9c5db199SXin Li        prop_dict = changed_prop[1]
3833*9c5db199SXin Li        self._chrc_property = prop_dict['Value']
3834*9c5db199SXin Li        if self._dbus_mainloop.is_running():
3835*9c5db199SXin Li            self._dbus_mainloop.quit()
3836*9c5db199SXin Li
3837*9c5db199SXin Li    def _property_wait_timeout(self):
3838*9c5db199SXin Li        """Timeout handler when waiting for properties update signal."""
3839*9c5db199SXin Li        # Sometimes, GLib.Mainloop doesn't exit after |mainloop.quit()| is
3840*9c5db199SXin Li        # called. This seems to occur only if a timeout source was active and
3841*9c5db199SXin Li        # was removed before it had a chance to run. To mitigate this, we don't
3842*9c5db199SXin Li        # cancel the timeout but mark an early completion instead.
3843*9c5db199SXin Li        # See b/222364364#comment3 for more information.
3844*9c5db199SXin Li        if not self._timeout_early and int(
3845*9c5db199SXin Li                (time.time() - self._timeout_start) *
3846*9c5db199SXin Li                1000) <= self.PROPERTY_UPDATE_TIMEOUT_MILLI_SECS:
3847*9c5db199SXin Li            # Returning True means this will be called again.
3848*9c5db199SXin Li            return True
3849*9c5db199SXin Li
3850*9c5db199SXin Li        self._signal_watch.disconnect()
3851*9c5db199SXin Li        if self._dbus_mainloop.is_running():
3852*9c5db199SXin Li            logging.warning("quit main loop due to timeout")
3853*9c5db199SXin Li            self._dbus_mainloop.quit()
3854*9c5db199SXin Li        # Return false so that this method will not be called again.
3855*9c5db199SXin Li        return False
3856*9c5db199SXin Li
3857*9c5db199SXin Li    @dbus_safe(False)
3858*9c5db199SXin Li    def _get_gatt_characteristic_object(self, object_path):
3859*9c5db199SXin Li        return self.bus.get(self.BLUEZ_SERVICE_NAME,
3860*9c5db199SXin Li                            object_path)[self.BLUEZ_GATT_CHAR_IFACE]
3861*9c5db199SXin Li
3862*9c5db199SXin Li    @dbus_safe(False)
3863*9c5db199SXin Li    def start_notify(self, object_path, cccd_value):
3864*9c5db199SXin Li        """Starts the notification session on the gatt characteristic.
3865*9c5db199SXin Li
3866*9c5db199SXin Li        @param object_path: the object path of the characteristic.
3867*9c5db199SXin Li        @param cccd_value: Possible CCCD values include
3868*9c5db199SXin Li               0x00 - inferred from the remote characteristic's properties
3869*9c5db199SXin Li               0x01 - notification
3870*9c5db199SXin Li               0x02 - indication
3871*9c5db199SXin Li
3872*9c5db199SXin Li        @returns: True if the operation succeeds.
3873*9c5db199SXin Li                  False if the characteristic is not found, or
3874*9c5db199SXin Li                      if a DBus exception was raised by the operation.
3875*9c5db199SXin Li
3876*9c5db199SXin Li        """
3877*9c5db199SXin Li        char_obj = self._get_gatt_characteristic_object(object_path)
3878*9c5db199SXin Li        if char_obj is None:
3879*9c5db199SXin Li            logging.error("characteristic not found: %s %s", object_path)
3880*9c5db199SXin Li            return False
3881*9c5db199SXin Li
3882*9c5db199SXin Li        try:
3883*9c5db199SXin Li            char_obj.StartNotify(cccd_value)
3884*9c5db199SXin Li            return True
3885*9c5db199SXin Li        except Exception as e:
3886*9c5db199SXin Li            logging.error('start_notify: %s', e)
3887*9c5db199SXin Li        except:
3888*9c5db199SXin Li            logging.error('start_notify: unexpected error')
3889*9c5db199SXin Li        return False
3890*9c5db199SXin Li
3891*9c5db199SXin Li    @dbus_safe(False)
3892*9c5db199SXin Li    def stop_notify(self, object_path):
3893*9c5db199SXin Li        """Stops the notification session on the gatt characteristic.
3894*9c5db199SXin Li
3895*9c5db199SXin Li        @param object_path: the object path of the characteristic.
3896*9c5db199SXin Li
3897*9c5db199SXin Li        @returns: True if the operation succeeds.
3898*9c5db199SXin Li                  False if the characteristic is not found, or
3899*9c5db199SXin Li                      if a DBus exception was raised by the operation.
3900*9c5db199SXin Li
3901*9c5db199SXin Li        """
3902*9c5db199SXin Li        char_obj = self._get_gatt_characteristic_object(object_path)
3903*9c5db199SXin Li        if char_obj is None:
3904*9c5db199SXin Li            logging.error("characteristic not found: %s %s", object_path)
3905*9c5db199SXin Li            return False
3906*9c5db199SXin Li
3907*9c5db199SXin Li        try:
3908*9c5db199SXin Li            char_obj.StopNotify()
3909*9c5db199SXin Li            return True
3910*9c5db199SXin Li        except Exception as e:
3911*9c5db199SXin Li            logging.error('stop_notify: %s', e)
3912*9c5db199SXin Li        except:
3913*9c5db199SXin Li            logging.error('stop_notify: unexpected error')
3914*9c5db199SXin Li        return False
3915*9c5db199SXin Li
3916*9c5db199SXin Li    @dbus_safe(False)
3917*9c5db199SXin Li    def is_notifying(self, object_path):
3918*9c5db199SXin Li        """Is the GATT characteristic in a notifying session?
3919*9c5db199SXin Li
3920*9c5db199SXin Li        @param object_path: the object path of the characteristic.
3921*9c5db199SXin Li
3922*9c5db199SXin Li        @return True if it is in a notification session. False otherwise.
3923*9c5db199SXin Li
3924*9c5db199SXin Li        """
3925*9c5db199SXin Li
3926*9c5db199SXin Li        return self.get_gatt_characteristic_property(object_path, 'Notifying')
3927*9c5db199SXin Li
3928*9c5db199SXin Li    @dbus_safe(False)
3929*9c5db199SXin Li    def is_characteristic_path_resolved(self, uuid, address):
3930*9c5db199SXin Li        """Checks whether a characteristic is in the object tree.
3931*9c5db199SXin Li
3932*9c5db199SXin Li        Checks whether a characteristic is curently found in the object tree.
3933*9c5db199SXin Li
3934*9c5db199SXin Li        @param uuid: The uuid of the characteristic to search for.
3935*9c5db199SXin Li        @param address: The MAC address of the device on which to search for
3936*9c5db199SXin Li            the characteristic.
3937*9c5db199SXin Li
3938*9c5db199SXin Li        @returns: True if the characteristic is found.
3939*9c5db199SXin Li                  False if the characteristic path is not found.
3940*9c5db199SXin Li
3941*9c5db199SXin Li        """
3942*9c5db199SXin Li        return bool(self.get_characteristic_map(address).get(uuid))
3943*9c5db199SXin Li
3944*9c5db199SXin Li    @dbus_safe(False)
3945*9c5db199SXin Li    def get_connection_info(self, address):
3946*9c5db199SXin Li        """Get device connection info.
3947*9c5db199SXin Li
3948*9c5db199SXin Li        @param address: The MAC address of the device.
3949*9c5db199SXin Li
3950*9c5db199SXin Li        @returns: On success, a JSON-encoded tuple of:
3951*9c5db199SXin Li                      ( RSSI, transmit_power, max_transmit_power )
3952*9c5db199SXin Li                  None otherwise.
3953*9c5db199SXin Li
3954*9c5db199SXin Li        """
3955*9c5db199SXin Li        plugin_device = self._get_plugin_device_interface(address)
3956*9c5db199SXin Li        if plugin_device is None:
3957*9c5db199SXin Li            return None
3958*9c5db199SXin Li
3959*9c5db199SXin Li        try:
3960*9c5db199SXin Li            connection_info = plugin_device.GetConnInfo()
3961*9c5db199SXin Li            return json.dumps(connection_info)
3962*9c5db199SXin Li        except Exception as e:
3963*9c5db199SXin Li            logging.error('get_connection_info: %s', e)
3964*9c5db199SXin Li        except:
3965*9c5db199SXin Li            logging.error('get_connection_info: unexpected error')
3966*9c5db199SXin Li        return None
3967*9c5db199SXin Li
3968*9c5db199SXin Li    def has_connection_info(self, address):
3969*9c5db199SXin Li        """Checks whether the address has connection info.
3970*9c5db199SXin Li
3971*9c5db199SXin Li        @param address: The MAC address of the device.
3972*9c5db199SXin Li        @returns True if connection info can be found.
3973*9c5db199SXin Li        """
3974*9c5db199SXin Li        return self.get_connection_info(address) is not None
3975*9c5db199SXin Li
3976*9c5db199SXin Li    @dbus_safe(False)
3977*9c5db199SXin Li    def set_le_connection_parameters(self, address, parameters):
3978*9c5db199SXin Li        """Set the LE connection parameters.
3979*9c5db199SXin Li
3980*9c5db199SXin Li        @param address: The MAC address of the device.
3981*9c5db199SXin Li        @param parameters: The LE connection parameters to set.
3982*9c5db199SXin Li
3983*9c5db199SXin Li        @return: True on success. False otherwise.
3984*9c5db199SXin Li
3985*9c5db199SXin Li        """
3986*9c5db199SXin Li        plugin_device = self._get_plugin_device_interface(address)
3987*9c5db199SXin Li        if plugin_device is None:
3988*9c5db199SXin Li            return False
3989*9c5db199SXin Li
3990*9c5db199SXin Li        return not self.dbus_method_with_handlers(
3991*9c5db199SXin Li                plugin_device.SetLEConnectionParameters,
3992*9c5db199SXin Li                # reply handler
3993*9c5db199SXin Li                lambda: logging.info('set_le_connection_parameters: succeeded.'
3994*9c5db199SXin Li                                     ),
3995*9c5db199SXin Li                # error handler
3996*9c5db199SXin Li                lambda error: logging.
3997*9c5db199SXin Li                error('set_le_connection_parameters: failed: %s', str(error)),
3998*9c5db199SXin Li                # other arguments
3999*9c5db199SXin Li                parameters)
4000*9c5db199SXin Li
4001*9c5db199SXin Li    @dbus_safe(False)
4002*9c5db199SXin Li    def _get_plugin_device_interface(self, address):
4003*9c5db199SXin Li        """Get the BlueZ Chromium device plugin interface.
4004*9c5db199SXin Li
4005*9c5db199SXin Li        This interface can be used to issue dbus requests such as
4006*9c5db199SXin Li        GetConnInfo and SetLEConnectionParameters.
4007*9c5db199SXin Li
4008*9c5db199SXin Li        @param address: The MAC address of the device.
4009*9c5db199SXin Li
4010*9c5db199SXin Li        @return: On success, the BlueZ Chromium device plugin interface
4011*9c5db199SXin Li                 None otherwise.
4012*9c5db199SXin Li
4013*9c5db199SXin Li        """
4014*9c5db199SXin Li        path = self._get_device_path(address)
4015*9c5db199SXin Li        if path is None:
4016*9c5db199SXin Li            return None
4017*9c5db199SXin Li
4018*9c5db199SXin Li        return self.bus.get(self.BLUEZ_SERVICE_NAME,
4019*9c5db199SXin Li                            path)[self.BLUEZ_PLUGIN_DEVICE_IFACE]
4020*9c5db199SXin Li
4021*9c5db199SXin Li    @dbus_safe(False)
4022*9c5db199SXin Li    def policy_get_service_allow_list(self):
4023*9c5db199SXin Li        """Get the service allow list for enterprise policy.
4024*9c5db199SXin Li
4025*9c5db199SXin Li        @returns: array of strings representing the allowed service UUIDs.
4026*9c5db199SXin Li        """
4027*9c5db199SXin Li        uuids = unpack_if_variant(
4028*9c5db199SXin Li                self._property_proxy.Get(self.BLUEZ_ADMIN_POLICY_STATUS_IFACE,
4029*9c5db199SXin Li                                         'ServiceAllowList'))
4030*9c5db199SXin Li        logging.debug('ServiceAllowList: %s', uuids)
4031*9c5db199SXin Li        return uuids
4032*9c5db199SXin Li
4033*9c5db199SXin Li    @dbus_safe(False, return_error=True)
4034*9c5db199SXin Li    def policy_set_service_allow_list(self, uuids):
4035*9c5db199SXin Li        """Set the service allow list for enterprise policy.
4036*9c5db199SXin Li
4037*9c5db199SXin Li        @param uuids: a string representing the uuids; e.g., "1234,0xabcd" or ""
4038*9c5db199SXin Li
4039*9c5db199SXin Li        @returns: (True, '') on success, (False, '<error>') on failure.
4040*9c5db199SXin Li        """
4041*9c5db199SXin Li        dbus_array = []
4042*9c5db199SXin Li        if bool(uuids.strip()):
4043*9c5db199SXin Li            for uuid in uuids.split(','):
4044*9c5db199SXin Li                dbus_array.append(uuid.strip())
4045*9c5db199SXin Li
4046*9c5db199SXin Li        logging.debug('policy_set_service_allow_list: %s', dbus_array)
4047*9c5db199SXin Li        self._adapter[self.BLUEZ_ADMIN_POLICY_SET_IFACE].SetServiceAllowList(
4048*9c5db199SXin Li                dbus_array)
4049*9c5db199SXin Li        return (True, '')
4050*9c5db199SXin Li
4051*9c5db199SXin Li    @dbus_safe(False, return_error=True)
4052*9c5db199SXin Li    def policy_get_device_affected(self, device_address):
4053*9c5db199SXin Li        """Check if the device is affected by enterprise policy.
4054*9c5db199SXin Li
4055*9c5db199SXin Li        @param device_address: address of the device
4056*9c5db199SXin Li                               e.g. '6C:29:95:1A:D4:6F'
4057*9c5db199SXin Li
4058*9c5db199SXin Li        @returns: True if the device is affected by the enterprise policy.
4059*9c5db199SXin Li                  False if not. None if the device is not found.
4060*9c5db199SXin Li        """
4061*9c5db199SXin Li        device = self._find_device(device_address)
4062*9c5db199SXin Li        if not device:
4063*9c5db199SXin Li            logging.debug('Failed to find device %s', device_address)
4064*9c5db199SXin Li            return None
4065*9c5db199SXin Li
4066*9c5db199SXin Li        affected = unpack_if_variant(device[self.DBUS_PROP_IFACE].Get(
4067*9c5db199SXin Li                self.BLUEZ_ADMIN_POLICY_STATUS_IFACE, 'AffectedByPolicy'))
4068*9c5db199SXin Li        logging.debug('policy_get_device_affected(%s): %s', device_address,
4069*9c5db199SXin Li                      affected)
4070*9c5db199SXin Li        return affected
4071*9c5db199SXin Li
4072*9c5db199SXin Li    def cleanup(self):
4073*9c5db199SXin Li        """Cleanup before exiting the client xmlrpc process."""
4074*9c5db199SXin Li
4075*9c5db199SXin Li        self.advmon_appmgr.destroy()
4076*9c5db199SXin Li
4077*9c5db199SXin Li    def get_sysconfig(self):
4078*9c5db199SXin Li        """Helper function to get default controller parameters
4079*9c5db199SXin Li
4080*9c5db199SXin Li        @returns: dict of type to values, both are in string form,
4081*9c5db199SXin Li                  None if the operation read-sysconfig failed.
4082*9c5db199SXin Li        """
4083*9c5db199SXin Li        tlv_re = re.compile('Type: (0x[0-9A-Fa-f]{4})\s+'
4084*9c5db199SXin Li                            'Length: ([0-9A-Fa-f]{2})\s+'
4085*9c5db199SXin Li                            'Value: ([0-9A-Fa-f]+)')
4086*9c5db199SXin Li
4087*9c5db199SXin Li        cmd = 'btmgmt read-sysconfig'
4088*9c5db199SXin Li        # btmgmt needs stdin, otherwise it won't output anything.
4089*9c5db199SXin Li        # Please refer to
4090*9c5db199SXin Li        # third_party/bluez/current/src/shared/shell.c:bt_shell_printf
4091*9c5db199SXin Li        # for more information
4092*9c5db199SXin Li        output = subprocess.check_output(cmd.split(),
4093*9c5db199SXin Li                                         stdin=subprocess.PIPE,
4094*9c5db199SXin Li                                         encoding='UTF-8')
4095*9c5db199SXin Li
4096*9c5db199SXin Li        if output is None:
4097*9c5db199SXin Li            logging.warning('Unable to retrieve output of %s', cmd)
4098*9c5db199SXin Li            return None
4099*9c5db199SXin Li
4100*9c5db199SXin Li        sysconfig = dict()
4101*9c5db199SXin Li
4102*9c5db199SXin Li        for line in output.splitlines():
4103*9c5db199SXin Li            try:
4104*9c5db199SXin Li                m = tlv_re.match(line)
4105*9c5db199SXin Li                t, l, v = m.groups()
4106*9c5db199SXin Li                sysconfig[int(t, 16)] = v
4107*9c5db199SXin Li            except Exception as e:
4108*9c5db199SXin Li                logging.warning('Unexpected error %s at "%s"', str(e), line)
4109*9c5db199SXin Li
4110*9c5db199SXin Li        logging.debug("default controller parameters: %s", sysconfig)
4111*9c5db199SXin Li        return sysconfig
4112*9c5db199SXin Li
4113*9c5db199SXin Li    def _le_hex_to_int(self, le_hex):
4114*9c5db199SXin Li        """Convert a little-endian hex-string to an unsigned integer.
4115*9c5db199SXin Li        For example, _le_hex_to_int('0x0102') returns the same value as
4116*9c5db199SXin Li        int('0201', 16)
4117*9c5db199SXin Li        """
4118*9c5db199SXin Li        if le_hex is None:
4119*9c5db199SXin Li            return None
4120*9c5db199SXin Li
4121*9c5db199SXin Li        ba = bytearray.fromhex(le_hex)
4122*9c5db199SXin Li        ba.reverse()
4123*9c5db199SXin Li        return int(binascii.hexlify(ba), 16)
4124*9c5db199SXin Li
4125*9c5db199SXin Li    def get_advmon_interleave_durations(self):
4126*9c5db199SXin Li        """Get durations of allowlist scan and no filter scan
4127*9c5db199SXin Li
4128*9c5db199SXin Li        @returns: a dict of {'allowlist': allowlist_duration,
4129*9c5db199SXin Li                             'no filter': no_filter_duration},
4130*9c5db199SXin Li                  or None if something went wrong
4131*9c5db199SXin Li        """
4132*9c5db199SXin Li
4133*9c5db199SXin Li        sysconfig = self.get_sysconfig()
4134*9c5db199SXin Li
4135*9c5db199SXin Li        if sysconfig is None:
4136*9c5db199SXin Li            return None
4137*9c5db199SXin Li
4138*9c5db199SXin Li        AllowlistScanDuration = self._le_hex_to_int(sysconfig.get(
4139*9c5db199SXin Li                0x001d, None))
4140*9c5db199SXin Li        NoFilterScanDuration = self._le_hex_to_int(sysconfig.get(0x001e, None))
4141*9c5db199SXin Li
4142*9c5db199SXin Li        return {
4143*9c5db199SXin Li                'allowlist': AllowlistScanDuration,
4144*9c5db199SXin Li                'no filter': NoFilterScanDuration
4145*9c5db199SXin Li        }
4146*9c5db199SXin Li
4147*9c5db199SXin Li
4148*9c5db199SXin Liclass FlossFacadeLocal(BluetoothBaseFacadeLocal):
4149*9c5db199SXin Li    """Exposes DUT methods called remotely during Bluetooth autotests for the
4150*9c5db199SXin Li    Floss daemon.
4151*9c5db199SXin Li
4152*9c5db199SXin Li    All instance methods of this object without a preceding '_' are exposed via
4153*9c5db199SXin Li    an XML-RPC server. This is not a stateless handler object, which means that
4154*9c5db199SXin Li    if you store state inside the delegate, that state will remain around for
4155*9c5db199SXin Li    future calls.
4156*9c5db199SXin Li    """
4157*9c5db199SXin Li
4158*9c5db199SXin Li    # Default to this adapter during init. We will initialize to the correct
4159*9c5db199SXin Li    # default adapter after the manager client is initialized.
4160*9c5db199SXin Li    DEFAULT_ADAPTER = 0
4161*9c5db199SXin Li
4162*9c5db199SXin Li    # How long we wait for the adapter to come up after we start it
4163*9c5db199SXin Li    ADAPTER_DAEMON_TIMEOUT_SEC = 20
4164*9c5db199SXin Li
4165*9c5db199SXin Li    # Floss stops discovery after ~12s after starting. To improve discovery
4166*9c5db199SXin Li    # chances in tests, we need to keep restarting discovery. This timeout
4167*9c5db199SXin Li    # tracks how long an overall discovery session should be.
4168*9c5db199SXin Li    DISCOVERY_TIMEOUT_SEC = 60
4169*9c5db199SXin Li
4170*9c5db199SXin Li    class DiscoveryObserver(BluetoothCallbacks):
4171*9c5db199SXin Li        """ Discovery observer that restarts discovery until a timeout.
4172*9c5db199SXin Li
4173*9c5db199SXin Li        By default, the Floss stack stops discovery after ~12s. This can be an
4174*9c5db199SXin Li        insufficient amount of time to discover a device, especially classic
4175*9c5db199SXin Li        devices. To mimic Bluez, we have this observer restart discovery each
4176*9c5db199SXin Li        time it is stopped up until a given timeout.
4177*9c5db199SXin Li        """
4178*9c5db199SXin Li
4179*9c5db199SXin Li        def __init__(self, adapter_client, timeout_secs):
4180*9c5db199SXin Li            """Constructor.
4181*9c5db199SXin Li
4182*9c5db199SXin Li            @param adapter_client: Already initialized client instance.
4183*9c5db199SXin Li            @param timeout_secs: How long to continue refreshing discovery.
4184*9c5db199SXin Li            """
4185*9c5db199SXin Li            self.adapter_client = adapter_client
4186*9c5db199SXin Li            self.deadline = datetime.now() + timedelta(seconds=timeout_secs)
4187*9c5db199SXin Li            self.adapter_client.register_callback_observer(
4188*9c5db199SXin Li                    'DiscoveryObserver', self)
4189*9c5db199SXin Li            self.discovering = None
4190*9c5db199SXin Li
4191*9c5db199SXin Li        def __del__(self):
4192*9c5db199SXin Li            if self.adapter_client:
4193*9c5db199SXin Li                self.cleanup()
4194*9c5db199SXin Li
4195*9c5db199SXin Li        def cleanup(self):
4196*9c5db199SXin Li            """Clean up after this observer."""
4197*9c5db199SXin Li            self.adapter_client.unregister_callback_observer(
4198*9c5db199SXin Li                    'DiscoveryObserver', self)
4199*9c5db199SXin Li            self.adapter_client = None
4200*9c5db199SXin Li
4201*9c5db199SXin Li        def on_discovering_changed(self, discovering):
4202*9c5db199SXin Li            """Discovering has changed."""
4203*9c5db199SXin Li
4204*9c5db199SXin Li            logging.info('Discovering changed to %s', discovering)
4205*9c5db199SXin Li
4206*9c5db199SXin Li            prev = self.discovering
4207*9c5db199SXin Li            self.discovering = discovering
4208*9c5db199SXin Li
4209*9c5db199SXin Li            # No-op if this is the same notification sent multiple times
4210*9c5db199SXin Li            if prev == discovering:
4211*9c5db199SXin Li                pass
4212*9c5db199SXin Li            # If discovering ended, check if the observer has timed out yet. If
4213*9c5db199SXin Li            # not, re-start the discovery.
4214*9c5db199SXin Li            if not discovering and datetime.now() < self.deadline:
4215*9c5db199SXin Li                self.adapter_client.start_discovery(
4216*9c5db199SXin Li                        method_callback=self.start_discovery_rsp)
4217*9c5db199SXin Li
4218*9c5db199SXin Li        def start_discovery_rsp(self, err, result):
4219*9c5db199SXin Li            """Result to |adapter_client.start_discovery|."""
4220*9c5db199SXin Li            # Log any errors that may have occurred
4221*9c5db199SXin Li            if err:
4222*9c5db199SXin Li                logging.error('Error on start_discovery: %s', err)
4223*9c5db199SXin Li            elif result:
4224*9c5db199SXin Li                logging.error('Error on start_discovery: Status=%s', result)
4225*9c5db199SXin Li
4226*9c5db199SXin Li    def __init__(self):
4227*9c5db199SXin Li        # Init the BaseFacade first
4228*9c5db199SXin Li        super(FlossFacadeLocal, self).__init__()
4229*9c5db199SXin Li
4230*9c5db199SXin Li        # Start mainloop thread in background. This will also initialize a few
4231*9c5db199SXin Li        # other variables (self.bus, self.mainloop, self.event_context) that may
4232*9c5db199SXin Li        # be necessary for proper operation.
4233*9c5db199SXin Li        self.mainloop_quit = threading.Event()
4234*9c5db199SXin Li        self.mainloop_ready = threading.Event()
4235*9c5db199SXin Li        self.thread = threading.Thread(
4236*9c5db199SXin Li                name=GLIB_THREAD_NAME,
4237*9c5db199SXin Li                target=FlossFacadeLocal.mainloop_thread,
4238*9c5db199SXin Li                args=(self, ))
4239*9c5db199SXin Li        self.thread.start()
4240*9c5db199SXin Li
4241*9c5db199SXin Li        # Wait for mainloop to be ready
4242*9c5db199SXin Li        if not self.mainloop_ready.wait(timeout=5):
4243*9c5db199SXin Li            raise Exception('Unable to initialize GLib mainloop')
4244*9c5db199SXin Li
4245*9c5db199SXin Li        # Always initialize the manager client since there is a single instance.
4246*9c5db199SXin Li        self.manager_client = FlossManagerClient(self.bus)
4247*9c5db199SXin Li        self.adapter_client = FlossAdapterClient(self.bus,
4248*9c5db199SXin Li                                                 self.DEFAULT_ADAPTER)
4249*9c5db199SXin Li
4250*9c5db199SXin Li        self.is_clean = False
4251*9c5db199SXin Li
4252*9c5db199SXin Li        # Discovery needs to last longer than the default 12s. Keep an observer
4253*9c5db199SXin Li        # that re-enables discovery up to some timeout.
4254*9c5db199SXin Li        self.discovery_observer = None
4255*9c5db199SXin Li
4256*9c5db199SXin Li        # Cache some mock properties for testing. These may be properties that
4257*9c5db199SXin Li        # are required in bluez but don't carry over well into Floss.
4258*9c5db199SXin Li        self.mock_properties = {}
4259*9c5db199SXin Li
4260*9c5db199SXin Li    def __del__(self):
4261*9c5db199SXin Li        if not self.is_clean:
4262*9c5db199SXin Li            self.cleanup()
4263*9c5db199SXin Li
4264*9c5db199SXin Li    def cleanup(self):
4265*9c5db199SXin Li        """Clean up the mainloop thread."""
4266*9c5db199SXin Li        self.mainloop_quit.set()
4267*9c5db199SXin Li        self.mainloop.quit()
4268*9c5db199SXin Li        self.is_clean = True
4269*9c5db199SXin Li
4270*9c5db199SXin Li    @staticmethod
4271*9c5db199SXin Li    def mainloop_thread(self):
4272*9c5db199SXin Li        """Runs GLib mainloop until we signal that we should quit."""
4273*9c5db199SXin Li
4274*9c5db199SXin Li        # Set up mainloop. All subsequent buses and connections will use this
4275*9c5db199SXin Li        # mainloop. We also use a separate main context to avoid multithreading
4276*9c5db199SXin Li        # issues.
4277*9c5db199SXin Li        #self.event_context = GLib.MainContext()
4278*9c5db199SXin Li        #self.mainloop = GLib.MainLoop(context=self.event_context)
4279*9c5db199SXin Li        GLib.threads_init()
4280*9c5db199SXin Li        self.mainloop = GLib.MainLoop()
4281*9c5db199SXin Li
4282*9c5db199SXin Li        # Set up bus connection
4283*9c5db199SXin Li        self.bus = pydbus.SystemBus()
4284*9c5db199SXin Li
4285*9c5db199SXin Li        # Set thread ready
4286*9c5db199SXin Li        self.mainloop_ready.set()
4287*9c5db199SXin Li
4288*9c5db199SXin Li        while not self.mainloop_quit.is_set():
4289*9c5db199SXin Li            self.mainloop.run()
4290*9c5db199SXin Li
4291*9c5db199SXin Li    def get_floss_enabled(self):
4292*9c5db199SXin Li        """Is Floss enabled right now?
4293*9c5db199SXin Li
4294*9c5db199SXin Li        Returns:
4295*9c5db199SXin Li            True if Floss is enabled, False if Bluez is enabled.
4296*9c5db199SXin Li        """
4297*9c5db199SXin Li        return self.manager_client.get_floss_enabled()
4298*9c5db199SXin Li
4299*9c5db199SXin Li    def set_floss_enabled(self, enabled):
4300*9c5db199SXin Li        """Enable or disable Floss."""
4301*9c5db199SXin Li        self.manager_client.set_floss_enabled(enabled)
4302*9c5db199SXin Li
4303*9c5db199SXin Li    def start_bluetoothd(self):
4304*9c5db199SXin Li        """Starts Floss. This includes enabling the adapter.
4305*9c5db199SXin Li
4306*9c5db199SXin Li        Returns:
4307*9c5db199SXin Li            True if default adapter is enabled successfully. False otherwise.
4308*9c5db199SXin Li        """
4309*9c5db199SXin Li        # Start manager and enable Floss
4310*9c5db199SXin Li        if not self.configure_floss(enabled=True):
4311*9c5db199SXin Li            return False
4312*9c5db199SXin Li
4313*9c5db199SXin Li        # Restarts the default adapter
4314*9c5db199SXin Li        if not self.reset_on():
4315*9c5db199SXin Li            return False
4316*9c5db199SXin Li
4317*9c5db199SXin Li        # If we need to wait for any other interfaces, add below here:
4318*9c5db199SXin Li        # ------------------------------------------------------------
4319*9c5db199SXin Li
4320*9c5db199SXin Li        return True
4321*9c5db199SXin Li
4322*9c5db199SXin Li    def stop_bluetoothd(self):
4323*9c5db199SXin Li        """Stops Floss. This includes disabling btmanagerd.
4324*9c5db199SXin Li
4325*9c5db199SXin Li        Returns:
4326*9c5db199SXin Li            True if adapter daemon and manager daemon are both off.
4327*9c5db199SXin Li        """
4328*9c5db199SXin Li        # First power off the adapter
4329*9c5db199SXin Li        if not self.reset_off():
4330*9c5db199SXin Li            logging.warn('Failed to stop btadapterd')
4331*9c5db199SXin Li            return False
4332*9c5db199SXin Li
4333*9c5db199SXin Li        if not UpstartClient.stop(self.MANAGER_JOB):
4334*9c5db199SXin Li            logging.warn('Failed to stop btmanagerd')
4335*9c5db199SXin Li            return False
4336*9c5db199SXin Li
4337*9c5db199SXin Li        def _daemon_stopped():
4338*9c5db199SXin Li            return all([
4339*9c5db199SXin Li                    not self.manager_client.has_proxy(),
4340*9c5db199SXin Li                    not self.adapter_client.has_proxy(),
4341*9c5db199SXin Li            ])
4342*9c5db199SXin Li
4343*9c5db199SXin Li        try:
4344*9c5db199SXin Li            utils.poll_for_condition(condition=_daemon_stopped,
4345*9c5db199SXin Li                                     desc='Bluetooth daemons have stopped',
4346*9c5db199SXin Li                                     timeout=self.DAEMON_TIMEOUT_SEC)
4347*9c5db199SXin Li            daemon_stopped = True
4348*9c5db199SXin Li        except Exception as e:
4349*9c5db199SXin Li            logging.error('timeout: error stopping floss daemons: %s', e)
4350*9c5db199SXin Li            daemon_stopped = False
4351*9c5db199SXin Li
4352*9c5db199SXin Li        return daemon_stopped
4353*9c5db199SXin Li
4354*9c5db199SXin Li    def restart_cras(self):
4355*9c5db199SXin Li        """Restarts the cras daemon."""
4356*9c5db199SXin Li        self._restart_cras(enable_floss=True)
4357*9c5db199SXin Li
4358*9c5db199SXin Li    def is_bluetoothd_proxy_valid(self):
4359*9c5db199SXin Li        """Checks whether the proxy objects for Floss are ok."""
4360*9c5db199SXin Li        return all([
4361*9c5db199SXin Li                self.manager_client.has_proxy(),
4362*9c5db199SXin Li                self.adapter_client.has_proxy()
4363*9c5db199SXin Li        ])
4364*9c5db199SXin Li
4365*9c5db199SXin Li    def is_bluetoothd_running(self):
4366*9c5db199SXin Li        """Checks whether Floss daemon is running."""
4367*9c5db199SXin Li        # This api doesn't enforce that the adapter is powered so we only check
4368*9c5db199SXin Li        # that the manager proxy is up.
4369*9c5db199SXin Li        return self.manager_client.has_proxy()
4370*9c5db199SXin Li
4371*9c5db199SXin Li    def has_adapter(self):
4372*9c5db199SXin Li        """Checks whether an adapter exists."""
4373*9c5db199SXin Li        return len(self.manager_client.get_available_adapters()) > 0
4374*9c5db199SXin Li
4375*9c5db199SXin Li    def set_debug_log_levels(self, bluez_vb, kernel_vb):
4376*9c5db199SXin Li        """Enables verbose logging."""
4377*9c5db199SXin Li        # TODO(abps) - This will be necessary for Floss but may not need to
4378*9c5db199SXin Li        #              touch the kernel. This needs to be implemented at the
4379*9c5db199SXin Li        #              daemon level still.
4380*9c5db199SXin Li        return False
4381*9c5db199SXin Li
4382*9c5db199SXin Li    def start_discovery(self):
4383*9c5db199SXin Li        """Start discovery of remote devices."""
4384*9c5db199SXin Li        if not self.adapter_client.has_proxy():
4385*9c5db199SXin Li            return (False, 'Adapter not found')
4386*9c5db199SXin Li
4387*9c5db199SXin Li        if self.discovery_observer:
4388*9c5db199SXin Li            self.discovery_observer.cleanup()
4389*9c5db199SXin Li
4390*9c5db199SXin Li        self.discovery_observer = self.DiscoveryObserver(
4391*9c5db199SXin Li                self.adapter_client, self.DISCOVERY_TIMEOUT_SEC)
4392*9c5db199SXin Li        return (self.adapter_client.start_discovery(), '')
4393*9c5db199SXin Li
4394*9c5db199SXin Li    def stop_discovery(self):
4395*9c5db199SXin Li        """Stop discovery of remote deviecs."""
4396*9c5db199SXin Li        if not self.adapter_client.has_proxy():
4397*9c5db199SXin Li            return (False, 'Adapter not found')
4398*9c5db199SXin Li
4399*9c5db199SXin Li        if self.discovery_observer:
4400*9c5db199SXin Li            self.discovery_observer.cleanup()
4401*9c5db199SXin Li            self.discovery_observer = None
4402*9c5db199SXin Li
4403*9c5db199SXin Li        return (self.adapter_client.stop_discovery(), '')
4404*9c5db199SXin Li
4405*9c5db199SXin Li    def is_discovering(self):
4406*9c5db199SXin Li        """Check if adapter is discovering."""
4407*9c5db199SXin Li        return self.adapter_client.is_discovering()
4408*9c5db199SXin Li
4409*9c5db199SXin Li    def is_powered_on(self):
4410*9c5db199SXin Li        """Gets whether the default adapter is enabled."""
4411*9c5db199SXin Li        default_adapter = self.manager_client.get_default_adapter()
4412*9c5db199SXin Li        return self.manager_client.get_adapter_enabled(default_adapter)
4413*9c5db199SXin Li
4414*9c5db199SXin Li    def set_powered(self, powered):
4415*9c5db199SXin Li        """Sets the default adapter's enabled state."""
4416*9c5db199SXin Li        default_adapter = self.manager_client.get_default_adapter()
4417*9c5db199SXin Li
4418*9c5db199SXin Li        if powered and not self.manager_client.has_default_adapter():
4419*9c5db199SXin Li            logging.warning('set_powered: Default adapter not available.')
4420*9c5db199SXin Li            return False
4421*9c5db199SXin Li
4422*9c5db199SXin Li        if powered:
4423*9c5db199SXin Li            self.manager_client.start(default_adapter)
4424*9c5db199SXin Li        else:
4425*9c5db199SXin Li            self.manager_client.stop(default_adapter)
4426*9c5db199SXin Li
4427*9c5db199SXin Li        return True
4428*9c5db199SXin Li
4429*9c5db199SXin Li    def reset_on(self):
4430*9c5db199SXin Li        """Reset the default adapter into an ON state."""
4431*9c5db199SXin Li        return self.do_reset(True)
4432*9c5db199SXin Li
4433*9c5db199SXin Li    def reset_off(self):
4434*9c5db199SXin Li        """Reset the default adapter into an OFF state."""
4435*9c5db199SXin Li        return self.do_reset(False)
4436*9c5db199SXin Li
4437*9c5db199SXin Li    def do_reset(self, power_on):
4438*9c5db199SXin Li        """Resets the default adapter."""
4439*9c5db199SXin Li        # Start manager and enable Floss if not already up
4440*9c5db199SXin Li        if not self.configure_floss(enabled=True):
4441*9c5db199SXin Li            return False
4442*9c5db199SXin Li
4443*9c5db199SXin Li        default_adapter = self.manager_client.get_default_adapter()
4444*9c5db199SXin Li
4445*9c5db199SXin Li        def _is_adapter_down(client):
4446*9c5db199SXin Li            return lambda: not client.has_proxy()
4447*9c5db199SXin Li
4448*9c5db199SXin Li        def _is_adapter_ready(client):
4449*9c5db199SXin Li            return lambda: client.has_proxy() and client.get_address()
4450*9c5db199SXin Li
4451*9c5db199SXin Li        self.manager_client.stop(default_adapter)
4452*9c5db199SXin Li        try:
4453*9c5db199SXin Li            condition = _is_adapter_down(self.adapter_client)
4454*9c5db199SXin Li            utils.poll_for_condition(condition=condition,
4455*9c5db199SXin Li                                     desc='Wait for adapter stop',
4456*9c5db199SXin Li                                     sleep_interval=0.5,
4457*9c5db199SXin Li                                     timeout=self.ADAPTER_DAEMON_TIMEOUT_SEC)
4458*9c5db199SXin Li        except Exception as e:
4459*9c5db199SXin Li            logging.error('timeout: error stopping adapter daemon: %s', e)
4460*9c5db199SXin Li            logging.error(traceback.format_exc())
4461*9c5db199SXin Li            return False
4462*9c5db199SXin Li
4463*9c5db199SXin Li        if not power_on:
4464*9c5db199SXin Li            logging.debug('do_reset: Completed with power_on=False')
4465*9c5db199SXin Li            return True
4466*9c5db199SXin Li
4467*9c5db199SXin Li        # Start the client again
4468*9c5db199SXin Li        self.manager_client.start(default_adapter)
4469*9c5db199SXin Li        self.adapter_client = FlossAdapterClient(self.bus, default_adapter)
4470*9c5db199SXin Li
4471*9c5db199SXin Li        try:
4472*9c5db199SXin Li            condition = _is_adapter_ready(self.adapter_client)
4473*9c5db199SXin Li            utils.poll_for_condition(condition=condition,
4474*9c5db199SXin Li                                     desc='Wait for adapter start',
4475*9c5db199SXin Li                                     sleep_interval=0.5,
4476*9c5db199SXin Li                                     timeout=self.ADAPTER_DAEMON_TIMEOUT_SEC)
4477*9c5db199SXin Li        except Exception as e:
4478*9c5db199SXin Li            logging.error('timeout: error starting adapter daemon: %s', e)
4479*9c5db199SXin Li            logging.error(traceback.format_exc())
4480*9c5db199SXin Li            return False
4481*9c5db199SXin Li
4482*9c5db199SXin Li        # We need to observe callbacks for proper operation.
4483*9c5db199SXin Li        if not self.adapter_client.register_callbacks():
4484*9c5db199SXin Li            logging.error('adapter_client: Failed to register callbacks')
4485*9c5db199SXin Li            return False
4486*9c5db199SXin Li
4487*9c5db199SXin Li        logging.debug('do_reset: Completed with power_on=True')
4488*9c5db199SXin Li        return True
4489*9c5db199SXin Li
4490*9c5db199SXin Li    def policy_get_service_allow_list(self):
4491*9c5db199SXin Li        """Gets the service allow list for enterprise policy."""
4492*9c5db199SXin Li        # TODO(abps) - Actually implement this
4493*9c5db199SXin Li        return []
4494*9c5db199SXin Li
4495*9c5db199SXin Li    def policy_set_service_allow_list(self, uuids):
4496*9c5db199SXin Li        """Sets the service allow list for enterprise policy."""
4497*9c5db199SXin Li        # TODO(abps) - Actually implement this
4498*9c5db199SXin Li        return (True, '')
4499*9c5db199SXin Li
4500*9c5db199SXin Li    def get_address(self):
4501*9c5db199SXin Li        """Gets the default adapter address."""
4502*9c5db199SXin Li        return self.adapter_client.get_address()
4503*9c5db199SXin Li
4504*9c5db199SXin Li    def has_device(self, address):
4505*9c5db199SXin Li        """Checks if adapter knows the device."""
4506*9c5db199SXin Li        return self.adapter_client.has_device(address)
4507*9c5db199SXin Li
4508*9c5db199SXin Li    def remove_device_object(self, address):
4509*9c5db199SXin Li        """Removes a known device object."""
4510*9c5db199SXin Li        return self.adapter_client.forget_device(address)
4511*9c5db199SXin Li
4512*9c5db199SXin Li    def connect_device(self, address):
4513*9c5db199SXin Li        """Connect a specific address."""
4514*9c5db199SXin Li        return self.adapter_client.connect_all_enabled_profiles(address)
4515*9c5db199SXin Li
4516*9c5db199SXin Li    def disconnect_device(self, address):
4517*9c5db199SXin Li        """Disconnect a specific address."""
4518*9c5db199SXin Li        return self.adapter_client.disconnect_all_enabled_profiles(address)
4519*9c5db199SXin Li
4520*9c5db199SXin Li    def get_device_property(self, address, prop_name):
4521*9c5db199SXin Li        """Read a property from a remote device.
4522*9c5db199SXin Li
4523*9c5db199SXin Li        @param address: Address of the device to query
4524*9c5db199SXin Li        @param prop_name: Property to be queried
4525*9c5db199SXin Li
4526*9c5db199SXin Li        @return Base64 encoded json if property exists or None.
4527*9c5db199SXin Li        """
4528*9c5db199SXin Li        prop_val = None
4529*9c5db199SXin Li
4530*9c5db199SXin Li        if self.adapter_client.has_device(address):
4531*9c5db199SXin Li            prop_val = self.adapter_client.get_remote_property(
4532*9c5db199SXin Li                    address, prop_name)
4533*9c5db199SXin Li
4534*9c5db199SXin Li        return self._encode_base64_json(prop_val)
4535*9c5db199SXin Li
4536*9c5db199SXin Li    def get_pairable(self):
4537*9c5db199SXin Li        """Gets whether the default adapter is pairable.
4538*9c5db199SXin Li
4539*9c5db199SXin Li        @return True if default adapter is pairable.
4540*9c5db199SXin Li        """
4541*9c5db199SXin Li        # TODO(abps) - Control pairable setting on adapter
4542*9c5db199SXin Li        return self.mock_properties.get('Pairable', False)
4543*9c5db199SXin Li
4544*9c5db199SXin Li    def set_pairable(self, pairable):
4545*9c5db199SXin Li        """Sets default adapter as pairable.
4546*9c5db199SXin Li
4547*9c5db199SXin Li        @param pairable: Control pairable property of the adapter.
4548*9c5db199SXin Li
4549*9c5db199SXin Li        @return True on success.
4550*9c5db199SXin Li        """
4551*9c5db199SXin Li        # TODO(abps) - Control pairable setting on adapter
4552*9c5db199SXin Li        self.mock_properties['Pairable'] = pairable
4553*9c5db199SXin Li        return True
4554*9c5db199SXin Li
4555*9c5db199SXin Li    def pair_legacy_device(self, address, pin, trusted, timeout=60):
4556*9c5db199SXin Li        """Pairs a peer device.
4557*9c5db199SXin Li
4558*9c5db199SXin Li        @param address: BT address of the peer device.
4559*9c5db199SXin Li        @param pin: What pin to use for pairing.
4560*9c5db199SXin Li        @param trusted: Unused by Floss.
4561*9c5db199SXin Li        @param timeout: How long to wait for pairing to complete.
4562*9c5db199SXin Li        """
4563*9c5db199SXin Li
4564*9c5db199SXin Li        class PairingObserver(BluetoothCallbacks,
4565*9c5db199SXin Li                              BluetoothConnectionCallbacks):
4566*9c5db199SXin Li            """Observer of certain callbacks for pairing."""
4567*9c5db199SXin Li
4568*9c5db199SXin Li            def __init__(self, adapter_client, done_event, address, pin):
4569*9c5db199SXin Li                self.adapter_client = adapter_client
4570*9c5db199SXin Li                self.adapter_client.register_callback_observer(
4571*9c5db199SXin Li                        'PairingObserver' + address, self)
4572*9c5db199SXin Li
4573*9c5db199SXin Li                # Event to trigger once we are paired and connected.
4574*9c5db199SXin Li                self.done_event = done_event
4575*9c5db199SXin Li                self.address = address
4576*9c5db199SXin Li                self.pin = pin
4577*9c5db199SXin Li                self.bond_state = BondState.NOT_BONDED
4578*9c5db199SXin Li                self.connected = self.adapter_client.is_connected(address)
4579*9c5db199SXin Li
4580*9c5db199SXin Li            def __del__(self):
4581*9c5db199SXin Li                """Destructor"""
4582*9c5db199SXin Li                if self.adapter_client:
4583*9c5db199SXin Li                    self.cleanup()
4584*9c5db199SXin Li
4585*9c5db199SXin Li            def cleanup(self):
4586*9c5db199SXin Li                """Clean up after this observer."""
4587*9c5db199SXin Li                self.adapter_client.unregister_callback_observer(
4588*9c5db199SXin Li                        'PairingObserver' + address, self)
4589*9c5db199SXin Li                self.adapter_client = None
4590*9c5db199SXin Li
4591*9c5db199SXin Li            def on_bond_state_changed(self, status, device_address, state):
4592*9c5db199SXin Li                """Handle bond state change."""
4593*9c5db199SXin Li                logging.info('[%s] bond state=%d', device_address, state)
4594*9c5db199SXin Li
4595*9c5db199SXin Li                if device_address != self.address:
4596*9c5db199SXin Li                    return
4597*9c5db199SXin Li
4598*9c5db199SXin Li                # If we have a non-zero status, bonding failed in some way.
4599*9c5db199SXin Li                # Report it and unblock the main thread.
4600*9c5db199SXin Li                if status != 0:
4601*9c5db199SXin Li                    logging.error('[%s] failed to bond. Status=%d, State=%d',
4602*9c5db199SXin Li                                  device_address, status, state)
4603*9c5db199SXin Li                    self.done_event.set()
4604*9c5db199SXin Li                    return
4605*9c5db199SXin Li
4606*9c5db199SXin Li                self.bond_state = state
4607*9c5db199SXin Li                logging.info('[%s] bond state=%d', device_address, state)
4608*9c5db199SXin Li
4609*9c5db199SXin Li                # We've completed bonding. Make sure to connect
4610*9c5db199SXin Li                if state == BondState.BONDED:
4611*9c5db199SXin Li                    # If not connected, connect profiles and wait for connected
4612*9c5db199SXin Li                    # callback. Else, unblock the main thread.
4613*9c5db199SXin Li                    if not self.connected:
4614*9c5db199SXin Li                        if not self.adapter_client.connect_all_enabled_profiles(
4615*9c5db199SXin Li                                self.address):
4616*9c5db199SXin Li                            logging.error(
4617*9c5db199SXin Li                                    '[%s] failed on connect_all_enabled_profiles',
4618*9c5db199SXin Li                                    self.address)
4619*9c5db199SXin Li                            self.done_event.set()
4620*9c5db199SXin Li                    else:
4621*9c5db199SXin Li                        self.done_event.set()
4622*9c5db199SXin Li
4623*9c5db199SXin Li            def on_ssp_request(self, remote_device, class_of_device, variant,
4624*9c5db199SXin Li                               passkey):
4625*9c5db199SXin Li                """Handle SSP request."""
4626*9c5db199SXin Li                (remote_address, remote_name) = remote_device
4627*9c5db199SXin Li
4628*9c5db199SXin Li                if remote_address != self.address:
4629*9c5db199SXin Li                    return
4630*9c5db199SXin Li
4631*9c5db199SXin Li                logging.info('Ssp: [%s: %s]: Class=%d, Variant=%d, Passkey=%d',
4632*9c5db199SXin Li                             remote_address, remote_name, class_of_device,
4633*9c5db199SXin Li                             variant, passkey)
4634*9c5db199SXin Li
4635*9c5db199SXin Li                if variant == int(SspVariant.CONSENT):
4636*9c5db199SXin Li                    self.adapter_client.set_pairing_confirmation(
4637*9c5db199SXin Li                            remote_address,
4638*9c5db199SXin Li                            True,
4639*9c5db199SXin Li                            method_callback=self.on_set_pairing_confirmation)
4640*9c5db199SXin Li
4641*9c5db199SXin Li                logging.info('Exited ssp request.')
4642*9c5db199SXin Li
4643*9c5db199SXin Li            def on_set_pairing_confirmation(self, err, result):
4644*9c5db199SXin Li                """Handle async method result from set pairing confirmation."""
4645*9c5db199SXin Li                if err or not result:
4646*9c5db199SXin Li                    logging.error(
4647*9c5db199SXin Li                            'Pairing confirmation failed: err[%s], result[%s]',
4648*9c5db199SXin Li                            err, result)
4649*9c5db199SXin Li                    self.done_event.set()
4650*9c5db199SXin Li
4651*9c5db199SXin Li            def on_device_connected(self, remote_device):
4652*9c5db199SXin Li                """Handle device connection."""
4653*9c5db199SXin Li                (remote_address, _) = remote_device
4654*9c5db199SXin Li
4655*9c5db199SXin Li                logging.info('[%s] connected', remote_address)
4656*9c5db199SXin Li
4657*9c5db199SXin Li                if remote_address != self.address:
4658*9c5db199SXin Li                    return
4659*9c5db199SXin Li
4660*9c5db199SXin Li                self.connected = True
4661*9c5db199SXin Li
4662*9c5db199SXin Li                # If we're already bonded, unblock the main thread.
4663*9c5db199SXin Li                if self.bond_state == BondState.BONDED:
4664*9c5db199SXin Li                    self.done_event.set()
4665*9c5db199SXin Li
4666*9c5db199SXin Li        # Start pairing process in main thread
4667*9c5db199SXin Li
4668*9c5db199SXin Li        done_evt = threading.Event()
4669*9c5db199SXin Li
4670*9c5db199SXin Li        # First we need an observer that watches for callbacks
4671*9c5db199SXin Li        pairing_observer = PairingObserver(self.adapter_client, done_evt,
4672*9c5db199SXin Li                                           address, pin)
4673*9c5db199SXin Li
4674*9c5db199SXin Li        # Pair and connect. If either action fails, mark the done event so that
4675*9c5db199SXin Li        # we fall through without blocking.
4676*9c5db199SXin Li        if not self.device_is_paired(address):
4677*9c5db199SXin Li            if not self.adapter_client.create_bond(address, Transport.AUTO):
4678*9c5db199SXin Li                done_evt.set()
4679*9c5db199SXin Li        elif not self.device_is_connected(address):
4680*9c5db199SXin Li            if not self.adapter_client.connect_all_enabled_profiles(address):
4681*9c5db199SXin Li                done_evt.set()
4682*9c5db199SXin Li
4683*9c5db199SXin Li        done_evt.wait(timeout=timeout)
4684*9c5db199SXin Li        if not done_evt.is_set():
4685*9c5db199SXin Li            logging.error('Timed out waiting for pairing to complete.')
4686*9c5db199SXin Li
4687*9c5db199SXin Li        is_paired = self.device_is_paired(address)
4688*9c5db199SXin Li        is_connected = self.device_is_connected(address)
4689*9c5db199SXin Li
4690*9c5db199SXin Li        # If pairing and hci connection is complete, also trigger all profile
4691*9c5db199SXin Li        # connections here. This is necessary because device connection doesn't
4692*9c5db199SXin Li        # always imply profile connection.
4693*9c5db199SXin Li        if is_paired and is_connected:
4694*9c5db199SXin Li            self.adapter_client.connect_all_enabled_profiles(address)
4695*9c5db199SXin Li
4696*9c5db199SXin Li        logging.info('Pairing result: paired(%s) connected(%s)', is_paired,
4697*9c5db199SXin Li                     is_connected)
4698*9c5db199SXin Li
4699*9c5db199SXin Li        return is_paired and is_connected
4700*9c5db199SXin Li
4701*9c5db199SXin Li    def device_is_connected(self, address):
4702*9c5db199SXin Li        """Checks whether a device is connected.
4703*9c5db199SXin Li
4704*9c5db199SXin Li        @param address: BT address of peer device.
4705*9c5db199SXin Li        @return True if connected.
4706*9c5db199SXin Li        """
4707*9c5db199SXin Li        return self.adapter_client.is_connected(address)
4708*9c5db199SXin Li
4709*9c5db199SXin Li    def has_connection_info(self, address):
4710*9c5db199SXin Li        """Same as |device_is_connected| on Floss.
4711*9c5db199SXin Li
4712*9c5db199SXin Li        Bluez has a separate ConnectionInfo tuple that is read from the kernel
4713*9c5db199SXin Li        but Floss doesn't have this. We have this function simply for
4714*9c5db199SXin Li        compatibility.
4715*9c5db199SXin Li
4716*9c5db199SXin Li        @param address: BT address of peer device.
4717*9c5db199SXin Li        @return True if connected.
4718*9c5db199SXin Li        """
4719*9c5db199SXin Li        return self.device_is_connected(address)
4720*9c5db199SXin Li
4721*9c5db199SXin Li    def get_num_connected_devices(self):
4722*9c5db199SXin Li        """ Return number of remote devices currently connected to the DUT.
4723*9c5db199SXin Li
4724*9c5db199SXin Li        @returns: The number of devices known to bluez with the Connected
4725*9c5db199SXin Li            property active
4726*9c5db199SXin Li        """
4727*9c5db199SXin Li        return self.adapter_client.get_connected_devices_count()
4728*9c5db199SXin Li
4729*9c5db199SXin Li    def device_is_paired(self, address):
4730*9c5db199SXin Li        """Checks if a device is paired.
4731*9c5db199SXin Li
4732*9c5db199SXin Li        @param address: address of the device.
4733*9c5db199SXin Li        @returns: True if device is paired. False otherwise.
4734*9c5db199SXin Li        """
4735*9c5db199SXin Li        return self.adapter_client.is_bonded(address)
4736*9c5db199SXin Li
4737*9c5db199SXin Li    def is_discoverable(self):
4738*9c5db199SXin Li        """Return whether the adapter is currently discoverable."""
4739*9c5db199SXin Li        return self.adapter_client.get_property('Discoverable')
4740*9c5db199SXin Li
4741*9c5db199SXin Li    def set_discoverable(self, discoverable, duration=60):
4742*9c5db199SXin Li        """Sets the adapter as discoverable for given duration in seconds."""
4743*9c5db199SXin Li        return self.adapter_client.set_property('Discoverable', discoverable,
4744*9c5db199SXin Li                                                duration)
4745*9c5db199SXin Li
4746*9c5db199SXin Li    def get_supported_capabilities(self):
4747*9c5db199SXin Li        """" Get supported capabilities of the adapter."""
4748*9c5db199SXin Li        return (json.dumps({}), 'Not yet implemented')
4749