Skip to content

Commit 430c5b9

Browse files
authored
Merge pull request #14 from zxzxwu/release
2025.10 Release
2 parents ea171df + e0b591a commit 430c5b9

45 files changed

Lines changed: 2045 additions & 522 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

navi/bumble_ext/a2dp.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
from bumble import a2dp
3030
from bumble import avdtp
31-
from bumble import avrcp
3231
from bumble import codecs
3332
from bumble import device as bumble_device
3433

@@ -443,33 +442,3 @@ def _(server: avdtp.Protocol) -> None:
443442
),
444443
})
445444
return listener
446-
447-
448-
def setup_avrcp_server(
449-
device: bumble_device.Device,
450-
avrcp_controller_handle: int,
451-
avrcp_target_handle: int,
452-
delegate: avrcp.Delegate | None = None,
453-
) -> avrcp.Protocol:
454-
"""Sets up the AVRCP server on the device.
455-
456-
Args:
457-
device: The device to set up the AVRCP server on.
458-
avrcp_controller_handle: The handle of the AVRCP service record.
459-
avrcp_target_handle: The handle of the AVRCP target service record.
460-
delegate: The delegate to handle AVRCP events.
461-
462-
Returns:
463-
The AVRCP protocol.
464-
"""
465-
avrcp_protocol = avrcp.Protocol(delegate)
466-
avrcp_protocol.listen(device)
467-
device.sdp_service_records.update({
468-
avrcp_controller_handle: avrcp.make_controller_service_sdp_records(
469-
avrcp_controller_handle
470-
),
471-
avrcp_target_handle: avrcp.make_target_service_sdp_records(
472-
avrcp_target_handle
473-
),
474-
})
475-
return avrcp_protocol

navi/bumble_ext/avrcp.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Custom AVRCP Bumble implementation."""
16+
17+
from bumble import avrcp
18+
from bumble import device as bumble_device
19+
20+
21+
def setup_server(
22+
device: bumble_device.Device,
23+
avrcp_controller_handle: int,
24+
avrcp_target_handle: int,
25+
delegate: avrcp.Delegate | None = None,
26+
avrcp_controller_features: int = 0x01,
27+
avrcp_target_features: int = 0x23,
28+
) -> avrcp.Protocol:
29+
"""Sets up the AVRCP server on the device.
30+
31+
Args:
32+
device: The device to set up the AVRCP server on.
33+
avrcp_controller_handle: The handle of the AVRCP service record.
34+
avrcp_target_handle: The handle of the AVRCP target service record.
35+
delegate: The delegate to handle AVRCP events.
36+
avrcp_controller_features: The features of the AVRCP controller.
37+
avrcp_target_features: The features of the AVRCP target.
38+
39+
Returns:
40+
The AVRCP protocol.
41+
"""
42+
avrcp_protocol = avrcp.Protocol(delegate)
43+
avrcp_protocol.listen(device)
44+
device.sdp_service_records.update({
45+
avrcp_controller_handle: avrcp.make_controller_service_sdp_records(
46+
avrcp_controller_handle, supported_features=avrcp_controller_features
47+
),
48+
avrcp_target_handle: avrcp.make_target_service_sdp_records(
49+
avrcp_target_handle, supported_features=avrcp_target_features
50+
),
51+
})
52+
return avrcp_protocol

navi/bumble_ext/crown.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import asyncio
2020
from collections.abc import Callable
21+
import contextlib
2122
import io
2223
import subprocess
2324
import sys
@@ -34,6 +35,7 @@
3435
import bumble.transport.common
3536
import grpc.aio
3637
from mobly.controllers import android_device
38+
from mobly.controllers.android_device_lib import adb
3739
from typing_extensions import override
3840

3941
from navi.utils import resources
@@ -225,6 +227,9 @@ def __init__(self, device: android_device.AndroidDevice) -> None:
225227
# Sync time.
226228
adb_snippets.sync_time(self.ad)
227229

230+
# Enable BT Snoop.
231+
adb_snippets.enable_btsnoop(self.ad)
232+
228233
# Enable Satellite Mode to avoid turning on Bluetooth.
229234
self.ad.adb.shell(
230235
['settings', 'put', 'global', 'satellite_mode_enabled', '1']
@@ -235,6 +240,17 @@ def __init__(self, device: android_device.AndroidDevice) -> None:
235240
)
236241
self.ad.adb.shell(['cmd', 'bluetooth_manager', 'disable'])
237242
self.ad.adb.shell(['cmd', 'bluetooth_manager', 'wait-for-state:STATE_OFF'])
243+
# TODO: Remove this once the bug is fixed.
244+
if (
245+
self.ad.adb.shell(['getprop', 'persist.bluetooth.leaudio_sw_offload'])
246+
!= 'false'
247+
):
248+
self.ad.adb.shell(
249+
['setprop', 'persist.bluetooth.leaudio_sw_offload', 'false']
250+
)
251+
with contextlib.suppress(adb.Error):
252+
self.ad.adb.shell(['pkill -U bluetooth'])
253+
time.sleep(0.5)
238254

239255
# Push HCI proxy to device.
240256
abi_type = self.ad.adb.getprop('ro.product.cpu.abi')

navi/bumble_ext/hfp.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ class HfProtocol(hfp.HfProtocol):
6666
"""Customized HF Protocol."""
6767

6868
controller_supported_codecs: list[hci.CodecID] | None = None
69+
slc_initialized: asyncio.Event
6970

7071
@classmethod
7172
def setup_server(
@@ -120,12 +121,18 @@ def __init__(
120121
auto_accept_sco_request: bool = True,
121122
) -> None:
122123
self.auto_accept_sco_request = auto_accept_sco_request
124+
self.slc_initialized = asyncio.Event()
123125
if auto_accept_sco_request:
124126
device = dlc.multiplexer.l2cap_channel.connection.device
125127
device.on(device.EVENT_SCO_REQUEST, self._on_sco_request)
126128
dlc.once(dlc.EVENT_CLOSE, self._on_disconnection)
127129
super().__init__(dlc=dlc, configuration=configuration)
128130

131+
@override
132+
async def initiate_slc(self) -> None:
133+
await super().initiate_slc()
134+
self.slc_initialized.set()
135+
129136
@override
130137
async def setup_codec_connection(self, codec_id: int) -> None:
131138
self.active_codec = hfp.AudioCodec(codec_id)

navi/bumble_ext/hid.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,90 @@
3535
Message: TypeAlias = hid.Message
3636

3737

38+
DEFAULT_REPORT_MAP = bytes([
39+
# fmt: off
40+
# pylint: disable=line-too-long
41+
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
42+
0x09, 0x06, # Usage (Keyboard)
43+
0xA1, 0x01, # Collection (Application)
44+
0x85, 0x01, # Report ID (1)
45+
0x05, 0x07, # Usage Page (Kbrd/Keypad)
46+
0x19, 0xE0, # Usage Minimum (0xE0)
47+
0x29, 0xE7, # Usage Maximum (0xE7)
48+
0x15, 0x00, # Logical Minimum (0)
49+
0x25, 0x01, # Logical Maximum (1)
50+
0x75, 0x01, # Report Size (1)
51+
0x95, 0x08, # Report Count (8)
52+
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
53+
0x95, 0x01, # Report Count (1)
54+
0x75, 0x08, # Report Size (8)
55+
0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
56+
0x95, 0x06, # Report Count (6)
57+
0x75, 0x08, # Report Size (8)
58+
0x15, 0x00, # Logical Minimum (0)
59+
0x25, 0x94, # Logical Maximum (-108)
60+
0x05, 0x07, # Usage Page (Kbrd/Keypad)
61+
0x19, 0x00, # Usage Minimum (0x00)
62+
0x29, 0x94, # Usage Maximum (0x94)
63+
0x81, 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
64+
0x95, 0x05, # Report Count (5)
65+
0x75, 0x01, # Report Size (1)
66+
0x05, 0x08, # Usage Page (LEDs)
67+
0x19, 0x01, # Usage Minimum (Num Lock)
68+
0x29, 0x05, # Usage Maximum (Kana)
69+
0x91, 0x02, # Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
70+
0x95, 0x01, # Report Count (1)
71+
0x75, 0x03, # Report Size (3)
72+
0x91, 0x01, # Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
73+
0xC0, # End Collection
74+
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
75+
0x09, 0x02, # Usage (Mouse)
76+
0xA1, 0x01, # Collection (Application)
77+
0x85, 0x02, # Report ID (2)
78+
0x09, 0x01, # Usage (Pointer)
79+
0xA1, 0x00, # Collection (Physical)
80+
0x05, 0x09, # Usage Page (Button)
81+
0x19, 0x01, # Usage Minimum (0x01)
82+
0x29, 0x05, # Usage Maximum (0x05)
83+
0x15, 0x00, # Logical Minimum (0)
84+
0x25, 0x01, # Logical Maximum (1)
85+
0x95, 0x05, # Report Count (5)
86+
0x75, 0x01, # Report Size (1)
87+
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
88+
0x95, 0x01, # Report Count (1)
89+
0x75, 0x03, # Report Size (3)
90+
0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
91+
0x05, 0x01, # Usage Page (Generic Desktop Ctrls)
92+
0x09, 0x30, # Usage (X)
93+
0x09, 0x31, # Usage (Y)
94+
0x16, 0x00, 0x80, # Logical Minimum (-32768)
95+
0x26, 0xFF, 0x7F, # Logical Maximum (32767)
96+
0x75, 0x10, # Report Size (16)
97+
0x95, 0x02, # Report Count (2)
98+
0x81, 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
99+
0x09, 0x38, # Usage (Wheel)
100+
0x15, 0x81, # Logical Minimum (-127)
101+
0x25, 0x7F, # Logical Maximum (127)
102+
0x75, 0x08, # Report Size (8)
103+
0x95, 0x01, # Report Count (1)
104+
0x81, 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
105+
0xC0, # End Collection
106+
0xC0, # End Collection
107+
])
108+
PROPERTY_HID_HOST_SUPPORTED = "bluetooth.profile.hid.host.enabled"
109+
110+
111+
class ReportProtocol(enum.IntEnum):
112+
BOOT = 0x00
113+
REPORT = 0x01
114+
115+
116+
class ReportType(enum.IntEnum):
117+
INPUT = 0x01
118+
OUTPUT = 0x02
119+
FEATURE = 0x03
120+
121+
38122
class AttributeId(enum.IntEnum):
39123
"""SDP attribute IDs."""
40124

64 KB
Binary file not shown.

navi/tests/benchmark/a2dp_test.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from typing_extensions import override
2525

2626
from navi.bumble_ext import a2dp as a2dp_ext
27+
from navi.bumble_ext import avrcp as avrcp_ext
2728
from navi.tests import navi_test_base
2829
from navi.tests.benchmark import performance_tool
2930
from navi.tests.benchmark import test_base
@@ -110,9 +111,9 @@ def _setup_a2dp_device(
110111
_A2DP_SERVICE_RECORD_HANDLE,
111112
)
112113
avrcp_delegator = AvrcpDelegate(
113-
supported_events=(avrcp.EventId.VOLUME_CHANGED,)
114+
supported_events=(avrcp.EventId.VOLUME_CHANGED,) # type: ignore[wrong-arg-types]
114115
)
115-
avrcp_protocol = a2dp_ext.setup_avrcp_server(
116+
avrcp_protocol = avrcp_ext.setup_server(
116117
self.ref.device,
117118
avrcp_controller_handle=_AVRCP_CONTROLLER_RECORD_HANDLE,
118119
avrcp_target_handle=_AVRCP_TARGET_RECORD_HANDLE,
@@ -246,7 +247,7 @@ async def test_connection_incoming(self, avrcp_enabled: bool) -> None:
246247
)
247248
ref_avrcp_protocol: avrcp.Protocol | None = None
248249
if avrcp_enabled:
249-
ref_avrcp_protocol = a2dp_ext.setup_avrcp_server(
250+
ref_avrcp_protocol = avrcp_ext.setup_server(
250251
self.ref.device,
251252
avrcp_controller_handle=_AVRCP_CONTROLLER_RECORD_HANDLE,
252253
avrcp_target_handle=_AVRCP_TARGET_RECORD_HANDLE,

navi/tests/benchmark/classic_gap_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
from navi.tests import navi_test_base
2828
from navi.tests.benchmark import performance_tool
2929
from navi.tests.benchmark import test_base
30-
from navi.tests.smoke import pairing_utils
3130
from navi.utils import android_constants
3231
from navi.utils import bl4a_api
3332
from navi.utils import constants
33+
from navi.utils import pairing as pairing_utils
3434

3535

3636
_TERMINATED_BOND_STATES = (

0 commit comments

Comments
 (0)