xref: /aosp_15_r20/external/autotest/server/cros/bluetooth/bluetooth_adapter_pairing_tests.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright 2019 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""
7Server side bluetooth tests on adapter pairing and connecting to a bluetooth
8HID device.
9"""
10
11from __future__ import absolute_import
12from __future__ import division
13from __future__ import print_function
14
15import logging
16import time
17
18import common
19from autotest_lib.server.cros.bluetooth import bluetooth_adapter_tests
20from six.moves import range
21
22
23class BluetoothAdapterPairingTests(
24        bluetooth_adapter_tests.BluetoothAdapterTests):
25    """Server side bluetooth adapter pairing and connecting to bluetooth device
26
27    This test tries to verify that the adapter of the DUT could
28    pair and connect to a bluetooth HID device correctly.
29
30    In particular, the following subtests are performed. Look at the
31    docstrings of the subtests for more details.
32    -
33
34    Refer to BluetoothAdapterTests for all subtests performed in this test.
35
36    """
37
38    # TODO(josephsih): Reduce the sleep intervals to speed up the tests.
39    PAIR_TEST_SLEEP_SECS = 5
40
41    def pairing_test(self, device, check_connected_method=lambda device: True,
42                     pairing_twice=False, suspend_resume=False, reboot=False):
43        """Running Bluetooth adapter tests about pairing to a device."""
44
45        # Reset the adapter to forget previously paired devices if any.
46        if not self.test_reset_on_adapter():
47            return
48
49        # The adapter must be set to the pairable state.
50        if not self.test_pairable():
51            return
52
53        # Test if the adapter could discover the target device.
54        time.sleep(self.PAIR_TEST_SLEEP_SECS)
55        if not self.test_discover_device(device.address):
56            return
57
58        # Test if the discovered device class of service is correct.
59        if not self.test_device_class_of_service(device.address,
60                                                 device.class_of_service):
61            return
62
63        # Test if the discovered device class of device is correct.
64        if not self.test_device_class_of_device(device.address,
65                                                device.class_of_device):
66            return
67
68        # Verify that the adapter could pair with the device.
69        # Also set the device trusted when pairing is done.
70        # Device will be connected at the end of pairing.
71        if not self.test_pairing(device.address, device.pin, trusted=True):
72            return
73
74        # Test if the discovered device name is correct.
75        # Sometimes, it takes quite a long time after discovering
76        # the device (more than 60 seconds) to resolve the device name.
77        # Hence, it is safer to test the device name after pairing and
78        # connection is done.
79        if not self.test_device_name(device.address, device.name):
80            return
81
82        # Run hid test to make sure profile is connected
83        if not check_connected_method(device):
84            return
85
86        # Test if the device is still connected after suspend/resume.
87        if suspend_resume:
88            self.suspend_resume()
89
90            time.sleep(self.PAIR_TEST_SLEEP_SECS)
91            if not self.test_device_is_paired(device.address):
92                return
93
94
95            # check if peripheral is connected after suspend resume
96            if not self.ignore_failure(check_connected_method, device):
97                logging.info("device not connected after suspend_resume")
98                self.test_connection_by_device(device)
99                time.sleep(self.PAIR_TEST_SLEEP_SECS)
100                if not check_connected_method(device):
101                    return
102            else:
103                logging.info("device remains connected after suspend_resume")
104
105            time.sleep(self.PAIR_TEST_SLEEP_SECS)
106            if not self.test_device_name(device.address, device.name):
107                return
108
109        # Test if the device is still connected after reboot.
110        # if reboot:
111        #     self.host.reboot()
112
113        #     time.sleep(self.PAIR_TEST_SLEEP_SECS)
114        #     self.test_device_is_paired(device.address)
115
116        #     # After a reboot, we need to wake the peripheral
117        #     # as it is not connected.
118        #     time.sleep(self.PAIR_TEST_SLEEP_SECS)
119        #     self.test_connection_by_adapter(device.address)
120
121        #     time.sleep(self.PAIR_TEST_SLEEP_SECS)
122        #     self.test_device_is_connected(device.address)
123
124        #     time.sleep(self.PAIR_TEST_SLEEP_SECS)
125        #     self.test_device_name(device.address, device.name)
126
127        # Verify that the adapter could disconnect the device.
128        if not self.test_disconnection_by_adapter(device.address):
129            return
130
131        time.sleep(self.PAIR_TEST_SLEEP_SECS)
132
133        def test_connection():
134            """Tests connection inited by either the device or the adapter"""
135            if device.can_init_connection:
136                # Verify that the device could initiate the connection.
137                if not self.test_connection_by_device(device):
138                    return False
139
140                # With raspberry pi peer, it takes a moment before the device is
141                # registered as an input device. Without delay, the input recorder
142                # doesn't find the device
143                time.sleep(1)
144                return check_connected_method(device)
145            # Adapter inited connection.
146            # Reconnect so that we can test disconnection from the kit.
147            return self.test_connection_by_adapter(device.address)
148
149        if not test_connection():
150            return
151
152        def test_disconnection():
153            """Tests disconnection inited by either the device or the adapter"""
154            # TODO(alent): Needs a new capability, but this is a good proxy
155            if device.can_init_connection:
156                # Verify that the device could initiate the disconnection.
157                return self.test_disconnection_by_device(device)
158            # Adapter inited connection.
159            return self.test_disconnection_by_adapter(device.address)
160
161        if not test_disconnection():
162            return
163
164        # Verify that the adapter could remove the paired device.
165        if not self.test_remove_pairing(device.address):
166            return
167
168        # Check if the device could be re-paired after being forgotten.
169        if pairing_twice:
170            # Test if the adapter could discover the target device again.
171            time.sleep(self.PAIR_TEST_SLEEP_SECS)
172            if not self.test_discover_device(device.address):
173                return
174
175            # Verify that the adapter could pair with the device again.
176            # Also set the device trusted when pairing is done.
177            time.sleep(self.PAIR_TEST_SLEEP_SECS)
178            if not self.test_pairing(device.address, device.pin, trusted=True):
179                return
180
181            # Verify that the adapter could remove the paired device again.
182            time.sleep(self.PAIR_TEST_SLEEP_SECS)
183            self.test_remove_pairing(device.address)
184
185
186    def connect_disconnect_loop(self, device, loops):
187        """Perform a connect disconnect loop test"""
188
189        # First pair and disconnect, to emulate real life scenario
190        self.test_discover_device(device.address)
191        # self.bluetooth_facade.is_discovering() doesn't work as expected:
192        # crbug:905374
193        # self.test_stop_discovery()
194        time.sleep(self.PAIR_TEST_SLEEP_SECS)
195        self.test_pairing(device.address, device.pin, trusted=False)
196
197        # Verify device is now connected
198        self.test_device_is_connected(device.address)
199        self.test_hid_device_created(device.address)
200
201        # Disconnect the device
202        self.test_disconnection_by_adapter(device.address)
203        total_duration_by_adapter = 0
204        loop_cnt = 0
205        for i in range(0, loops):
206
207            # Verify device didn't connect automatically
208            time.sleep(2)
209            self.test_device_is_not_connected(device.address)
210
211            start_time = time.time()
212            self.test_connection_by_adapter(device.address)
213            end_time = time.time()
214            time_diff = end_time - start_time
215
216            # Verify device is now connected
217            self.test_device_is_connected(device.address)
218            self.test_hid_device_created(device.address)
219
220            self.test_disconnection_by_adapter(device.address)
221
222            if not bool(self.fails):
223                loop_cnt += 1
224                total_duration_by_adapter += time_diff
225                logging.info('%d: Connection establishment duration %f sec',
226                             i, time_diff)
227            else:
228                break
229
230        if not bool(self.fails):
231            logging.info('Average duration (by adapter) %f sec',
232                         total_duration_by_adapter/loop_cnt)
233
234
235    def connect_disconnect_by_device_loop(
236            self,
237            device,
238            loops,
239            device_type,
240            check_connected_method=lambda device: True):
241        """Perform a connect disconnect loop test"""
242
243        # Reset the adapter to forget previously paired devices if any.
244        self.test_reset_on_adapter()
245        self.test_pairable()
246        # First pair and disconnect, to emulate real life scenario
247        self.test_discover_device(device.address)
248        time.sleep(self.PAIR_TEST_SLEEP_SECS)
249        self.test_pairing(device.address, device.pin, trusted=True)
250
251        # Verify device is now connected
252        self.test_device_is_connected(device.address)
253        self.test_hid_device_created(device.address)
254
255        # Make device not discoverable and disconnect
256        self.test_device_set_discoverable(device, False)
257        self.test_disconnection_by_device(device)
258
259        total_reconnection_duration = 0
260        loop_cnt = 0
261        for i in range(0, loops):
262
263            # Verify device didn't connect automatically
264            time.sleep(2)
265            self.test_device_is_not_connected(device.address)
266
267            start_time = time.time()
268            if 'BLE' in device_type:
269                self.test_device_set_discoverable(device, True)
270                self.test_device_is_connected(device.address,
271                                              sleep_interval=0.1)
272            else:
273                self.test_connection_by_device(device, post_connection_delay=0)
274
275            check_connected_method(device)
276            end_time = time.time()
277            time_diff = end_time - start_time
278
279            if 'BLE' in device_type:
280                self.test_device_set_discoverable(device, False)
281
282            self.test_disconnection_by_device(device)
283
284            if not bool(self.fails):
285                loop_cnt += 1
286                total_reconnection_duration += time_diff
287                logging.info('%d: Connection establishment duration %f sec', i,
288                             time_diff)
289            else:
290                break
291
292        self.test_remove_pairing(device.address)
293        if not bool(self.fails):
294            average_reconnection_duration = total_reconnection_duration / loop_cnt
295            logging.info('Average duration (by device) %f sec',
296                         average_reconnection_duration)
297            return average_reconnection_duration
298
299
300    def auto_reconnect_loop(self,
301                            device,
302                            loops,
303                            check_connected_method=lambda device: True,
304                            restart_adapter=False):
305        """Running a loop to verify the paired peer can auto reconnect"""
306
307        # Let the adapter pair, and connect to the target device first
308        self.test_discover_device(device.address)
309        self.test_pairing(device.address, device.pin, trusted=True)
310
311        # Verify device is now connected
312        self.test_connection_by_adapter(device.address)
313        self.test_hid_device_created(device.address)
314
315        total_reconnection_duration = 0
316        loop_cnt = 0
317        for i in range(loops):
318            # Restart either the adapter or the peer
319            if restart_adapter:
320                self.test_power_off_adapter()
321                self.test_power_on_adapter()
322                start_time = time.time()
323            else:
324                # Restart and clear peer HID device
325                self.restart_peers()
326                start_time = time.time()
327
328            # Verify that the device is reconnected. Wait for the input device
329            # to become available before checking the profile connection.
330            self.test_device_is_connected(device.address)
331            self.test_hid_device_created(device.address)
332
333            check_connected_method(device)
334            end_time = time.time()
335            time_diff = end_time - start_time
336
337            if not bool(self.fails):
338                total_reconnection_duration += time_diff
339                loop_cnt += 1
340                logging.info('%d: Reconnection duration %f sec', i, time_diff)
341            else:
342                break
343
344        if not bool(self.fails):
345            logging.info('Average Reconnection duration %f sec',
346                         total_reconnection_duration/loop_cnt)
347
348
349    def hid_reconnect_speed(self, device, device_type):
350        """Test the HID device reconnect speed
351
352        @param device: the meta data with the peer device
353        @param device_type: The device type (used to check if it's LE)
354        """
355
356        duration = self.connect_disconnect_by_device_loop(
357                device=device,
358                loops=3,
359                device_type=device_type,
360                check_connected_method=self.test_hid_device_created_speed)
361        if duration is not None:
362            self.test_hid_device_reconnect_time(duration, device_type)
363