mirror of
https://github.com/google/bumble.git
synced 2026-04-17 00:35:31 +00:00
Compare commits
94 Commits
v0.0.170
...
gbg/usb-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c7089c8ff | ||
|
|
a8ec1b0949 | ||
|
|
0667e83919 | ||
|
|
46d6242171 | ||
|
|
2cd4f84800 | ||
|
|
c67ca4a09e | ||
|
|
94506220d3 | ||
|
|
dbd865a484 | ||
|
|
9d2f3e932a | ||
|
|
49d32f5b5b | ||
|
|
f7b74c0bcb | ||
|
|
c75cb0c7b7 | ||
|
|
a63b335149 | ||
|
|
d8517ce407 | ||
|
|
ad13b11464 | ||
|
|
99bc92d53d | ||
|
|
72199f5615 | ||
|
|
78b8b50082 | ||
|
|
3ab64ce00d | ||
|
|
651e44e0b6 | ||
|
|
963fa41a49 | ||
|
|
493f4f8b95 | ||
|
|
fc1bf36ace | ||
|
|
5ddee17411 | ||
|
|
5ce353bcde | ||
|
|
16d33199eb | ||
|
|
e02303a448 | ||
|
|
36fc966ad6 | ||
|
|
644f74400d | ||
|
|
b7cd451ddb | ||
|
|
59d7717963 | ||
|
|
88392efca4 | ||
|
|
907f2acc7e | ||
|
|
6616477bcf | ||
|
|
5b173cb879 | ||
|
|
dc6b466a42 | ||
|
|
8b04161da3 | ||
|
|
5a85765360 | ||
|
|
333940919b | ||
|
|
b9476be9ad | ||
|
|
704c60491c | ||
|
|
4a8e612c6e | ||
|
|
5e5c9c2580 | ||
|
|
4e71ec5738 | ||
|
|
1004f10384 | ||
|
|
1051648ffb | ||
|
|
7255a09705 | ||
|
|
c2bf6b5f13 | ||
|
|
d8e699b588 | ||
|
|
3e4d4705f5 | ||
|
|
c8b2804446 | ||
|
|
e732f2589f | ||
|
|
aec5543081 | ||
|
|
e03d90ca57 | ||
|
|
495ce62d9c | ||
|
|
fbc3959a5a | ||
|
|
246b11925c | ||
|
|
dfa9131192 | ||
|
|
88c801b4c2 | ||
|
|
a1b55b94e0 | ||
|
|
80db9e2e2f | ||
|
|
ce74690420 | ||
|
|
50de4dfb5d | ||
|
|
9bcdf860f4 | ||
|
|
511ab4b630 | ||
|
|
6f2b623e3c | ||
|
|
fa12165cd3 | ||
|
|
c0c6f3329d | ||
|
|
406a932467 | ||
|
|
cc96d4245f | ||
|
|
c6cdca8923 | ||
|
|
45edcafb06 | ||
|
|
9f0bcc131f | ||
|
|
7e331c2944 | ||
|
|
10347765cb | ||
|
|
c12dee4e76 | ||
|
|
772c188674 | ||
|
|
7c1a3bb8f9 | ||
|
|
8c3c0b1e13 | ||
|
|
1ad84ad51c | ||
|
|
64937c3f77 | ||
|
|
50fd2218fa | ||
|
|
4c29a16271 | ||
|
|
762d3e92de | ||
|
|
2f97531d78 | ||
|
|
f6c7cae661 | ||
|
|
f1777a5bd2 | ||
|
|
78a06ae8cf | ||
|
|
d290df4aa9 | ||
|
|
e559744f32 | ||
|
|
67418e649a | ||
|
|
5adf9fab53 | ||
|
|
6ec6f1efe5 | ||
|
|
5d9598ea51 |
43
.github/workflows/python-avatar.yml
vendored
Normal file
43
.github/workflows/python-avatar.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Python Avatar
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Avatar [${{ matrix.shard }}]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
shard: [
|
||||
1/24, 2/24, 3/24, 4/24,
|
||||
5/24, 6/24, 7/24, 8/24,
|
||||
9/24, 10/24, 11/24, 12/24,
|
||||
13/24, 14/24, 15/24, 16/24,
|
||||
17/24, 18/24, 19/24, 20/24,
|
||||
21/24, 22/24, 23/24, 24/24,
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set Up Python 3.11
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.11
|
||||
- name: Install
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install .[avatar]
|
||||
- name: Rootcanal
|
||||
run: nohup python -m rootcanal > rootcanal.log &
|
||||
- name: Test
|
||||
run: |
|
||||
avatar --list | grep -Ev '^=' > test-names.txt
|
||||
timeout 5m avatar --test-beds bumble.bumbles --tests $(split test-names.txt -n l/${{ matrix.shard }})
|
||||
- name: Rootcanal Logs
|
||||
run: cat rootcanal.log
|
||||
10
.github/workflows/python-build-test.yml
vendored
10
.github/workflows/python-build-test.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install ".[build,test,development,documentation]"
|
||||
@@ -65,13 +65,17 @@ jobs:
|
||||
with:
|
||||
components: clippy,rustfmt
|
||||
toolchain: ${{ matrix.rust-version }}
|
||||
- name: Install Rust dependencies
|
||||
run: cargo install cargo-all-features # allows building/testing combinations of features
|
||||
- name: Check License Headers
|
||||
run: cd rust && cargo run --features dev-tools --bin file-header check-all
|
||||
- name: Rust Build
|
||||
run: cd rust && cargo build --all-targets && cargo build --all-features --all-targets
|
||||
run: cd rust && cargo build --all-targets && cargo build-all-features --all-targets
|
||||
# Lints after build so what clippy needs is already built
|
||||
- name: Rust Lints
|
||||
run: cd rust && cargo fmt --check && cargo clippy --all-targets -- --deny warnings && cargo clippy --all-features --all-targets -- --deny warnings
|
||||
- name: Rust Tests
|
||||
run: cd rust && cargo test
|
||||
run: cd rust && cargo test-all-features
|
||||
# At some point, hook up publishing the binary. For now, just make sure it builds.
|
||||
# Once we're ready to publish binaries, this should be built with `--release`.
|
||||
- name: Build Bumble CLI
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -39,12 +39,15 @@
|
||||
"libusb",
|
||||
"MITM",
|
||||
"NDIS",
|
||||
"netsim",
|
||||
"NONBLOCK",
|
||||
"NONCONN",
|
||||
"OXIMETER",
|
||||
"popleft",
|
||||
"protobuf",
|
||||
"psms",
|
||||
"pyee",
|
||||
"Pyodide",
|
||||
"pyusb",
|
||||
"rfcomm",
|
||||
"ROHC",
|
||||
|
||||
@@ -24,6 +24,7 @@ import time
|
||||
|
||||
import click
|
||||
|
||||
from bumble import l2cap
|
||||
from bumble.core import (
|
||||
BT_BR_EDR_TRANSPORT,
|
||||
BT_LE_TRANSPORT,
|
||||
@@ -85,6 +86,7 @@ DEFAULT_LINGER_TIME = 1.0
|
||||
|
||||
DEFAULT_RFCOMM_CHANNEL = 8
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Utils
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -197,6 +199,7 @@ class PacketType(enum.IntEnum):
|
||||
|
||||
PACKET_FLAG_LAST = 1
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Sender
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -659,17 +662,19 @@ class L2capClient(StreamedPacketIO):
|
||||
self.mps = mps
|
||||
self.ready = asyncio.Event()
|
||||
|
||||
async def on_connection(self, connection):
|
||||
async def on_connection(self, connection: Connection) -> None:
|
||||
connection.on('disconnection', self.on_disconnection)
|
||||
|
||||
# Connect a new L2CAP channel
|
||||
print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
|
||||
try:
|
||||
l2cap_channel = await connection.open_l2cap_channel(
|
||||
psm=self.psm,
|
||||
max_credits=self.max_credits,
|
||||
mtu=self.mtu,
|
||||
mps=self.mps,
|
||||
l2cap_channel = await connection.create_l2cap_channel(
|
||||
spec=l2cap.LeCreditBasedChannelSpec(
|
||||
psm=self.psm,
|
||||
max_credits=self.max_credits,
|
||||
mtu=self.mtu,
|
||||
mps=self.mps,
|
||||
)
|
||||
)
|
||||
print(color('*** L2CAP channel:', 'cyan'), l2cap_channel)
|
||||
except Exception as error:
|
||||
@@ -695,7 +700,7 @@ class L2capClient(StreamedPacketIO):
|
||||
class L2capServer(StreamedPacketIO):
|
||||
def __init__(
|
||||
self,
|
||||
device,
|
||||
device: Device,
|
||||
psm=DEFAULT_L2CAP_PSM,
|
||||
max_credits=DEFAULT_L2CAP_MAX_CREDITS,
|
||||
mtu=DEFAULT_L2CAP_MTU,
|
||||
@@ -706,12 +711,11 @@ class L2capServer(StreamedPacketIO):
|
||||
self.ready = asyncio.Event()
|
||||
|
||||
# Listen for incoming L2CAP CoC connections
|
||||
device.register_l2cap_channel_server(
|
||||
psm=psm,
|
||||
server=self.on_l2cap_channel,
|
||||
max_credits=max_credits,
|
||||
mtu=mtu,
|
||||
mps=mps,
|
||||
device.create_l2cap_server(
|
||||
spec=l2cap.LeCreditBasedChannelSpec(
|
||||
psm=psm, mtu=mtu, mps=mps, max_credits=max_credits
|
||||
),
|
||||
handler=self.on_l2cap_channel,
|
||||
)
|
||||
print(color(f'### Listening for CoC connection on PSM {psm}', 'yellow'))
|
||||
|
||||
|
||||
@@ -1172,7 +1172,7 @@ class ScanResult:
|
||||
name = ''
|
||||
|
||||
# Remove any '/P' qualifier suffix from the address string
|
||||
address_str = str(self.address).replace('/P', '')
|
||||
address_str = self.address.to_string(with_type_qualifier=False)
|
||||
|
||||
# RSSI bar
|
||||
bar_string = rssi_bar(self.rssi)
|
||||
|
||||
@@ -63,7 +63,8 @@ async def get_classic_info(host):
|
||||
if command_succeeded(response):
|
||||
print()
|
||||
print(
|
||||
color('Classic Address:', 'yellow'), response.return_parameters.bd_addr
|
||||
color('Classic Address:', 'yellow'),
|
||||
response.return_parameters.bd_addr.to_string(False),
|
||||
)
|
||||
|
||||
if host.supports_command(HCI_READ_LOCAL_NAME_COMMAND):
|
||||
|
||||
@@ -21,6 +21,7 @@ import struct
|
||||
import logging
|
||||
import click
|
||||
|
||||
from bumble import l2cap
|
||||
from bumble.colors import color
|
||||
from bumble.device import Device, Peer
|
||||
from bumble.core import AdvertisingData
|
||||
@@ -204,7 +205,7 @@ class GattlinkHubBridge(GattlinkL2capEndpoint, Device.Listener):
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener):
|
||||
def __init__(self, device):
|
||||
def __init__(self, device: Device):
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.peer = None
|
||||
@@ -218,7 +219,12 @@ class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener):
|
||||
|
||||
# Listen for incoming L2CAP CoC connections
|
||||
psm = 0xFB
|
||||
device.register_l2cap_channel_server(0xFB, self.on_coc)
|
||||
device.create_l2cap_server(
|
||||
spec=l2cap.LeCreditBasedChannelSpec(
|
||||
psm=0xFB,
|
||||
),
|
||||
handler=self.on_coc,
|
||||
)
|
||||
print(f'### Listening for CoC connection on PSM {psm}')
|
||||
|
||||
# Setup the Gattlink service
|
||||
|
||||
@@ -20,6 +20,7 @@ import logging
|
||||
import os
|
||||
import click
|
||||
|
||||
from bumble import l2cap
|
||||
from bumble.colors import color
|
||||
from bumble.transport import open_transport_or_link
|
||||
from bumble.device import Device
|
||||
@@ -47,14 +48,13 @@ class ServerBridge:
|
||||
self.tcp_host = tcp_host
|
||||
self.tcp_port = tcp_port
|
||||
|
||||
async def start(self, device):
|
||||
async def start(self, device: Device) -> None:
|
||||
# Listen for incoming L2CAP CoC connections
|
||||
device.register_l2cap_channel_server(
|
||||
psm=self.psm,
|
||||
server=self.on_coc,
|
||||
max_credits=self.max_credits,
|
||||
mtu=self.mtu,
|
||||
mps=self.mps,
|
||||
device.create_l2cap_server(
|
||||
spec=l2cap.LeCreditBasedChannelSpec(
|
||||
psm=self.psm, mtu=self.mtu, mps=self.mps, max_credits=self.max_credits
|
||||
),
|
||||
handler=self.on_coc,
|
||||
)
|
||||
print(color(f'### Listening for CoC connection on PSM {self.psm}', 'yellow'))
|
||||
|
||||
@@ -195,11 +195,13 @@ class ClientBridge:
|
||||
# Connect a new L2CAP channel
|
||||
print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
|
||||
try:
|
||||
l2cap_channel = await connection.open_l2cap_channel(
|
||||
psm=self.psm,
|
||||
max_credits=self.max_credits,
|
||||
mtu=self.mtu,
|
||||
mps=self.mps,
|
||||
l2cap_channel = await connection.create_l2cap_channel(
|
||||
spec=l2cap.LeCreditBasedChannelSpec(
|
||||
psm=self.psm,
|
||||
max_credits=self.max_credits,
|
||||
mtu=self.mtu,
|
||||
mps=self.mps,
|
||||
)
|
||||
)
|
||||
print(color('*** L2CAP channel:', 'cyan'), l2cap_channel)
|
||||
except Exception as error:
|
||||
|
||||
@@ -306,6 +306,7 @@ async def pair(
|
||||
# Expose a GATT characteristic that can be used to trigger pairing by
|
||||
# responding with an authentication error when read
|
||||
if mode == 'le':
|
||||
device.le_enabled = True
|
||||
device.add_service(
|
||||
Service(
|
||||
'50DB505C-8AC4-4738-8448-3B1D9CC09CC5',
|
||||
@@ -326,7 +327,6 @@ async def pair(
|
||||
# Select LE or Classic
|
||||
if mode == 'classic':
|
||||
device.classic_enabled = True
|
||||
device.le_enabled = False
|
||||
device.classic_smp_enabled = ctkd
|
||||
|
||||
# Get things going
|
||||
|
||||
@@ -195,7 +195,7 @@ class WebSocketOutput(QueuedOutput):
|
||||
except HCI_StatusError:
|
||||
pass
|
||||
peer_name = '' if connection.peer_name is None else connection.peer_name
|
||||
peer_address = str(connection.peer_address).replace('/P', '')
|
||||
peer_address = connection.peer_address.to_string(False)
|
||||
await self.send_message(
|
||||
'connection',
|
||||
peer_address=peer_address,
|
||||
@@ -376,7 +376,7 @@ class UiServer:
|
||||
if connection := self.speaker().connection:
|
||||
await self.send_message(
|
||||
'connection',
|
||||
peer_address=str(connection.peer_address).replace('/P', ''),
|
||||
peer_address=connection.peer_address.to_string(False),
|
||||
peer_name=connection.peer_name,
|
||||
)
|
||||
|
||||
@@ -641,7 +641,7 @@ class Speaker:
|
||||
self.device.on('connection', self.on_bluetooth_connection)
|
||||
|
||||
# Create a listener to wait for AVDTP connections
|
||||
self.listener = Listener(Listener.create_registrar(self.device))
|
||||
self.listener = Listener.for_device(self.device)
|
||||
self.listener.on('connection', self.on_avdtp_connection)
|
||||
|
||||
print(f'Speaker ready to play, codec={color(self.codec, "cyan")}')
|
||||
|
||||
124
bumble/att.py
124
bumble/att.py
@@ -23,13 +23,14 @@
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import enum
|
||||
import functools
|
||||
import struct
|
||||
from pyee import EventEmitter
|
||||
from typing import Dict, Type, TYPE_CHECKING
|
||||
from typing import Dict, Type, List, Protocol, Union, Optional, Any, TYPE_CHECKING
|
||||
|
||||
from bumble.core import UUID, name_or_number, get_dict_key_by_value, ProtocolError
|
||||
from bumble.hci import HCI_Object, key_with_value, HCI_Constant
|
||||
from bumble.core import UUID, name_or_number, ProtocolError
|
||||
from bumble.hci import HCI_Object, key_with_value
|
||||
from bumble.colors import color
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -182,6 +183,7 @@ UUID_2_FIELD_SPEC = lambda x, y: UUID.parse_uuid_2(x, y) # noqa: E731
|
||||
# pylint: enable=line-too-long
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Exceptions
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -209,7 +211,7 @@ class ATT_PDU:
|
||||
|
||||
pdu_classes: Dict[int, Type[ATT_PDU]] = {}
|
||||
op_code = 0
|
||||
name = None
|
||||
name: str
|
||||
|
||||
@staticmethod
|
||||
def from_bytes(pdu):
|
||||
@@ -719,48 +721,68 @@ class ATT_Handle_Value_Confirmation(ATT_PDU):
|
||||
'''
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class ConnectionValue(Protocol):
|
||||
def read(self, connection) -> bytes:
|
||||
...
|
||||
|
||||
def write(self, connection, value: bytes) -> None:
|
||||
...
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class Attribute(EventEmitter):
|
||||
# Permission flags
|
||||
READABLE = 0x01
|
||||
WRITEABLE = 0x02
|
||||
READ_REQUIRES_ENCRYPTION = 0x04
|
||||
WRITE_REQUIRES_ENCRYPTION = 0x08
|
||||
READ_REQUIRES_AUTHENTICATION = 0x10
|
||||
WRITE_REQUIRES_AUTHENTICATION = 0x20
|
||||
READ_REQUIRES_AUTHORIZATION = 0x40
|
||||
WRITE_REQUIRES_AUTHORIZATION = 0x80
|
||||
class Permissions(enum.IntFlag):
|
||||
READABLE = 0x01
|
||||
WRITEABLE = 0x02
|
||||
READ_REQUIRES_ENCRYPTION = 0x04
|
||||
WRITE_REQUIRES_ENCRYPTION = 0x08
|
||||
READ_REQUIRES_AUTHENTICATION = 0x10
|
||||
WRITE_REQUIRES_AUTHENTICATION = 0x20
|
||||
READ_REQUIRES_AUTHORIZATION = 0x40
|
||||
WRITE_REQUIRES_AUTHORIZATION = 0x80
|
||||
|
||||
PERMISSION_NAMES = {
|
||||
READABLE: 'READABLE',
|
||||
WRITEABLE: 'WRITEABLE',
|
||||
READ_REQUIRES_ENCRYPTION: 'READ_REQUIRES_ENCRYPTION',
|
||||
WRITE_REQUIRES_ENCRYPTION: 'WRITE_REQUIRES_ENCRYPTION',
|
||||
READ_REQUIRES_AUTHENTICATION: 'READ_REQUIRES_AUTHENTICATION',
|
||||
WRITE_REQUIRES_AUTHENTICATION: 'WRITE_REQUIRES_AUTHENTICATION',
|
||||
READ_REQUIRES_AUTHORIZATION: 'READ_REQUIRES_AUTHORIZATION',
|
||||
WRITE_REQUIRES_AUTHORIZATION: 'WRITE_REQUIRES_AUTHORIZATION',
|
||||
}
|
||||
@classmethod
|
||||
def from_string(cls, permissions_str: str) -> Attribute.Permissions:
|
||||
try:
|
||||
return functools.reduce(
|
||||
lambda x, y: x | Attribute.Permissions[y],
|
||||
permissions_str.replace('|', ',').split(","),
|
||||
Attribute.Permissions(0),
|
||||
)
|
||||
except TypeError as exc:
|
||||
# The check for `p.name is not None` here is needed because for InFlag
|
||||
# enums, the .name property can be None, when the enum value is 0,
|
||||
# so the type hint for .name is Optional[str].
|
||||
enum_list: List[str] = [p.name for p in cls if p.name is not None]
|
||||
enum_list_str = ",".join(enum_list)
|
||||
raise TypeError(
|
||||
f"Attribute::permissions error:\nExpected a string containing any of the keys, separated by commas: {enum_list_str }\nGot: {permissions_str}"
|
||||
) from exc
|
||||
|
||||
@staticmethod
|
||||
def string_to_permissions(permissions_str: str):
|
||||
try:
|
||||
return functools.reduce(
|
||||
lambda x, y: x | get_dict_key_by_value(Attribute.PERMISSION_NAMES, y),
|
||||
permissions_str.split(","),
|
||||
0,
|
||||
)
|
||||
except TypeError as exc:
|
||||
raise TypeError(
|
||||
f"Attribute::permissions error:\nExpected a string containing any of the keys, separated by commas: {','.join(Attribute.PERMISSION_NAMES.values())}\nGot: {permissions_str}"
|
||||
) from exc
|
||||
# Permission flags(legacy-use only)
|
||||
READABLE = Permissions.READABLE
|
||||
WRITEABLE = Permissions.WRITEABLE
|
||||
READ_REQUIRES_ENCRYPTION = Permissions.READ_REQUIRES_ENCRYPTION
|
||||
WRITE_REQUIRES_ENCRYPTION = Permissions.WRITE_REQUIRES_ENCRYPTION
|
||||
READ_REQUIRES_AUTHENTICATION = Permissions.READ_REQUIRES_AUTHENTICATION
|
||||
WRITE_REQUIRES_AUTHENTICATION = Permissions.WRITE_REQUIRES_AUTHENTICATION
|
||||
READ_REQUIRES_AUTHORIZATION = Permissions.READ_REQUIRES_AUTHORIZATION
|
||||
WRITE_REQUIRES_AUTHORIZATION = Permissions.WRITE_REQUIRES_AUTHORIZATION
|
||||
|
||||
def __init__(self, attribute_type, permissions, value=b''):
|
||||
value: Union[str, bytes, ConnectionValue]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
attribute_type: Union[str, bytes, UUID],
|
||||
permissions: Union[str, Attribute.Permissions],
|
||||
value: Union[str, bytes, ConnectionValue] = b'',
|
||||
) -> None:
|
||||
EventEmitter.__init__(self)
|
||||
self.handle = 0
|
||||
self.end_group_handle = 0
|
||||
if isinstance(permissions, str):
|
||||
self.permissions = self.string_to_permissions(permissions)
|
||||
self.permissions = Attribute.Permissions.from_string(permissions)
|
||||
else:
|
||||
self.permissions = permissions
|
||||
|
||||
@@ -778,22 +800,26 @@ class Attribute(EventEmitter):
|
||||
else:
|
||||
self.value = value
|
||||
|
||||
def encode_value(self, value):
|
||||
def encode_value(self, value: Any) -> bytes:
|
||||
return value
|
||||
|
||||
def decode_value(self, value_bytes):
|
||||
def decode_value(self, value_bytes: bytes) -> Any:
|
||||
return value_bytes
|
||||
|
||||
def read_value(self, connection: Connection):
|
||||
def read_value(self, connection: Optional[Connection]) -> bytes:
|
||||
if (
|
||||
self.permissions & self.READ_REQUIRES_ENCRYPTION
|
||||
) and not connection.encryption:
|
||||
(self.permissions & self.READ_REQUIRES_ENCRYPTION)
|
||||
and connection is not None
|
||||
and not connection.encryption
|
||||
):
|
||||
raise ATT_Error(
|
||||
error_code=ATT_INSUFFICIENT_ENCRYPTION_ERROR, att_handle=self.handle
|
||||
)
|
||||
if (
|
||||
self.permissions & self.READ_REQUIRES_AUTHENTICATION
|
||||
) and not connection.authenticated:
|
||||
(self.permissions & self.READ_REQUIRES_AUTHENTICATION)
|
||||
and connection is not None
|
||||
and not connection.authenticated
|
||||
):
|
||||
raise ATT_Error(
|
||||
error_code=ATT_INSUFFICIENT_AUTHENTICATION_ERROR, att_handle=self.handle
|
||||
)
|
||||
@@ -803,9 +829,9 @@ class Attribute(EventEmitter):
|
||||
error_code=ATT_INSUFFICIENT_AUTHORIZATION_ERROR, att_handle=self.handle
|
||||
)
|
||||
|
||||
if read := getattr(self.value, 'read', None):
|
||||
if hasattr(self.value, 'read'):
|
||||
try:
|
||||
value = read(connection) # pylint: disable=not-callable
|
||||
value = self.value.read(connection)
|
||||
except ATT_Error as error:
|
||||
raise ATT_Error(
|
||||
error_code=error.error_code, att_handle=self.handle
|
||||
@@ -815,7 +841,7 @@ class Attribute(EventEmitter):
|
||||
|
||||
return self.encode_value(value)
|
||||
|
||||
def write_value(self, connection: Connection, value_bytes):
|
||||
def write_value(self, connection: Connection, value_bytes: bytes) -> None:
|
||||
if (
|
||||
self.permissions & self.WRITE_REQUIRES_ENCRYPTION
|
||||
) and not connection.encryption:
|
||||
@@ -836,9 +862,9 @@ class Attribute(EventEmitter):
|
||||
|
||||
value = self.decode_value(value_bytes)
|
||||
|
||||
if write := getattr(self.value, 'write', None):
|
||||
if hasattr(self.value, 'write'):
|
||||
try:
|
||||
write(connection, value) # pylint: disable=not-callable
|
||||
self.value.write(connection, value) # pylint: disable=not-callable
|
||||
except ATT_Error as error:
|
||||
raise ATT_Error(
|
||||
error_code=error.error_code, att_handle=self.handle
|
||||
|
||||
480
bumble/avdtp.py
480
bumble/avdtp.py
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1000,6 +1000,9 @@ class Controller:
|
||||
'''
|
||||
See Bluetooth spec Vol 4, Part E - 7.8.10 LE Set Scan Parameters Command
|
||||
'''
|
||||
if self.le_scan_enable:
|
||||
return bytes([HCI_COMMAND_DISALLOWED_ERROR])
|
||||
|
||||
self.le_scan_type = command.le_scan_type
|
||||
self.le_scan_interval = command.le_scan_interval
|
||||
self.le_scan_window = command.le_scan_window
|
||||
|
||||
125
bumble/device.py
125
bumble/device.py
@@ -33,6 +33,8 @@ from typing import (
|
||||
Tuple,
|
||||
Type,
|
||||
Union,
|
||||
cast,
|
||||
overload,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
@@ -151,6 +153,7 @@ from .utils import (
|
||||
CompositeEventEmitter,
|
||||
setup_event_forwarding,
|
||||
composite_listener,
|
||||
deprecated,
|
||||
)
|
||||
from .keys import (
|
||||
KeyStore,
|
||||
@@ -670,9 +673,7 @@ class Connection(CompositeEventEmitter):
|
||||
def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None:
|
||||
self.device.send_l2cap_pdu(self.handle, cid, pdu)
|
||||
|
||||
def create_l2cap_connector(self, psm):
|
||||
return self.device.create_l2cap_connector(self, psm)
|
||||
|
||||
@deprecated("Please use create_l2cap_channel()")
|
||||
async def open_l2cap_channel(
|
||||
self,
|
||||
psm,
|
||||
@@ -682,6 +683,23 @@ class Connection(CompositeEventEmitter):
|
||||
):
|
||||
return await self.device.open_l2cap_channel(self, psm, max_credits, mtu, mps)
|
||||
|
||||
@overload
|
||||
async def create_l2cap_channel(
|
||||
self, spec: l2cap.ClassicChannelSpec
|
||||
) -> l2cap.ClassicChannel:
|
||||
...
|
||||
|
||||
@overload
|
||||
async def create_l2cap_channel(
|
||||
self, spec: l2cap.LeCreditBasedChannelSpec
|
||||
) -> l2cap.LeCreditBasedChannel:
|
||||
...
|
||||
|
||||
async def create_l2cap_channel(
|
||||
self, spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec]
|
||||
) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]:
|
||||
return await self.device.create_l2cap_channel(connection=self, spec=spec)
|
||||
|
||||
async def disconnect(
|
||||
self, reason: int = HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
|
||||
) -> None:
|
||||
@@ -829,6 +847,9 @@ class DeviceConfiguration:
|
||||
self.connectable = config.get('connectable', self.connectable)
|
||||
self.discoverable = config.get('discoverable', self.discoverable)
|
||||
self.gatt_services = config.get('gatt_services', self.gatt_services)
|
||||
self.address_resolution_offload = config.get(
|
||||
'address_resolution_offload', self.address_resolution_offload
|
||||
)
|
||||
|
||||
# Load or synthesize an IRK
|
||||
irk = config.get('irk')
|
||||
@@ -1180,15 +1201,11 @@ class Device(CompositeEventEmitter):
|
||||
|
||||
return None
|
||||
|
||||
def create_l2cap_connector(self, connection, psm):
|
||||
return lambda: self.l2cap_channel_manager.connect(connection, psm)
|
||||
|
||||
def create_l2cap_registrar(self, psm):
|
||||
return lambda handler: self.register_l2cap_server(psm, handler)
|
||||
|
||||
def register_l2cap_server(self, psm, server):
|
||||
self.l2cap_channel_manager.register_server(psm, server)
|
||||
@deprecated("Please use create_l2cap_server()")
|
||||
def register_l2cap_server(self, psm, server) -> int:
|
||||
return self.l2cap_channel_manager.register_server(psm, server)
|
||||
|
||||
@deprecated("Please use create_l2cap_server()")
|
||||
def register_l2cap_channel_server(
|
||||
self,
|
||||
psm,
|
||||
@@ -1201,6 +1218,7 @@ class Device(CompositeEventEmitter):
|
||||
psm, server, max_credits, mtu, mps
|
||||
)
|
||||
|
||||
@deprecated("Please use create_l2cap_channel()")
|
||||
async def open_l2cap_channel(
|
||||
self,
|
||||
connection,
|
||||
@@ -1213,6 +1231,74 @@ class Device(CompositeEventEmitter):
|
||||
connection, psm, max_credits, mtu, mps
|
||||
)
|
||||
|
||||
@overload
|
||||
async def create_l2cap_channel(
|
||||
self,
|
||||
connection: Connection,
|
||||
spec: l2cap.ClassicChannelSpec,
|
||||
) -> l2cap.ClassicChannel:
|
||||
...
|
||||
|
||||
@overload
|
||||
async def create_l2cap_channel(
|
||||
self,
|
||||
connection: Connection,
|
||||
spec: l2cap.LeCreditBasedChannelSpec,
|
||||
) -> l2cap.LeCreditBasedChannel:
|
||||
...
|
||||
|
||||
async def create_l2cap_channel(
|
||||
self,
|
||||
connection: Connection,
|
||||
spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec],
|
||||
) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]:
|
||||
if isinstance(spec, l2cap.ClassicChannelSpec):
|
||||
return await self.l2cap_channel_manager.create_classic_channel(
|
||||
connection=connection, spec=spec
|
||||
)
|
||||
if isinstance(spec, l2cap.LeCreditBasedChannelSpec):
|
||||
return await self.l2cap_channel_manager.create_le_credit_based_channel(
|
||||
connection=connection, spec=spec
|
||||
)
|
||||
|
||||
@overload
|
||||
def create_l2cap_server(
|
||||
self,
|
||||
spec: l2cap.ClassicChannelSpec,
|
||||
handler: Optional[Callable[[l2cap.ClassicChannel], Any]] = None,
|
||||
) -> l2cap.ClassicChannelServer:
|
||||
...
|
||||
|
||||
@overload
|
||||
def create_l2cap_server(
|
||||
self,
|
||||
spec: l2cap.LeCreditBasedChannelSpec,
|
||||
handler: Optional[Callable[[l2cap.LeCreditBasedChannel], Any]] = None,
|
||||
) -> l2cap.LeCreditBasedChannelServer:
|
||||
...
|
||||
|
||||
def create_l2cap_server(
|
||||
self,
|
||||
spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec],
|
||||
handler: Union[
|
||||
Callable[[l2cap.ClassicChannel], Any],
|
||||
Callable[[l2cap.LeCreditBasedChannel], Any],
|
||||
None,
|
||||
] = None,
|
||||
) -> Union[l2cap.ClassicChannelServer, l2cap.LeCreditBasedChannelServer]:
|
||||
if isinstance(spec, l2cap.ClassicChannelSpec):
|
||||
return self.l2cap_channel_manager.create_classic_server(
|
||||
spec=spec,
|
||||
handler=cast(Callable[[l2cap.ClassicChannel], Any], handler),
|
||||
)
|
||||
elif isinstance(spec, l2cap.LeCreditBasedChannelSpec):
|
||||
return self.l2cap_channel_manager.create_le_credit_based_server(
|
||||
handler=cast(Callable[[l2cap.LeCreditBasedChannel], Any], handler),
|
||||
spec=spec,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f'Unexpected mode {spec}')
|
||||
|
||||
def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
|
||||
self.host.send_l2cap_pdu(connection_handle, cid, pdu)
|
||||
|
||||
@@ -1222,7 +1308,7 @@ class Device(CompositeEventEmitter):
|
||||
self.host.send_command(command, check_result), self.command_timeout
|
||||
)
|
||||
except asyncio.TimeoutError as error:
|
||||
logger.warning('!!! Command timed out')
|
||||
logger.warning(f'!!! Command {command.name} timed out')
|
||||
raise CommandTimeoutError() from error
|
||||
|
||||
async def power_on(self) -> None:
|
||||
@@ -1323,6 +1409,9 @@ class Device(CompositeEventEmitter):
|
||||
# Done
|
||||
self.powered_on = True
|
||||
|
||||
async def reset(self) -> None:
|
||||
await self.host.reset()
|
||||
|
||||
async def power_off(self) -> None:
|
||||
if self.powered_on:
|
||||
await self.host.flush()
|
||||
@@ -1425,10 +1514,10 @@ class Device(CompositeEventEmitter):
|
||||
check_result=True,
|
||||
)
|
||||
|
||||
self.advertising_own_address_type = own_address_type
|
||||
self.auto_restart_advertising = auto_restart
|
||||
self.advertising_type = advertising_type
|
||||
self.advertising_own_address_type = own_address_type
|
||||
self.advertising = True
|
||||
self.auto_restart_advertising = auto_restart
|
||||
|
||||
async def stop_advertising(self) -> None:
|
||||
# Disable advertising
|
||||
@@ -1438,9 +1527,9 @@ class Device(CompositeEventEmitter):
|
||||
check_result=True,
|
||||
)
|
||||
|
||||
self.advertising_type = None
|
||||
self.advertising_own_address_type = None
|
||||
self.advertising = False
|
||||
self.advertising_type = None
|
||||
self.auto_restart_advertising = False
|
||||
|
||||
@property
|
||||
@@ -2630,7 +2719,6 @@ class Device(CompositeEventEmitter):
|
||||
own_address_type = self.advertising_own_address_type
|
||||
|
||||
# We are no longer advertising
|
||||
self.advertising_own_address_type = None
|
||||
self.advertising = False
|
||||
|
||||
if own_address_type in (
|
||||
@@ -2687,7 +2775,6 @@ class Device(CompositeEventEmitter):
|
||||
and self.advertising
|
||||
and self.advertising_type.is_directed
|
||||
):
|
||||
self.advertising_own_address_type = None
|
||||
self.advertising = False
|
||||
|
||||
# Notify listeners
|
||||
@@ -2758,7 +2845,9 @@ class Device(CompositeEventEmitter):
|
||||
self.abort_on(
|
||||
'flush',
|
||||
self.start_advertising(
|
||||
advertising_type=self.advertising_type, auto_restart=True
|
||||
advertising_type=self.advertising_type,
|
||||
own_address_type=self.advertising_own_address_type,
|
||||
auto_restart=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import enum
|
||||
import functools
|
||||
import logging
|
||||
import struct
|
||||
from typing import Optional, Sequence, List
|
||||
from typing import Optional, Sequence, Iterable, List, Union
|
||||
|
||||
from .colors import color
|
||||
from .core import UUID, get_dict_key_by_value
|
||||
@@ -187,7 +187,7 @@ GATT_CENTRAL_ADDRESS_RESOLUTION__CHARACTERISTIC = UUID.from_16_bi
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def show_services(services):
|
||||
def show_services(services: Iterable[Service]) -> None:
|
||||
for service in services:
|
||||
print(color(str(service), 'cyan'))
|
||||
|
||||
@@ -210,11 +210,11 @@ class Service(Attribute):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
uuid,
|
||||
uuid: Union[str, UUID],
|
||||
characteristics: List[Characteristic],
|
||||
primary=True,
|
||||
included_services: List[Service] = [],
|
||||
):
|
||||
) -> None:
|
||||
# Convert the uuid to a UUID object if it isn't already
|
||||
if isinstance(uuid, str):
|
||||
uuid = UUID(uuid)
|
||||
@@ -239,7 +239,7 @@ class Service(Attribute):
|
||||
"""
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'Service(handle=0x{self.handle:04X}, '
|
||||
f'end=0x{self.end_group_handle:04X}, '
|
||||
@@ -255,9 +255,11 @@ class TemplateService(Service):
|
||||
to expose their UUID as a class property
|
||||
'''
|
||||
|
||||
UUID: Optional[UUID] = None
|
||||
UUID: UUID
|
||||
|
||||
def __init__(self, characteristics, primary=True):
|
||||
def __init__(
|
||||
self, characteristics: List[Characteristic], primary: bool = True
|
||||
) -> None:
|
||||
super().__init__(self.UUID, characteristics, primary)
|
||||
|
||||
|
||||
@@ -269,7 +271,7 @@ class IncludedServiceDeclaration(Attribute):
|
||||
|
||||
service: Service
|
||||
|
||||
def __init__(self, service):
|
||||
def __init__(self, service: Service) -> None:
|
||||
declaration_bytes = struct.pack(
|
||||
'<HH2s', service.handle, service.end_group_handle, service.uuid.to_bytes()
|
||||
)
|
||||
@@ -278,7 +280,7 @@ class IncludedServiceDeclaration(Attribute):
|
||||
)
|
||||
self.service = service
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'IncludedServiceDefinition(handle=0x{self.handle:04X}, '
|
||||
f'group_starting_handle=0x{self.service.handle:04X}, '
|
||||
@@ -326,7 +328,7 @@ class Characteristic(Attribute):
|
||||
f"Characteristic.Properties::from_string() error:\nExpected a string containing any of the keys, separated by , or |: {enum_list_str}\nGot: {properties_str}"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
# NOTE: we override this method to offer a consistent result between python
|
||||
# versions: the value returned by IntFlag.__str__() changed in version 11.
|
||||
return '|'.join(
|
||||
@@ -348,10 +350,10 @@ class Characteristic(Attribute):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
uuid,
|
||||
uuid: Union[str, bytes, UUID],
|
||||
properties: Characteristic.Properties,
|
||||
permissions,
|
||||
value=b'',
|
||||
permissions: Union[str, Attribute.Permissions],
|
||||
value: Union[str, bytes, CharacteristicValue] = b'',
|
||||
descriptors: Sequence[Descriptor] = (),
|
||||
):
|
||||
super().__init__(uuid, permissions, value)
|
||||
@@ -369,7 +371,7 @@ class Characteristic(Attribute):
|
||||
def has_properties(self, properties: Characteristic.Properties) -> bool:
|
||||
return self.properties & properties == properties
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'Characteristic(handle=0x{self.handle:04X}, '
|
||||
f'end=0x{self.end_group_handle:04X}, '
|
||||
@@ -386,7 +388,7 @@ class CharacteristicDeclaration(Attribute):
|
||||
|
||||
characteristic: Characteristic
|
||||
|
||||
def __init__(self, characteristic, value_handle):
|
||||
def __init__(self, characteristic: Characteristic, value_handle: int) -> None:
|
||||
declaration_bytes = (
|
||||
struct.pack('<BH', characteristic.properties, value_handle)
|
||||
+ characteristic.uuid.to_pdu_bytes()
|
||||
@@ -397,7 +399,7 @@ class CharacteristicDeclaration(Attribute):
|
||||
self.value_handle = value_handle
|
||||
self.characteristic = characteristic
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'CharacteristicDeclaration(handle=0x{self.handle:04X}, '
|
||||
f'value_handle=0x{self.value_handle:04X}, '
|
||||
@@ -520,7 +522,7 @@ class CharacteristicAdapter:
|
||||
|
||||
return self.wrapped_characteristic.unsubscribe(subscriber)
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
wrapped = str(self.wrapped_characteristic)
|
||||
return f'{self.__class__.__name__}({wrapped})'
|
||||
|
||||
@@ -600,10 +602,10 @@ class UTF8CharacteristicAdapter(CharacteristicAdapter):
|
||||
Adapter that converts strings to/from bytes using UTF-8 encoding
|
||||
'''
|
||||
|
||||
def encode_value(self, value):
|
||||
def encode_value(self, value: str) -> bytes:
|
||||
return value.encode('utf-8')
|
||||
|
||||
def decode_value(self, value):
|
||||
def decode_value(self, value: bytes) -> str:
|
||||
return value.decode('utf-8')
|
||||
|
||||
|
||||
@@ -613,7 +615,7 @@ class Descriptor(Attribute):
|
||||
See Vol 3, Part G - 3.3.3 Characteristic Descriptor Declarations
|
||||
'''
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'Descriptor(handle=0x{self.handle:04X}, '
|
||||
f'type={self.type}, '
|
||||
|
||||
@@ -28,7 +28,18 @@ import asyncio
|
||||
import logging
|
||||
import struct
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Dict, Tuple, Callable, Union, Any
|
||||
from typing import (
|
||||
List,
|
||||
Optional,
|
||||
Dict,
|
||||
Tuple,
|
||||
Callable,
|
||||
Union,
|
||||
Any,
|
||||
Iterable,
|
||||
Type,
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
from pyee import EventEmitter
|
||||
|
||||
@@ -66,8 +77,12 @@ from .gatt import (
|
||||
GATT_INCLUDE_ATTRIBUTE_TYPE,
|
||||
Characteristic,
|
||||
ClientCharacteristicConfigurationBits,
|
||||
TemplateService,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.device import Connection
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -78,16 +93,16 @@ logger = logging.getLogger(__name__)
|
||||
# Proxies
|
||||
# -----------------------------------------------------------------------------
|
||||
class AttributeProxy(EventEmitter):
|
||||
client: Client
|
||||
|
||||
def __init__(self, client, handle, end_group_handle, attribute_type):
|
||||
def __init__(
|
||||
self, client: Client, handle: int, end_group_handle: int, attribute_type: UUID
|
||||
) -> None:
|
||||
EventEmitter.__init__(self)
|
||||
self.client = client
|
||||
self.handle = handle
|
||||
self.end_group_handle = end_group_handle
|
||||
self.type = attribute_type
|
||||
|
||||
async def read_value(self, no_long_read=False):
|
||||
async def read_value(self, no_long_read: bool = False) -> bytes:
|
||||
return self.decode_value(
|
||||
await self.client.read_value(self.handle, no_long_read)
|
||||
)
|
||||
@@ -97,13 +112,13 @@ class AttributeProxy(EventEmitter):
|
||||
self.handle, self.encode_value(value), with_response
|
||||
)
|
||||
|
||||
def encode_value(self, value):
|
||||
def encode_value(self, value: Any) -> bytes:
|
||||
return value
|
||||
|
||||
def decode_value(self, value_bytes):
|
||||
def decode_value(self, value_bytes: bytes) -> Any:
|
||||
return value_bytes
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return f'Attribute(handle=0x{self.handle:04X}, type={self.type})'
|
||||
|
||||
|
||||
@@ -136,14 +151,14 @@ class ServiceProxy(AttributeProxy):
|
||||
def get_characteristics_by_uuid(self, uuid):
|
||||
return self.client.get_characteristics_by_uuid(uuid, self)
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return f'Service(handle=0x{self.handle:04X}, uuid={self.uuid})'
|
||||
|
||||
|
||||
class CharacteristicProxy(AttributeProxy):
|
||||
properties: Characteristic.Properties
|
||||
descriptors: List[DescriptorProxy]
|
||||
subscribers: Dict[Any, Callable]
|
||||
subscribers: Dict[Any, Callable[[bytes], Any]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -171,7 +186,9 @@ class CharacteristicProxy(AttributeProxy):
|
||||
return await self.client.discover_descriptors(self)
|
||||
|
||||
async def subscribe(
|
||||
self, subscriber: Optional[Callable] = None, prefer_notify=True
|
||||
self,
|
||||
subscriber: Optional[Callable[[bytes], Any]] = None,
|
||||
prefer_notify: bool = True,
|
||||
):
|
||||
if subscriber is not None:
|
||||
if subscriber in self.subscribers:
|
||||
@@ -195,7 +212,7 @@ class CharacteristicProxy(AttributeProxy):
|
||||
|
||||
return await self.client.unsubscribe(self, subscriber)
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'Characteristic(handle=0x{self.handle:04X}, '
|
||||
f'uuid={self.uuid}, '
|
||||
@@ -207,7 +224,7 @@ class DescriptorProxy(AttributeProxy):
|
||||
def __init__(self, client, handle, descriptor_type):
|
||||
super().__init__(client, handle, 0, descriptor_type)
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return f'Descriptor(handle=0x{self.handle:04X}, type={self.type})'
|
||||
|
||||
|
||||
@@ -216,8 +233,10 @@ class ProfileServiceProxy:
|
||||
Base class for profile-specific service proxies
|
||||
'''
|
||||
|
||||
SERVICE_CLASS: Type[TemplateService]
|
||||
|
||||
@classmethod
|
||||
def from_client(cls, client):
|
||||
def from_client(cls, client: Client) -> ProfileServiceProxy:
|
||||
return ServiceProxy.from_client(cls, client, cls.SERVICE_CLASS.UUID)
|
||||
|
||||
|
||||
@@ -227,8 +246,12 @@ class ProfileServiceProxy:
|
||||
class Client:
|
||||
services: List[ServiceProxy]
|
||||
cached_values: Dict[int, Tuple[datetime, bytes]]
|
||||
notification_subscribers: Dict[int, Callable[[bytes], Any]]
|
||||
indication_subscribers: Dict[int, Callable[[bytes], Any]]
|
||||
pending_response: Optional[asyncio.futures.Future[ATT_PDU]]
|
||||
pending_request: Optional[ATT_PDU]
|
||||
|
||||
def __init__(self, connection):
|
||||
def __init__(self, connection: Connection) -> None:
|
||||
self.connection = connection
|
||||
self.mtu_exchange_done = False
|
||||
self.request_semaphore = asyncio.Semaphore(1)
|
||||
@@ -241,16 +264,16 @@ class Client:
|
||||
self.services = []
|
||||
self.cached_values = {}
|
||||
|
||||
def send_gatt_pdu(self, pdu):
|
||||
def send_gatt_pdu(self, pdu: bytes) -> None:
|
||||
self.connection.send_l2cap_pdu(ATT_CID, pdu)
|
||||
|
||||
async def send_command(self, command):
|
||||
async def send_command(self, command: ATT_PDU) -> None:
|
||||
logger.debug(
|
||||
f'GATT Command from client: [0x{self.connection.handle:04X}] {command}'
|
||||
)
|
||||
self.send_gatt_pdu(command.to_bytes())
|
||||
|
||||
async def send_request(self, request):
|
||||
async def send_request(self, request: ATT_PDU):
|
||||
logger.debug(
|
||||
f'GATT Request from client: [0x{self.connection.handle:04X}] {request}'
|
||||
)
|
||||
@@ -279,14 +302,14 @@ class Client:
|
||||
|
||||
return response
|
||||
|
||||
def send_confirmation(self, confirmation):
|
||||
def send_confirmation(self, confirmation: ATT_Handle_Value_Confirmation) -> None:
|
||||
logger.debug(
|
||||
f'GATT Confirmation from client: [0x{self.connection.handle:04X}] '
|
||||
f'{confirmation}'
|
||||
)
|
||||
self.send_gatt_pdu(confirmation.to_bytes())
|
||||
|
||||
async def request_mtu(self, mtu):
|
||||
async def request_mtu(self, mtu: int) -> int:
|
||||
# Check the range
|
||||
if mtu < ATT_DEFAULT_MTU:
|
||||
raise ValueError(f'MTU must be >= {ATT_DEFAULT_MTU}')
|
||||
@@ -313,10 +336,12 @@ class Client:
|
||||
|
||||
return self.connection.att_mtu
|
||||
|
||||
def get_services_by_uuid(self, uuid):
|
||||
def get_services_by_uuid(self, uuid: UUID) -> List[ServiceProxy]:
|
||||
return [service for service in self.services if service.uuid == uuid]
|
||||
|
||||
def get_characteristics_by_uuid(self, uuid, service=None):
|
||||
def get_characteristics_by_uuid(
|
||||
self, uuid: UUID, service: Optional[ServiceProxy] = None
|
||||
) -> List[CharacteristicProxy]:
|
||||
services = [service] if service else self.services
|
||||
return [
|
||||
c
|
||||
@@ -363,7 +388,7 @@ class Client:
|
||||
if not already_known:
|
||||
self.services.append(service)
|
||||
|
||||
async def discover_services(self, uuids=None) -> List[ServiceProxy]:
|
||||
async def discover_services(self, uuids: Iterable[UUID] = []) -> List[ServiceProxy]:
|
||||
'''
|
||||
See Vol 3, Part G - 4.4.1 Discover All Primary Services
|
||||
'''
|
||||
@@ -435,7 +460,7 @@ class Client:
|
||||
|
||||
return services
|
||||
|
||||
async def discover_service(self, uuid):
|
||||
async def discover_service(self, uuid: Union[str, UUID]) -> List[ServiceProxy]:
|
||||
'''
|
||||
See Vol 3, Part G - 4.4.2 Discover Primary Service by Service UUID
|
||||
'''
|
||||
@@ -468,7 +493,7 @@ class Client:
|
||||
f'{HCI_Constant.error_name(response.error_code)}'
|
||||
)
|
||||
# TODO raise appropriate exception
|
||||
return
|
||||
return []
|
||||
break
|
||||
|
||||
for attribute_handle, end_group_handle in response.handles_information:
|
||||
@@ -480,7 +505,7 @@ class Client:
|
||||
logger.warning(
|
||||
f'bogus handle values: {attribute_handle} {end_group_handle}'
|
||||
)
|
||||
return
|
||||
return []
|
||||
|
||||
# Create a service proxy for this service
|
||||
service = ServiceProxy(
|
||||
@@ -721,7 +746,7 @@ class Client:
|
||||
|
||||
return descriptors
|
||||
|
||||
async def discover_attributes(self):
|
||||
async def discover_attributes(self) -> List[AttributeProxy]:
|
||||
'''
|
||||
Discover all attributes, regardless of type
|
||||
'''
|
||||
@@ -844,7 +869,9 @@ class Client:
|
||||
# No more subscribers left
|
||||
await self.write_value(cccd, b'\x00\x00', with_response=True)
|
||||
|
||||
async def read_value(self, attribute, no_long_read=False):
|
||||
async def read_value(
|
||||
self, attribute: Union[int, AttributeProxy], no_long_read: bool = False
|
||||
) -> Any:
|
||||
'''
|
||||
See Vol 3, Part G - 4.8.1 Read Characteristic Value
|
||||
|
||||
@@ -905,7 +932,9 @@ class Client:
|
||||
# Return the value as bytes
|
||||
return attribute_value
|
||||
|
||||
async def read_characteristics_by_uuid(self, uuid, service):
|
||||
async def read_characteristics_by_uuid(
|
||||
self, uuid: UUID, service: Optional[ServiceProxy]
|
||||
) -> List[bytes]:
|
||||
'''
|
||||
See Vol 3, Part G - 4.8.2 Read Using Characteristic UUID
|
||||
'''
|
||||
@@ -960,7 +989,12 @@ class Client:
|
||||
|
||||
return characteristics_values
|
||||
|
||||
async def write_value(self, attribute, value, with_response=False):
|
||||
async def write_value(
|
||||
self,
|
||||
attribute: Union[int, AttributeProxy],
|
||||
value: bytes,
|
||||
with_response: bool = False,
|
||||
) -> None:
|
||||
'''
|
||||
See Vol 3, Part G - 4.9.1 Write Without Response & 4.9.3 Write Characteristic
|
||||
Value
|
||||
@@ -990,7 +1024,7 @@ class Client:
|
||||
)
|
||||
)
|
||||
|
||||
def on_gatt_pdu(self, att_pdu):
|
||||
def on_gatt_pdu(self, att_pdu: ATT_PDU) -> None:
|
||||
logger.debug(
|
||||
f'GATT Response to client: [0x{self.connection.handle:04X}] {att_pdu}'
|
||||
)
|
||||
@@ -1013,6 +1047,7 @@ class Client:
|
||||
return
|
||||
|
||||
# Return the response to the coroutine that is waiting for it
|
||||
assert self.pending_response is not None
|
||||
self.pending_response.set_result(att_pdu)
|
||||
else:
|
||||
handler_name = f'on_{att_pdu.name.lower()}'
|
||||
@@ -1060,7 +1095,7 @@ class Client:
|
||||
# Confirm that we received the indication
|
||||
self.send_confirmation(ATT_Handle_Value_Confirmation())
|
||||
|
||||
def cache_value(self, attribute_handle: int, value: bytes):
|
||||
def cache_value(self, attribute_handle: int, value: bytes) -> None:
|
||||
self.cached_values[attribute_handle] = (
|
||||
datetime.now(),
|
||||
value,
|
||||
|
||||
@@ -23,11 +23,12 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
import struct
|
||||
from typing import List, Tuple, Optional, TypeVar, Type
|
||||
from typing import List, Tuple, Optional, TypeVar, Type, Dict, Iterable, TYPE_CHECKING
|
||||
from pyee import EventEmitter
|
||||
|
||||
from .colors import color
|
||||
@@ -42,6 +43,7 @@ from .att import (
|
||||
ATT_INVALID_OFFSET_ERROR,
|
||||
ATT_REQUEST_NOT_SUPPORTED_ERROR,
|
||||
ATT_REQUESTS,
|
||||
ATT_PDU,
|
||||
ATT_UNLIKELY_ERROR_ERROR,
|
||||
ATT_UNSUPPORTED_GROUP_TYPE_ERROR,
|
||||
ATT_Error,
|
||||
@@ -73,6 +75,8 @@ from .gatt import (
|
||||
Service,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.device import Device, Connection
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
@@ -91,8 +95,13 @@ GATT_SERVER_DEFAULT_MAX_MTU = 517
|
||||
# -----------------------------------------------------------------------------
|
||||
class Server(EventEmitter):
|
||||
attributes: List[Attribute]
|
||||
services: List[Service]
|
||||
attributes_by_handle: Dict[int, Attribute]
|
||||
subscribers: Dict[int, Dict[int, bytes]]
|
||||
indication_semaphores: defaultdict[int, asyncio.Semaphore]
|
||||
pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]]
|
||||
|
||||
def __init__(self, device):
|
||||
def __init__(self, device: Device) -> None:
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.services = []
|
||||
@@ -107,16 +116,16 @@ class Server(EventEmitter):
|
||||
self.indication_semaphores = defaultdict(lambda: asyncio.Semaphore(1))
|
||||
self.pending_confirmations = defaultdict(lambda: None)
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return "\n".join(map(str, self.attributes))
|
||||
|
||||
def send_gatt_pdu(self, connection_handle, pdu):
|
||||
def send_gatt_pdu(self, connection_handle: int, pdu: bytes) -> None:
|
||||
self.device.send_l2cap_pdu(connection_handle, ATT_CID, pdu)
|
||||
|
||||
def next_handle(self):
|
||||
def next_handle(self) -> int:
|
||||
return 1 + len(self.attributes)
|
||||
|
||||
def get_advertising_service_data(self):
|
||||
def get_advertising_service_data(self) -> Dict[Attribute, bytes]:
|
||||
return {
|
||||
attribute: data
|
||||
for attribute in self.attributes
|
||||
@@ -124,7 +133,7 @@ class Server(EventEmitter):
|
||||
and (data := attribute.get_advertising_data())
|
||||
}
|
||||
|
||||
def get_attribute(self, handle):
|
||||
def get_attribute(self, handle: int) -> Optional[Attribute]:
|
||||
attribute = self.attributes_by_handle.get(handle)
|
||||
if attribute:
|
||||
return attribute
|
||||
@@ -173,12 +182,17 @@ class Server(EventEmitter):
|
||||
|
||||
return next(
|
||||
(
|
||||
(attribute, self.get_attribute(attribute.characteristic.handle))
|
||||
(
|
||||
attribute,
|
||||
self.get_attribute(attribute.characteristic.handle),
|
||||
) # type: ignore
|
||||
for attribute in map(
|
||||
self.get_attribute,
|
||||
range(service_handle.handle, service_handle.end_group_handle + 1),
|
||||
)
|
||||
if attribute.type == GATT_CHARACTERISTIC_ATTRIBUTE_TYPE
|
||||
if attribute is not None
|
||||
and attribute.type == GATT_CHARACTERISTIC_ATTRIBUTE_TYPE
|
||||
and isinstance(attribute, CharacteristicDeclaration)
|
||||
and attribute.characteristic.uuid == characteristic_uuid
|
||||
),
|
||||
None,
|
||||
@@ -197,7 +211,7 @@ class Server(EventEmitter):
|
||||
|
||||
return next(
|
||||
(
|
||||
attribute
|
||||
attribute # type: ignore
|
||||
for attribute in map(
|
||||
self.get_attribute,
|
||||
range(
|
||||
@@ -205,12 +219,12 @@ class Server(EventEmitter):
|
||||
characteristic_value.end_group_handle + 1,
|
||||
),
|
||||
)
|
||||
if attribute.type == descriptor_uuid
|
||||
if attribute is not None and attribute.type == descriptor_uuid
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
def add_attribute(self, attribute):
|
||||
def add_attribute(self, attribute: Attribute) -> None:
|
||||
# Assign a handle to this attribute
|
||||
attribute.handle = self.next_handle()
|
||||
attribute.end_group_handle = (
|
||||
@@ -220,7 +234,7 @@ class Server(EventEmitter):
|
||||
# Add this attribute to the list
|
||||
self.attributes.append(attribute)
|
||||
|
||||
def add_service(self, service: Service):
|
||||
def add_service(self, service: Service) -> None:
|
||||
# Add the service attribute to the DB
|
||||
self.add_attribute(service)
|
||||
|
||||
@@ -285,11 +299,13 @@ class Server(EventEmitter):
|
||||
service.end_group_handle = self.attributes[-1].handle
|
||||
self.services.append(service)
|
||||
|
||||
def add_services(self, services):
|
||||
def add_services(self, services: Iterable[Service]) -> None:
|
||||
for service in services:
|
||||
self.add_service(service)
|
||||
|
||||
def read_cccd(self, connection, characteristic):
|
||||
def read_cccd(
|
||||
self, connection: Optional[Connection], characteristic: Characteristic
|
||||
) -> bytes:
|
||||
if connection is None:
|
||||
return bytes([0, 0])
|
||||
|
||||
@@ -300,7 +316,12 @@ class Server(EventEmitter):
|
||||
|
||||
return cccd or bytes([0, 0])
|
||||
|
||||
def write_cccd(self, connection, characteristic, value):
|
||||
def write_cccd(
|
||||
self,
|
||||
connection: Connection,
|
||||
characteristic: Characteristic,
|
||||
value: bytes,
|
||||
) -> None:
|
||||
logger.debug(
|
||||
f'Subscription update for connection=0x{connection.handle:04X}, '
|
||||
f'handle=0x{characteristic.handle:04X}: {value.hex()}'
|
||||
@@ -327,13 +348,19 @@ class Server(EventEmitter):
|
||||
indicate_enabled,
|
||||
)
|
||||
|
||||
def send_response(self, connection, response):
|
||||
def send_response(self, connection: Connection, response: ATT_PDU) -> None:
|
||||
logger.debug(
|
||||
f'GATT Response from server: [0x{connection.handle:04X}] {response}'
|
||||
)
|
||||
self.send_gatt_pdu(connection.handle, response.to_bytes())
|
||||
|
||||
async def notify_subscriber(self, connection, attribute, value=None, force=False):
|
||||
async def notify_subscriber(
|
||||
self,
|
||||
connection: Connection,
|
||||
attribute: Attribute,
|
||||
value: Optional[bytes] = None,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
# Check if there's a subscriber
|
||||
if not force:
|
||||
subscribers = self.subscribers.get(connection.handle)
|
||||
@@ -370,7 +397,13 @@ class Server(EventEmitter):
|
||||
)
|
||||
self.send_gatt_pdu(connection.handle, bytes(notification))
|
||||
|
||||
async def indicate_subscriber(self, connection, attribute, value=None, force=False):
|
||||
async def indicate_subscriber(
|
||||
self,
|
||||
connection: Connection,
|
||||
attribute: Attribute,
|
||||
value: Optional[bytes] = None,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
# Check if there's a subscriber
|
||||
if not force:
|
||||
subscribers = self.subscribers.get(connection.handle)
|
||||
@@ -411,15 +444,13 @@ class Server(EventEmitter):
|
||||
assert self.pending_confirmations[connection.handle] is None
|
||||
|
||||
# Create a future value to hold the eventual response
|
||||
self.pending_confirmations[
|
||||
pending_confirmation = self.pending_confirmations[
|
||||
connection.handle
|
||||
] = asyncio.get_running_loop().create_future()
|
||||
|
||||
try:
|
||||
self.send_gatt_pdu(connection.handle, indication.to_bytes())
|
||||
await asyncio.wait_for(
|
||||
self.pending_confirmations[connection.handle], GATT_REQUEST_TIMEOUT
|
||||
)
|
||||
await asyncio.wait_for(pending_confirmation, GATT_REQUEST_TIMEOUT)
|
||||
except asyncio.TimeoutError as error:
|
||||
logger.warning(color('!!! GATT Indicate timeout', 'red'))
|
||||
raise TimeoutError(f'GATT timeout for {indication.name}') from error
|
||||
@@ -427,8 +458,12 @@ class Server(EventEmitter):
|
||||
self.pending_confirmations[connection.handle] = None
|
||||
|
||||
async def notify_or_indicate_subscribers(
|
||||
self, indicate, attribute, value=None, force=False
|
||||
):
|
||||
self,
|
||||
indicate: bool,
|
||||
attribute: Attribute,
|
||||
value: Optional[bytes] = None,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
# Get all the connections for which there's at least one subscription
|
||||
connections = [
|
||||
connection
|
||||
@@ -450,13 +485,23 @@ class Server(EventEmitter):
|
||||
]
|
||||
)
|
||||
|
||||
async def notify_subscribers(self, attribute, value=None, force=False):
|
||||
async def notify_subscribers(
|
||||
self,
|
||||
attribute: Attribute,
|
||||
value: Optional[bytes] = None,
|
||||
force: bool = False,
|
||||
):
|
||||
return await self.notify_or_indicate_subscribers(False, attribute, value, force)
|
||||
|
||||
async def indicate_subscribers(self, attribute, value=None, force=False):
|
||||
async def indicate_subscribers(
|
||||
self,
|
||||
attribute: Attribute,
|
||||
value: Optional[bytes] = None,
|
||||
force: bool = False,
|
||||
):
|
||||
return await self.notify_or_indicate_subscribers(True, attribute, value, force)
|
||||
|
||||
def on_disconnection(self, connection):
|
||||
def on_disconnection(self, connection: Connection) -> None:
|
||||
if connection.handle in self.subscribers:
|
||||
del self.subscribers[connection.handle]
|
||||
if connection.handle in self.indication_semaphores:
|
||||
@@ -464,7 +509,7 @@ class Server(EventEmitter):
|
||||
if connection.handle in self.pending_confirmations:
|
||||
del self.pending_confirmations[connection.handle]
|
||||
|
||||
def on_gatt_pdu(self, connection, att_pdu):
|
||||
def on_gatt_pdu(self, connection: Connection, att_pdu: ATT_PDU) -> None:
|
||||
logger.debug(f'GATT Request to server: [0x{connection.handle:04X}] {att_pdu}')
|
||||
handler_name = f'on_{att_pdu.name.lower()}'
|
||||
handler = getattr(self, handler_name, None)
|
||||
@@ -506,7 +551,7 @@ class Server(EventEmitter):
|
||||
#######################################################
|
||||
# ATT handlers
|
||||
#######################################################
|
||||
def on_att_request(self, connection, pdu):
|
||||
def on_att_request(self, connection: Connection, pdu: ATT_PDU) -> None:
|
||||
'''
|
||||
Handler for requests without a more specific handler
|
||||
'''
|
||||
@@ -679,7 +724,6 @@ class Server(EventEmitter):
|
||||
and attribute.handle <= request.ending_handle
|
||||
and pdu_space_available
|
||||
):
|
||||
|
||||
try:
|
||||
attribute_value = attribute.read_value(connection)
|
||||
except ATT_Error as error:
|
||||
|
||||
125
bumble/hci.py
125
bumble/hci.py
@@ -17,6 +17,7 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import collections
|
||||
import enum
|
||||
import functools
|
||||
import logging
|
||||
import struct
|
||||
@@ -121,6 +122,7 @@ HCI_VERSION_BLUETOOTH_CORE_5_0 = 9
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_1 = 10
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_2 = 11
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_3 = 12
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_4 = 13
|
||||
|
||||
HCI_VERSION_NAMES = {
|
||||
HCI_VERSION_BLUETOOTH_CORE_1_0B: 'HCI_VERSION_BLUETOOTH_CORE_1_0B',
|
||||
@@ -135,7 +137,8 @@ HCI_VERSION_NAMES = {
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_0: 'HCI_VERSION_BLUETOOTH_CORE_5_0',
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_1: 'HCI_VERSION_BLUETOOTH_CORE_5_1',
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_2: 'HCI_VERSION_BLUETOOTH_CORE_5_2',
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_3: 'HCI_VERSION_BLUETOOTH_CORE_5_3'
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_3: 'HCI_VERSION_BLUETOOTH_CORE_5_3',
|
||||
HCI_VERSION_BLUETOOTH_CORE_5_4: 'HCI_VERSION_BLUETOOTH_CORE_5_4',
|
||||
}
|
||||
|
||||
# LMP Version
|
||||
@@ -1368,6 +1371,7 @@ HCI_LE_SUPPORTED_FEATURES_NAMES = {
|
||||
if feature_name.startswith('HCI_') and feature_name.endswith('_LE_SUPPORTED_FEATURE')
|
||||
}
|
||||
|
||||
|
||||
# fmt: on
|
||||
# pylint: enable=line-too-long
|
||||
# pylint: disable=invalid-name
|
||||
@@ -1923,6 +1927,9 @@ class HCI_Packet:
|
||||
if packet_type == HCI_ACL_DATA_PACKET:
|
||||
return HCI_AclDataPacket.from_bytes(packet)
|
||||
|
||||
if packet_type == HCI_SYNCHRONOUS_DATA_PACKET:
|
||||
return HCI_SynchronousDataPacket.from_bytes(packet)
|
||||
|
||||
if packet_type == HCI_EVENT_PACKET:
|
||||
return HCI_Event.from_bytes(packet)
|
||||
|
||||
@@ -2291,6 +2298,19 @@ class HCI_Read_Clock_Offset_Command(HCI_Command):
|
||||
'''
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@HCI_Command.command(
|
||||
fields=[
|
||||
('bd_addr', Address.parse_address),
|
||||
('reason', {'size': 1, 'mapper': HCI_Constant.error_name}),
|
||||
],
|
||||
)
|
||||
class HCI_Reject_Synchronous_Connection_Request_Command(HCI_Command):
|
||||
'''
|
||||
See Bluetooth spec @ 7.1.28 Reject Synchronous Connection Request Command
|
||||
'''
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@HCI_Command.command(
|
||||
fields=[
|
||||
@@ -2452,6 +2472,51 @@ class HCI_Enhanced_Setup_Synchronous_Connection_Command(HCI_Command):
|
||||
See Bluetooth spec @ 7.1.45 Enhanced Setup Synchronous Connection Command
|
||||
'''
|
||||
|
||||
class CodingFormat(enum.IntEnum):
|
||||
U_LOG = 0x00
|
||||
A_LOG = 0x01
|
||||
CVSD = 0x02
|
||||
TRANSPARENT = 0x03
|
||||
PCM = 0x04
|
||||
MSBC = 0x05
|
||||
LC3 = 0x06
|
||||
G729A = 0x07
|
||||
|
||||
def to_bytes(self):
|
||||
return self.value.to_bytes(5, 'little')
|
||||
|
||||
def __bytes__(self):
|
||||
return self.to_bytes()
|
||||
|
||||
class PcmDataFormat(enum.IntEnum):
|
||||
NA = 0x00
|
||||
ONES_COMPLEMENT = 0x01
|
||||
TWOS_COMPLEMENT = 0x02
|
||||
SIGN_MAGNITUDE = 0x03
|
||||
UNSIGNED = 0x04
|
||||
|
||||
class DataPath(enum.IntEnum):
|
||||
HCI = 0x00
|
||||
PCM = 0x01
|
||||
|
||||
class RetransmissionEffort(enum.IntEnum):
|
||||
NO_RETRANSMISSION = 0x00
|
||||
OPTIMIZE_FOR_POWER = 0x01
|
||||
OPTIMIZE_FOR_QUALITY = 0x02
|
||||
DONT_CARE = 0xFF
|
||||
|
||||
class PacketType(enum.IntFlag):
|
||||
HV1 = 0x0001
|
||||
HV2 = 0x0002
|
||||
HV3 = 0x0004
|
||||
EV3 = 0x0008
|
||||
EV4 = 0x0010
|
||||
EV5 = 0x0020
|
||||
NO_2_EV3 = 0x0040
|
||||
NO_3_EV3 = 0x0080
|
||||
NO_2_EV5 = 0x0100
|
||||
NO_3_EV5 = 0x0200
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@HCI_Command.command(
|
||||
@@ -5736,6 +5801,64 @@ class HCI_AclDataPacket(HCI_Packet):
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class HCI_SynchronousDataPacket(HCI_Packet):
|
||||
'''
|
||||
See Bluetooth spec @ 5.4.3 HCI SCO Data Packets
|
||||
'''
|
||||
|
||||
hci_packet_type = HCI_SYNCHRONOUS_DATA_PACKET
|
||||
|
||||
@staticmethod
|
||||
def from_bytes(packet: bytes) -> HCI_SynchronousDataPacket:
|
||||
# Read the header
|
||||
h, data_total_length = struct.unpack_from('<HB', packet, 1)
|
||||
connection_handle = h & 0xFFF
|
||||
packet_status = (h >> 12) & 0b11
|
||||
rfu = (h >> 14) & 0b11
|
||||
data = packet[4:]
|
||||
if len(data) != data_total_length:
|
||||
raise ValueError(
|
||||
f'invalid packet length {len(data)} != {data_total_length}'
|
||||
)
|
||||
return HCI_SynchronousDataPacket(
|
||||
connection_handle, packet_status, rfu, data_total_length, data
|
||||
)
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
h = (self.packet_status << 12) | (self.rfu << 14) | self.connection_handle
|
||||
return (
|
||||
struct.pack('<BHB', HCI_SYNCHRONOUS_DATA_PACKET, h, self.data_total_length)
|
||||
+ self.data
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
connection_handle: int,
|
||||
packet_status: int,
|
||||
rfu: int,
|
||||
data_total_length: int,
|
||||
data: bytes,
|
||||
) -> None:
|
||||
self.connection_handle = connection_handle
|
||||
self.packet_status = packet_status
|
||||
self.rfu = rfu
|
||||
self.data_total_length = data_total_length
|
||||
self.data = data
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.to_bytes()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'{color("SCO", "blue")}: '
|
||||
f'handle=0x{self.connection_handle:04x}, '
|
||||
f'ps={self.packet_status}, rfu={self.rfu}, '
|
||||
f'data_total_length={self.data_total_length}, '
|
||||
f'data={self.data.hex()}'
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class HCI_AclDataPacketAssembler:
|
||||
current_data: Optional[bytes]
|
||||
|
||||
168
bumble/hfp.py
168
bumble/hfp.py
@@ -35,6 +35,7 @@ from bumble.core import (
|
||||
BT_L2CAP_PROTOCOL_ID,
|
||||
BT_RFCOMM_PROTOCOL_ID,
|
||||
)
|
||||
from bumble.hci import HCI_Enhanced_Setup_Synchronous_Connection_Command
|
||||
from bumble.sdp import (
|
||||
DataElement,
|
||||
ServiceAttribute,
|
||||
@@ -819,3 +820,170 @@ def sdp_records(
|
||||
DataElement.unsigned_integer_16(hf_supported_features),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ESCO Codec Default Parameters
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
# Hands-Free Profile v1.8, 5.7 Codec Interoperability Requirements
|
||||
class DefaultCodecParameters(enum.IntEnum):
|
||||
SCO_CVSD_D0 = enum.auto()
|
||||
SCO_CVSD_D1 = enum.auto()
|
||||
ESCO_CVSD_S1 = enum.auto()
|
||||
ESCO_CVSD_S2 = enum.auto()
|
||||
ESCO_CVSD_S3 = enum.auto()
|
||||
ESCO_CVSD_S4 = enum.auto()
|
||||
ESCO_MSBC_T1 = enum.auto()
|
||||
ESCO_MSBC_T2 = enum.auto()
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class EscoParameters:
|
||||
# Codec specific
|
||||
transmit_coding_format: HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat
|
||||
receive_coding_format: HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat
|
||||
packet_type: HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType
|
||||
retransmission_effort: HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort
|
||||
max_latency: int
|
||||
|
||||
# Common
|
||||
input_coding_format: HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat = (
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.TRANSPARENT
|
||||
)
|
||||
output_coding_format: HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat = (
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.TRANSPARENT
|
||||
)
|
||||
input_coded_data_size: int = 16
|
||||
output_coded_data_size: int = 16
|
||||
input_pcm_data_format: HCI_Enhanced_Setup_Synchronous_Connection_Command.PcmDataFormat = (
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.PcmDataFormat.TWOS_COMPLEMENT
|
||||
)
|
||||
output_pcm_data_format: HCI_Enhanced_Setup_Synchronous_Connection_Command.PcmDataFormat = (
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.PcmDataFormat.TWOS_COMPLEMENT
|
||||
)
|
||||
input_pcm_sample_payload_msb_position: int = 0
|
||||
output_pcm_sample_payload_msb_position: int = 0
|
||||
input_data_path: HCI_Enhanced_Setup_Synchronous_Connection_Command.DataPath = (
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.DataPath.HCI
|
||||
)
|
||||
output_data_path: HCI_Enhanced_Setup_Synchronous_Connection_Command.DataPath = (
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.DataPath.HCI
|
||||
)
|
||||
input_transport_unit_size: int = 0
|
||||
output_transport_unit_size: int = 0
|
||||
input_bandwidth: int = 16000
|
||||
output_bandwidth: int = 16000
|
||||
transmit_bandwidth: int = 8000
|
||||
receive_bandwidth: int = 8000
|
||||
transmit_codec_frame_size: int = 60
|
||||
receive_codec_frame_size: int = 60
|
||||
|
||||
|
||||
_ESCO_PARAMETERS_CVSD_D0 = EscoParameters(
|
||||
transmit_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
receive_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
max_latency=0xFFFF,
|
||||
packet_type=HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.HV1,
|
||||
retransmission_effort=HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort.NO_RETRANSMISSION,
|
||||
)
|
||||
|
||||
_ESCO_PARAMETERS_CVSD_D1 = EscoParameters(
|
||||
transmit_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
receive_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
max_latency=0xFFFF,
|
||||
packet_type=HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.HV3,
|
||||
retransmission_effort=HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort.NO_RETRANSMISSION,
|
||||
)
|
||||
|
||||
_ESCO_PARAMETERS_CVSD_S1 = EscoParameters(
|
||||
transmit_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
receive_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
max_latency=0x0007,
|
||||
packet_type=(
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_2_EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_2_EV5
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV5
|
||||
),
|
||||
retransmission_effort=HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort.OPTIMIZE_FOR_POWER,
|
||||
)
|
||||
|
||||
_ESCO_PARAMETERS_CVSD_S2 = EscoParameters(
|
||||
transmit_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
receive_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
max_latency=0x0007,
|
||||
packet_type=(
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_2_EV5
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV5
|
||||
),
|
||||
retransmission_effort=HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort.OPTIMIZE_FOR_POWER,
|
||||
)
|
||||
|
||||
_ESCO_PARAMETERS_CVSD_S3 = EscoParameters(
|
||||
transmit_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
receive_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
max_latency=0x000A,
|
||||
packet_type=(
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_2_EV5
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV5
|
||||
),
|
||||
retransmission_effort=HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort.OPTIMIZE_FOR_POWER,
|
||||
)
|
||||
|
||||
_ESCO_PARAMETERS_CVSD_S4 = EscoParameters(
|
||||
transmit_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
receive_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.CVSD,
|
||||
max_latency=0x000C,
|
||||
packet_type=(
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_2_EV5
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV5
|
||||
),
|
||||
retransmission_effort=HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort.OPTIMIZE_FOR_QUALITY,
|
||||
)
|
||||
|
||||
_ESCO_PARAMETERS_MSBC_T1 = EscoParameters(
|
||||
transmit_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.MSBC,
|
||||
receive_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.MSBC,
|
||||
max_latency=0x0008,
|
||||
packet_type=(
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_2_EV5
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV5
|
||||
),
|
||||
retransmission_effort=HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort.OPTIMIZE_FOR_QUALITY,
|
||||
)
|
||||
|
||||
_ESCO_PARAMETERS_MSBC_T2 = EscoParameters(
|
||||
transmit_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.MSBC,
|
||||
receive_coding_format=HCI_Enhanced_Setup_Synchronous_Connection_Command.CodingFormat.MSBC,
|
||||
max_latency=0x000D,
|
||||
packet_type=(
|
||||
HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_2_EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV3
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_2_EV5
|
||||
| HCI_Enhanced_Setup_Synchronous_Connection_Command.PacketType.NO_3_EV5
|
||||
),
|
||||
retransmission_effort=HCI_Enhanced_Setup_Synchronous_Connection_Command.RetransmissionEffort.OPTIMIZE_FOR_QUALITY,
|
||||
)
|
||||
|
||||
ESCO_PERAMETERS = {
|
||||
DefaultCodecParameters.SCO_CVSD_D0: _ESCO_PARAMETERS_CVSD_D0,
|
||||
DefaultCodecParameters.SCO_CVSD_D1: _ESCO_PARAMETERS_CVSD_D1,
|
||||
DefaultCodecParameters.ESCO_CVSD_S1: _ESCO_PARAMETERS_CVSD_S1,
|
||||
DefaultCodecParameters.ESCO_CVSD_S2: _ESCO_PARAMETERS_CVSD_S2,
|
||||
DefaultCodecParameters.ESCO_CVSD_S3: _ESCO_PARAMETERS_CVSD_S3,
|
||||
DefaultCodecParameters.ESCO_CVSD_S4: _ESCO_PARAMETERS_CVSD_S4,
|
||||
DefaultCodecParameters.ESCO_MSBC_T1: _ESCO_PARAMETERS_MSBC_T1,
|
||||
DefaultCodecParameters.ESCO_MSBC_T2: _ESCO_PARAMETERS_MSBC_T2,
|
||||
}
|
||||
|
||||
332
bumble/hid.py
Normal file
332
bumble/hid.py
Normal file
@@ -0,0 +1,332 @@
|
||||
# Copyright 2021-2022 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import asyncio
|
||||
import enum
|
||||
|
||||
from pyee import EventEmitter
|
||||
from typing import Optional, Tuple, Callable, Dict, Union, TYPE_CHECKING
|
||||
|
||||
from . import core, l2cap # type: ignore
|
||||
from .colors import color # type: ignore
|
||||
from .core import BT_BR_EDR_TRANSPORT, InvalidStateError, ProtocolError # type: ignore
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bumble.device import Device, Connection
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Constants
|
||||
# -----------------------------------------------------------------------------
|
||||
# fmt: on
|
||||
HID_CONTROL_PSM = 0x0011
|
||||
HID_INTERRUPT_PSM = 0x0013
|
||||
|
||||
|
||||
class Message:
|
||||
message_type: MessageType
|
||||
# Report types
|
||||
class ReportType(enum.IntEnum):
|
||||
OTHER_REPORT = 0x00
|
||||
INPUT_REPORT = 0x01
|
||||
OUTPUT_REPORT = 0x02
|
||||
FEATURE_REPORT = 0x03
|
||||
|
||||
# Handshake parameters
|
||||
class Handshake(enum.IntEnum):
|
||||
SUCCESSFUL = 0x00
|
||||
NOT_READY = 0x01
|
||||
ERR_INVALID_REPORT_ID = 0x02
|
||||
ERR_UNSUPPORTED_REQUEST = 0x03
|
||||
ERR_UNKNOWN = 0x0E
|
||||
ERR_FATAL = 0x0F
|
||||
|
||||
# Message Type
|
||||
class MessageType(enum.IntEnum):
|
||||
HANDSHAKE = 0x00
|
||||
CONTROL = 0x01
|
||||
GET_REPORT = 0x04
|
||||
SET_REPORT = 0x05
|
||||
GET_PROTOCOL = 0x06
|
||||
SET_PROTOCOL = 0x07
|
||||
DATA = 0x0A
|
||||
|
||||
# Protocol modes
|
||||
class ProtocolMode(enum.IntEnum):
|
||||
BOOT_PROTOCOL = 0x00
|
||||
REPORT_PROTOCOL = 0x01
|
||||
|
||||
# Control Operations
|
||||
class ControlCommand(enum.IntEnum):
|
||||
SUSPEND = 0x03
|
||||
EXIT_SUSPEND = 0x04
|
||||
VIRTUAL_CABLE_UNPLUG = 0x05
|
||||
|
||||
# Class Method to derive header
|
||||
@classmethod
|
||||
def header(cls, lower_bits: int = 0x00) -> bytes:
|
||||
return bytes([(cls.message_type << 4) | lower_bits])
|
||||
|
||||
|
||||
# HIDP messages
|
||||
@dataclass
|
||||
class GetReportMessage(Message):
|
||||
report_type: int
|
||||
report_id: int
|
||||
buffer_size: int
|
||||
message_type = Message.MessageType.GET_REPORT
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
packet_bytes = bytearray()
|
||||
packet_bytes.append(self.report_id)
|
||||
packet_bytes.extend(
|
||||
[(self.buffer_size & 0xFF), ((self.buffer_size >> 8) & 0xFF)]
|
||||
)
|
||||
if self.report_type == Message.ReportType.OTHER_REPORT:
|
||||
return self.header(self.report_type) + packet_bytes
|
||||
else:
|
||||
return self.header(0x08 | self.report_type) + packet_bytes
|
||||
|
||||
|
||||
@dataclass
|
||||
class SetReportMessage(Message):
|
||||
report_type: int
|
||||
data: bytes
|
||||
message_type = Message.MessageType.SET_REPORT
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.header(self.report_type) + self.data
|
||||
|
||||
|
||||
@dataclass
|
||||
class GetProtocolMessage(Message):
|
||||
message_type = Message.MessageType.GET_PROTOCOL
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.header()
|
||||
|
||||
|
||||
@dataclass
|
||||
class SetProtocolMessage(Message):
|
||||
protocol_mode: int
|
||||
message_type = Message.MessageType.SET_PROTOCOL
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.header(self.protocol_mode)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Suspend(Message):
|
||||
message_type = Message.MessageType.CONTROL
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.header(Message.ControlCommand.SUSPEND)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExitSuspend(Message):
|
||||
message_type = Message.MessageType.CONTROL
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.header(Message.ControlCommand.EXIT_SUSPEND)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VirtualCableUnplug(Message):
|
||||
message_type = Message.MessageType.CONTROL
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.header(Message.ControlCommand.VIRTUAL_CABLE_UNPLUG)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SendData(Message):
|
||||
data: bytes
|
||||
message_type = Message.MessageType.DATA
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
return self.header(Message.ReportType.OUTPUT_REPORT) + self.data
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class Host(EventEmitter):
|
||||
l2cap_ctrl_channel: Optional[l2cap.ClassicChannel]
|
||||
l2cap_intr_channel: Optional[l2cap.ClassicChannel]
|
||||
|
||||
def __init__(self, device: Device, connection: Connection) -> None:
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.connection = connection
|
||||
|
||||
self.l2cap_ctrl_channel = None
|
||||
self.l2cap_intr_channel = None
|
||||
|
||||
# Register ourselves with the L2CAP channel manager
|
||||
device.register_l2cap_server(HID_CONTROL_PSM, self.on_connection)
|
||||
device.register_l2cap_server(HID_INTERRUPT_PSM, self.on_connection)
|
||||
|
||||
async def connect_control_channel(self) -> None:
|
||||
# Create a new L2CAP connection - control channel
|
||||
try:
|
||||
self.l2cap_ctrl_channel = await self.device.l2cap_channel_manager.connect(
|
||||
self.connection, HID_CONTROL_PSM
|
||||
)
|
||||
except ProtocolError:
|
||||
logging.exception(f'L2CAP connection failed.')
|
||||
raise
|
||||
|
||||
assert self.l2cap_ctrl_channel is not None
|
||||
# Become a sink for the L2CAP channel
|
||||
self.l2cap_ctrl_channel.sink = self.on_ctrl_pdu
|
||||
|
||||
async def connect_interrupt_channel(self) -> None:
|
||||
# Create a new L2CAP connection - interrupt channel
|
||||
try:
|
||||
self.l2cap_intr_channel = await self.device.l2cap_channel_manager.connect(
|
||||
self.connection, HID_INTERRUPT_PSM
|
||||
)
|
||||
except ProtocolError:
|
||||
logging.exception(f'L2CAP connection failed.')
|
||||
raise
|
||||
|
||||
assert self.l2cap_intr_channel is not None
|
||||
# Become a sink for the L2CAP channel
|
||||
self.l2cap_intr_channel.sink = self.on_intr_pdu
|
||||
|
||||
async def disconnect_interrupt_channel(self) -> None:
|
||||
if self.l2cap_intr_channel is None:
|
||||
raise InvalidStateError('invalid state')
|
||||
channel = self.l2cap_intr_channel
|
||||
self.l2cap_intr_channel = None
|
||||
await channel.disconnect()
|
||||
|
||||
async def disconnect_control_channel(self) -> None:
|
||||
if self.l2cap_ctrl_channel is None:
|
||||
raise InvalidStateError('invalid state')
|
||||
channel = self.l2cap_ctrl_channel
|
||||
self.l2cap_ctrl_channel = None
|
||||
await channel.disconnect()
|
||||
|
||||
def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
|
||||
logger.debug(f'+++ New L2CAP connection: {l2cap_channel}')
|
||||
l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel))
|
||||
|
||||
def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
|
||||
if l2cap_channel.psm == HID_CONTROL_PSM:
|
||||
self.l2cap_ctrl_channel = l2cap_channel
|
||||
self.l2cap_ctrl_channel.sink = self.on_ctrl_pdu
|
||||
else:
|
||||
self.l2cap_intr_channel = l2cap_channel
|
||||
self.l2cap_intr_channel.sink = self.on_intr_pdu
|
||||
logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}')
|
||||
|
||||
def on_ctrl_pdu(self, pdu: bytes) -> None:
|
||||
logger.debug(f'<<< HID CONTROL PDU: {pdu.hex()}')
|
||||
# Here we will receive all kinds of packets, parse and then call respective callbacks
|
||||
message_type = pdu[0] >> 4
|
||||
param = pdu[0] & 0x0F
|
||||
|
||||
if message_type == Message.MessageType.HANDSHAKE:
|
||||
logger.debug(f'<<< HID HANDSHAKE: {Message.Handshake(param).name}')
|
||||
self.emit('handshake', Message.Handshake(param))
|
||||
elif message_type == Message.MessageType.DATA:
|
||||
logger.debug('<<< HID CONTROL DATA')
|
||||
self.emit('data', pdu)
|
||||
elif message_type == Message.MessageType.CONTROL:
|
||||
if param == Message.ControlCommand.SUSPEND:
|
||||
logger.debug('<<< HID SUSPEND')
|
||||
self.emit('suspend', pdu)
|
||||
elif param == Message.ControlCommand.EXIT_SUSPEND:
|
||||
logger.debug('<<< HID EXIT SUSPEND')
|
||||
self.emit('exit_suspend', pdu)
|
||||
elif param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG:
|
||||
logger.debug('<<< HID VIRTUAL CABLE UNPLUG')
|
||||
self.emit('virtual_cable_unplug')
|
||||
else:
|
||||
logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED')
|
||||
else:
|
||||
logger.debug('<<< HID CONTROL DATA')
|
||||
self.emit('data', pdu)
|
||||
|
||||
def on_intr_pdu(self, pdu: bytes) -> None:
|
||||
logger.debug(f'<<< HID INTERRUPT PDU: {pdu.hex()}')
|
||||
self.emit("data", pdu)
|
||||
|
||||
def get_report(self, report_type: int, report_id: int, buffer_size: int) -> None:
|
||||
msg = GetReportMessage(
|
||||
report_type=report_type, report_id=report_id, buffer_size=buffer_size
|
||||
)
|
||||
hid_message = bytes(msg)
|
||||
logger.debug(f'>>> HID CONTROL GET REPORT, PDU: {hid_message.hex()}')
|
||||
self.send_pdu_on_ctrl(hid_message)
|
||||
|
||||
def set_report(self, report_type: int, data: bytes):
|
||||
msg = SetReportMessage(report_type=report_type, data=data)
|
||||
hid_message = bytes(msg)
|
||||
logger.debug(f'>>> HID CONTROL SET REPORT, PDU:{hid_message.hex()}')
|
||||
self.send_pdu_on_ctrl(hid_message)
|
||||
|
||||
def get_protocol(self):
|
||||
msg = GetProtocolMessage()
|
||||
hid_message = bytes(msg)
|
||||
logger.debug(f'>>> HID CONTROL GET PROTOCOL, PDU: {hid_message.hex()}')
|
||||
self.send_pdu_on_ctrl(hid_message)
|
||||
|
||||
def set_protocol(self, protocol_mode: int):
|
||||
msg = SetProtocolMessage(protocol_mode=protocol_mode)
|
||||
hid_message = bytes(msg)
|
||||
logger.debug(f'>>> HID CONTROL SET PROTOCOL, PDU: {hid_message.hex()}')
|
||||
self.send_pdu_on_ctrl(hid_message)
|
||||
|
||||
def send_pdu_on_ctrl(self, msg: bytes) -> None:
|
||||
self.l2cap_ctrl_channel.send_pdu(msg) # type: ignore
|
||||
|
||||
def send_pdu_on_intr(self, msg: bytes) -> None:
|
||||
self.l2cap_intr_channel.send_pdu(msg) # type: ignore
|
||||
|
||||
def send_data(self, data):
|
||||
msg = SendData(data)
|
||||
hid_message = bytes(msg)
|
||||
logger.debug(f'>>> HID INTERRUPT SEND DATA, PDU: {hid_message.hex()}')
|
||||
self.send_pdu_on_intr(hid_message)
|
||||
|
||||
def suspend(self):
|
||||
msg = Suspend()
|
||||
hid_message = bytes(msg)
|
||||
logger.debug(f'>>> HID CONTROL SUSPEND, PDU:{hid_message.hex()}')
|
||||
self.send_pdu_on_ctrl(msg)
|
||||
|
||||
def exit_suspend(self):
|
||||
msg = ExitSuspend()
|
||||
hid_message = bytes(msg)
|
||||
logger.debug(f'>>> HID CONTROL EXIT SUSPEND, PDU:{hid_message.hex()}')
|
||||
self.send_pdu_on_ctrl(msg)
|
||||
|
||||
def virtual_cable_unplug(self):
|
||||
msg = VirtualCableUnplug()
|
||||
hid_message = bytes(msg)
|
||||
logger.debug(f'>>> HID CONTROL VIRTUAL CABLE UNPLUG, PDU: {hid_message.hex()}')
|
||||
self.send_pdu_on_ctrl(msg)
|
||||
@@ -21,7 +21,7 @@ import collections
|
||||
import logging
|
||||
import struct
|
||||
|
||||
from typing import Optional, TYPE_CHECKING, Dict, Callable, Awaitable
|
||||
from typing import Optional, TYPE_CHECKING, Dict, Callable, Awaitable, cast
|
||||
|
||||
from bumble.colors import color
|
||||
from bumble.l2cap import L2CAP_PDU
|
||||
@@ -43,6 +43,7 @@ from .hci import (
|
||||
HCI_RESET_COMMAND,
|
||||
HCI_SUCCESS,
|
||||
HCI_SUPPORTED_COMMANDS_FLAGS,
|
||||
HCI_SYNCHRONOUS_DATA_PACKET,
|
||||
HCI_VERSION_BLUETOOTH_CORE_4_0,
|
||||
HCI_AclDataPacket,
|
||||
HCI_AclDataPacketAssembler,
|
||||
@@ -67,6 +68,7 @@ from .hci import (
|
||||
HCI_Read_Local_Version_Information_Command,
|
||||
HCI_Reset_Command,
|
||||
HCI_Set_Event_Mask_Command,
|
||||
HCI_SynchronousDataPacket,
|
||||
)
|
||||
from .core import (
|
||||
BT_BR_EDR_TRANSPORT,
|
||||
@@ -485,12 +487,14 @@ class Host(AbortableEventEmitter):
|
||||
self.snooper.snoop(bytes(packet), Snooper.Direction.CONTROLLER_TO_HOST)
|
||||
|
||||
# If the packet is a command, invoke the handler for this packet
|
||||
if isinstance(packet, HCI_Command):
|
||||
self.on_hci_command_packet(packet)
|
||||
elif isinstance(packet, HCI_Event):
|
||||
self.on_hci_event_packet(packet)
|
||||
elif isinstance(packet, HCI_AclDataPacket):
|
||||
self.on_hci_acl_data_packet(packet)
|
||||
if packet.hci_packet_type == HCI_COMMAND_PACKET:
|
||||
self.on_hci_command_packet(cast(HCI_Command, packet))
|
||||
elif packet.hci_packet_type == HCI_EVENT_PACKET:
|
||||
self.on_hci_event_packet(cast(HCI_Event, packet))
|
||||
elif packet.hci_packet_type == HCI_ACL_DATA_PACKET:
|
||||
self.on_hci_acl_data_packet(cast(HCI_AclDataPacket, packet))
|
||||
elif packet.hci_packet_type == HCI_SYNCHRONOUS_DATA_PACKET:
|
||||
self.on_hci_sco_data_packet(cast(HCI_SynchronousDataPacket, packet))
|
||||
else:
|
||||
logger.warning(f'!!! unknown packet type {packet.hci_packet_type}')
|
||||
|
||||
@@ -507,6 +511,10 @@ class Host(AbortableEventEmitter):
|
||||
if connection := self.connections.get(packet.connection_handle):
|
||||
connection.on_hci_acl_data_packet(packet)
|
||||
|
||||
def on_hci_sco_data_packet(self, packet: HCI_SynchronousDataPacket) -> None:
|
||||
# Experimental
|
||||
self.emit('sco_packet', packet.connection_handle, packet)
|
||||
|
||||
def on_l2cap_pdu(self, connection: Connection, cid: int, pdu: bytes) -> None:
|
||||
self.emit('l2cap_pdu', connection.handle, cid, pdu)
|
||||
|
||||
@@ -760,7 +768,25 @@ class Host(AbortableEventEmitter):
|
||||
asyncio.create_task(send_long_term_key())
|
||||
|
||||
def on_hci_synchronous_connection_complete_event(self, event):
|
||||
pass
|
||||
if event.status == HCI_SUCCESS:
|
||||
# Create/update the connection
|
||||
logger.debug(
|
||||
f'### SCO CONNECTION: [0x{event.connection_handle:04X}] '
|
||||
f'{event.bd_addr}'
|
||||
)
|
||||
|
||||
# Notify the client
|
||||
self.emit(
|
||||
'sco_connection',
|
||||
event.bd_addr,
|
||||
event.connection_handle,
|
||||
event.link_type,
|
||||
)
|
||||
else:
|
||||
logger.debug(f'### SCO CONNECTION FAILED: {event.status}')
|
||||
|
||||
# Notify the client
|
||||
self.emit('sco_connection_failure', event.bd_addr, event.status)
|
||||
|
||||
def on_hci_synchronous_connection_changed_event(self, event):
|
||||
pass
|
||||
|
||||
512
bumble/l2cap.py
512
bumble/l2cap.py
@@ -17,6 +17,8 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import enum
|
||||
import logging
|
||||
import struct
|
||||
|
||||
@@ -37,6 +39,7 @@ from typing import (
|
||||
TYPE_CHECKING,
|
||||
)
|
||||
|
||||
from .utils import deprecated
|
||||
from .colors import color
|
||||
from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
|
||||
from .hci import (
|
||||
@@ -166,6 +169,34 @@ L2CAP_MTU_CONFIGURATION_PARAMETER_TYPE = 0x01
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ClassicChannelSpec:
|
||||
psm: Optional[int] = None
|
||||
mtu: int = L2CAP_MIN_BR_EDR_MTU
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class LeCreditBasedChannelSpec:
|
||||
psm: Optional[int] = None
|
||||
mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU
|
||||
mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS
|
||||
max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS
|
||||
|
||||
def __post_init__(self):
|
||||
if (
|
||||
self.max_credits < 1
|
||||
or self.max_credits > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_CREDITS
|
||||
):
|
||||
raise ValueError('max credits out of range')
|
||||
if self.mtu < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MTU:
|
||||
raise ValueError('MTU too small')
|
||||
if (
|
||||
self.mps < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MPS
|
||||
or self.mps > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_MPS
|
||||
):
|
||||
raise ValueError('MPS out of range')
|
||||
|
||||
|
||||
class L2CAP_PDU:
|
||||
'''
|
||||
See Bluetooth spec @ Vol 3, Part A - 3 DATA PACKET FORMAT
|
||||
@@ -675,57 +706,36 @@ class L2CAP_LE_Flow_Control_Credit(L2CAP_Control_Frame):
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class Channel(EventEmitter):
|
||||
# States
|
||||
CLOSED = 0x00
|
||||
WAIT_CONNECT = 0x01
|
||||
WAIT_CONNECT_RSP = 0x02
|
||||
OPEN = 0x03
|
||||
WAIT_DISCONNECT = 0x04
|
||||
WAIT_CREATE = 0x05
|
||||
WAIT_CREATE_RSP = 0x06
|
||||
WAIT_MOVE = 0x07
|
||||
WAIT_MOVE_RSP = 0x08
|
||||
WAIT_MOVE_CONFIRM = 0x09
|
||||
WAIT_CONFIRM_RSP = 0x0A
|
||||
class ClassicChannel(EventEmitter):
|
||||
class State(enum.IntEnum):
|
||||
# States
|
||||
CLOSED = 0x00
|
||||
WAIT_CONNECT = 0x01
|
||||
WAIT_CONNECT_RSP = 0x02
|
||||
OPEN = 0x03
|
||||
WAIT_DISCONNECT = 0x04
|
||||
WAIT_CREATE = 0x05
|
||||
WAIT_CREATE_RSP = 0x06
|
||||
WAIT_MOVE = 0x07
|
||||
WAIT_MOVE_RSP = 0x08
|
||||
WAIT_MOVE_CONFIRM = 0x09
|
||||
WAIT_CONFIRM_RSP = 0x0A
|
||||
|
||||
# CONFIG substates
|
||||
WAIT_CONFIG = 0x10
|
||||
WAIT_SEND_CONFIG = 0x11
|
||||
WAIT_CONFIG_REQ_RSP = 0x12
|
||||
WAIT_CONFIG_RSP = 0x13
|
||||
WAIT_CONFIG_REQ = 0x14
|
||||
WAIT_IND_FINAL_RSP = 0x15
|
||||
WAIT_FINAL_RSP = 0x16
|
||||
WAIT_CONTROL_IND = 0x17
|
||||
|
||||
STATE_NAMES = {
|
||||
CLOSED: 'CLOSED',
|
||||
WAIT_CONNECT: 'WAIT_CONNECT',
|
||||
WAIT_CONNECT_RSP: 'WAIT_CONNECT_RSP',
|
||||
OPEN: 'OPEN',
|
||||
WAIT_DISCONNECT: 'WAIT_DISCONNECT',
|
||||
WAIT_CREATE: 'WAIT_CREATE',
|
||||
WAIT_CREATE_RSP: 'WAIT_CREATE_RSP',
|
||||
WAIT_MOVE: 'WAIT_MOVE',
|
||||
WAIT_MOVE_RSP: 'WAIT_MOVE_RSP',
|
||||
WAIT_MOVE_CONFIRM: 'WAIT_MOVE_CONFIRM',
|
||||
WAIT_CONFIRM_RSP: 'WAIT_CONFIRM_RSP',
|
||||
WAIT_CONFIG: 'WAIT_CONFIG',
|
||||
WAIT_SEND_CONFIG: 'WAIT_SEND_CONFIG',
|
||||
WAIT_CONFIG_REQ_RSP: 'WAIT_CONFIG_REQ_RSP',
|
||||
WAIT_CONFIG_RSP: 'WAIT_CONFIG_RSP',
|
||||
WAIT_CONFIG_REQ: 'WAIT_CONFIG_REQ',
|
||||
WAIT_IND_FINAL_RSP: 'WAIT_IND_FINAL_RSP',
|
||||
WAIT_FINAL_RSP: 'WAIT_FINAL_RSP',
|
||||
WAIT_CONTROL_IND: 'WAIT_CONTROL_IND',
|
||||
}
|
||||
# CONFIG substates
|
||||
WAIT_CONFIG = 0x10
|
||||
WAIT_SEND_CONFIG = 0x11
|
||||
WAIT_CONFIG_REQ_RSP = 0x12
|
||||
WAIT_CONFIG_RSP = 0x13
|
||||
WAIT_CONFIG_REQ = 0x14
|
||||
WAIT_IND_FINAL_RSP = 0x15
|
||||
WAIT_FINAL_RSP = 0x16
|
||||
WAIT_CONTROL_IND = 0x17
|
||||
|
||||
connection_result: Optional[asyncio.Future[None]]
|
||||
disconnection_result: Optional[asyncio.Future[None]]
|
||||
response: Optional[asyncio.Future[bytes]]
|
||||
sink: Optional[Callable[[bytes], Any]]
|
||||
state: int
|
||||
state: State
|
||||
connection: Connection
|
||||
|
||||
def __init__(
|
||||
@@ -741,7 +751,7 @@ class Channel(EventEmitter):
|
||||
self.manager = manager
|
||||
self.connection = connection
|
||||
self.signaling_cid = signaling_cid
|
||||
self.state = Channel.CLOSED
|
||||
self.state = self.State.CLOSED
|
||||
self.mtu = mtu
|
||||
self.psm = psm
|
||||
self.source_cid = source_cid
|
||||
@@ -751,10 +761,8 @@ class Channel(EventEmitter):
|
||||
self.disconnection_result = None
|
||||
self.sink = None
|
||||
|
||||
def change_state(self, new_state: int) -> None:
|
||||
logger.debug(
|
||||
f'{self} state change -> {color(Channel.STATE_NAMES[new_state], "cyan")}'
|
||||
)
|
||||
def _change_state(self, new_state: State) -> None:
|
||||
logger.debug(f'{self} state change -> {color(new_state.name, "cyan")}')
|
||||
self.state = new_state
|
||||
|
||||
def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None:
|
||||
@@ -767,7 +775,7 @@ class Channel(EventEmitter):
|
||||
# Check that there isn't already a request pending
|
||||
if self.response:
|
||||
raise InvalidStateError('request already pending')
|
||||
if self.state != Channel.OPEN:
|
||||
if self.state != self.State.OPEN:
|
||||
raise InvalidStateError('channel not open')
|
||||
|
||||
self.response = asyncio.get_running_loop().create_future()
|
||||
@@ -787,14 +795,14 @@ class Channel(EventEmitter):
|
||||
)
|
||||
|
||||
async def connect(self) -> None:
|
||||
if self.state != Channel.CLOSED:
|
||||
if self.state != self.State.CLOSED:
|
||||
raise InvalidStateError('invalid state')
|
||||
|
||||
# Check that we can start a new connection
|
||||
if self.connection_result:
|
||||
raise RuntimeError('connection already pending')
|
||||
|
||||
self.change_state(Channel.WAIT_CONNECT_RSP)
|
||||
self._change_state(self.State.WAIT_CONNECT_RSP)
|
||||
self.send_control_frame(
|
||||
L2CAP_Connection_Request(
|
||||
identifier=self.manager.next_identifier(self.connection),
|
||||
@@ -814,10 +822,10 @@ class Channel(EventEmitter):
|
||||
self.connection_result = None
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
if self.state != Channel.OPEN:
|
||||
if self.state != self.State.OPEN:
|
||||
raise InvalidStateError('invalid state')
|
||||
|
||||
self.change_state(Channel.WAIT_DISCONNECT)
|
||||
self._change_state(self.State.WAIT_DISCONNECT)
|
||||
self.send_control_frame(
|
||||
L2CAP_Disconnection_Request(
|
||||
identifier=self.manager.next_identifier(self.connection),
|
||||
@@ -832,8 +840,8 @@ class Channel(EventEmitter):
|
||||
return await self.disconnection_result
|
||||
|
||||
def abort(self) -> None:
|
||||
if self.state == self.OPEN:
|
||||
self.change_state(self.CLOSED)
|
||||
if self.state == self.State.OPEN:
|
||||
self._change_state(self.State.CLOSED)
|
||||
self.emit('close')
|
||||
|
||||
def send_configure_request(self) -> None:
|
||||
@@ -856,7 +864,7 @@ class Channel(EventEmitter):
|
||||
|
||||
def on_connection_request(self, request) -> None:
|
||||
self.destination_cid = request.source_cid
|
||||
self.change_state(Channel.WAIT_CONNECT)
|
||||
self._change_state(self.State.WAIT_CONNECT)
|
||||
self.send_control_frame(
|
||||
L2CAP_Connection_Response(
|
||||
identifier=request.identifier,
|
||||
@@ -866,24 +874,24 @@ class Channel(EventEmitter):
|
||||
status=0x0000,
|
||||
)
|
||||
)
|
||||
self.change_state(Channel.WAIT_CONFIG)
|
||||
self._change_state(self.State.WAIT_CONFIG)
|
||||
self.send_configure_request()
|
||||
self.change_state(Channel.WAIT_CONFIG_REQ_RSP)
|
||||
self._change_state(self.State.WAIT_CONFIG_REQ_RSP)
|
||||
|
||||
def on_connection_response(self, response):
|
||||
if self.state != Channel.WAIT_CONNECT_RSP:
|
||||
if self.state != self.State.WAIT_CONNECT_RSP:
|
||||
logger.warning(color('invalid state', 'red'))
|
||||
return
|
||||
|
||||
if response.result == L2CAP_Connection_Response.CONNECTION_SUCCESSFUL:
|
||||
self.destination_cid = response.destination_cid
|
||||
self.change_state(Channel.WAIT_CONFIG)
|
||||
self._change_state(self.State.WAIT_CONFIG)
|
||||
self.send_configure_request()
|
||||
self.change_state(Channel.WAIT_CONFIG_REQ_RSP)
|
||||
self._change_state(self.State.WAIT_CONFIG_REQ_RSP)
|
||||
elif response.result == L2CAP_Connection_Response.CONNECTION_PENDING:
|
||||
pass
|
||||
else:
|
||||
self.change_state(Channel.CLOSED)
|
||||
self._change_state(self.State.CLOSED)
|
||||
self.connection_result.set_exception(
|
||||
ProtocolError(
|
||||
response.result,
|
||||
@@ -895,9 +903,9 @@ class Channel(EventEmitter):
|
||||
|
||||
def on_configure_request(self, request) -> None:
|
||||
if self.state not in (
|
||||
Channel.WAIT_CONFIG,
|
||||
Channel.WAIT_CONFIG_REQ,
|
||||
Channel.WAIT_CONFIG_REQ_RSP,
|
||||
self.State.WAIT_CONFIG,
|
||||
self.State.WAIT_CONFIG_REQ,
|
||||
self.State.WAIT_CONFIG_REQ_RSP,
|
||||
):
|
||||
logger.warning(color('invalid state', 'red'))
|
||||
return
|
||||
@@ -918,25 +926,28 @@ class Channel(EventEmitter):
|
||||
options=request.options, # TODO: don't accept everything blindly
|
||||
)
|
||||
)
|
||||
if self.state == Channel.WAIT_CONFIG:
|
||||
self.change_state(Channel.WAIT_SEND_CONFIG)
|
||||
if self.state == self.State.WAIT_CONFIG:
|
||||
self._change_state(self.State.WAIT_SEND_CONFIG)
|
||||
self.send_configure_request()
|
||||
self.change_state(Channel.WAIT_CONFIG_RSP)
|
||||
elif self.state == Channel.WAIT_CONFIG_REQ:
|
||||
self.change_state(Channel.OPEN)
|
||||
self._change_state(self.State.WAIT_CONFIG_RSP)
|
||||
elif self.state == self.State.WAIT_CONFIG_REQ:
|
||||
self._change_state(self.State.OPEN)
|
||||
if self.connection_result:
|
||||
self.connection_result.set_result(None)
|
||||
self.connection_result = None
|
||||
self.emit('open')
|
||||
elif self.state == Channel.WAIT_CONFIG_REQ_RSP:
|
||||
self.change_state(Channel.WAIT_CONFIG_RSP)
|
||||
elif self.state == self.State.WAIT_CONFIG_REQ_RSP:
|
||||
self._change_state(self.State.WAIT_CONFIG_RSP)
|
||||
|
||||
def on_configure_response(self, response) -> None:
|
||||
if response.result == L2CAP_Configure_Response.SUCCESS:
|
||||
if self.state == Channel.WAIT_CONFIG_REQ_RSP:
|
||||
self.change_state(Channel.WAIT_CONFIG_REQ)
|
||||
elif self.state in (Channel.WAIT_CONFIG_RSP, Channel.WAIT_CONTROL_IND):
|
||||
self.change_state(Channel.OPEN)
|
||||
if self.state == self.State.WAIT_CONFIG_REQ_RSP:
|
||||
self._change_state(self.State.WAIT_CONFIG_REQ)
|
||||
elif self.state in (
|
||||
self.State.WAIT_CONFIG_RSP,
|
||||
self.State.WAIT_CONTROL_IND,
|
||||
):
|
||||
self._change_state(self.State.OPEN)
|
||||
if self.connection_result:
|
||||
self.connection_result.set_result(None)
|
||||
self.connection_result = None
|
||||
@@ -966,7 +977,7 @@ class Channel(EventEmitter):
|
||||
# TODO: decide how to fail gracefully
|
||||
|
||||
def on_disconnection_request(self, request) -> None:
|
||||
if self.state in (Channel.OPEN, Channel.WAIT_DISCONNECT):
|
||||
if self.state in (self.State.OPEN, self.State.WAIT_DISCONNECT):
|
||||
self.send_control_frame(
|
||||
L2CAP_Disconnection_Response(
|
||||
identifier=request.identifier,
|
||||
@@ -974,14 +985,14 @@ class Channel(EventEmitter):
|
||||
source_cid=request.source_cid,
|
||||
)
|
||||
)
|
||||
self.change_state(Channel.CLOSED)
|
||||
self._change_state(self.State.CLOSED)
|
||||
self.emit('close')
|
||||
self.manager.on_channel_closed(self)
|
||||
else:
|
||||
logger.warning(color('invalid state', 'red'))
|
||||
|
||||
def on_disconnection_response(self, response) -> None:
|
||||
if self.state != Channel.WAIT_DISCONNECT:
|
||||
if self.state != self.State.WAIT_DISCONNECT:
|
||||
logger.warning(color('invalid state', 'red'))
|
||||
return
|
||||
|
||||
@@ -992,7 +1003,7 @@ class Channel(EventEmitter):
|
||||
logger.warning('unexpected source or destination CID')
|
||||
return
|
||||
|
||||
self.change_state(Channel.CLOSED)
|
||||
self._change_state(self.State.CLOSED)
|
||||
if self.disconnection_result:
|
||||
self.disconnection_result.set_result(None)
|
||||
self.disconnection_result = None
|
||||
@@ -1004,42 +1015,32 @@ class Channel(EventEmitter):
|
||||
f'Channel({self.source_cid}->{self.destination_cid}, '
|
||||
f'PSM={self.psm}, '
|
||||
f'MTU={self.mtu}, '
|
||||
f'state={Channel.STATE_NAMES[self.state]})'
|
||||
f'state={self.state.name})'
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class LeConnectionOrientedChannel(EventEmitter):
|
||||
class LeCreditBasedChannel(EventEmitter):
|
||||
"""
|
||||
LE Credit-based Connection Oriented Channel
|
||||
"""
|
||||
|
||||
INIT = 0
|
||||
CONNECTED = 1
|
||||
CONNECTING = 2
|
||||
DISCONNECTING = 3
|
||||
DISCONNECTED = 4
|
||||
CONNECTION_ERROR = 5
|
||||
|
||||
STATE_NAMES = {
|
||||
INIT: 'INIT',
|
||||
CONNECTED: 'CONNECTED',
|
||||
CONNECTING: 'CONNECTING',
|
||||
DISCONNECTING: 'DISCONNECTING',
|
||||
DISCONNECTED: 'DISCONNECTED',
|
||||
CONNECTION_ERROR: 'CONNECTION_ERROR',
|
||||
}
|
||||
class State(enum.IntEnum):
|
||||
INIT = 0
|
||||
CONNECTED = 1
|
||||
CONNECTING = 2
|
||||
DISCONNECTING = 3
|
||||
DISCONNECTED = 4
|
||||
CONNECTION_ERROR = 5
|
||||
|
||||
out_queue: Deque[bytes]
|
||||
connection_result: Optional[asyncio.Future[LeConnectionOrientedChannel]]
|
||||
connection_result: Optional[asyncio.Future[LeCreditBasedChannel]]
|
||||
disconnection_result: Optional[asyncio.Future[None]]
|
||||
in_sdu: Optional[bytes]
|
||||
out_sdu: Optional[bytes]
|
||||
state: int
|
||||
state: State
|
||||
connection: Connection
|
||||
|
||||
@staticmethod
|
||||
def state_name(state: int) -> str:
|
||||
return name_or_number(LeConnectionOrientedChannel.STATE_NAMES, state)
|
||||
sink: Optional[Callable[[bytes], Any]]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -1083,19 +1084,17 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
self.drained.set()
|
||||
|
||||
if connected:
|
||||
self.state = LeConnectionOrientedChannel.CONNECTED
|
||||
self.state = self.State.CONNECTED
|
||||
else:
|
||||
self.state = LeConnectionOrientedChannel.INIT
|
||||
self.state = self.State.INIT
|
||||
|
||||
def change_state(self, new_state: int) -> None:
|
||||
logger.debug(
|
||||
f'{self} state change -> {color(self.state_name(new_state), "cyan")}'
|
||||
)
|
||||
def _change_state(self, new_state: State) -> None:
|
||||
logger.debug(f'{self} state change -> {color(new_state.name, "cyan")}')
|
||||
self.state = new_state
|
||||
|
||||
if new_state == self.CONNECTED:
|
||||
if new_state == self.State.CONNECTED:
|
||||
self.emit('open')
|
||||
elif new_state == self.DISCONNECTED:
|
||||
elif new_state == self.State.DISCONNECTED:
|
||||
self.emit('close')
|
||||
|
||||
def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None:
|
||||
@@ -1104,9 +1103,9 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
|
||||
self.manager.send_control_frame(self.connection, L2CAP_LE_SIGNALING_CID, frame)
|
||||
|
||||
async def connect(self) -> LeConnectionOrientedChannel:
|
||||
async def connect(self) -> LeCreditBasedChannel:
|
||||
# Check that we're in the right state
|
||||
if self.state != self.INIT:
|
||||
if self.state != self.State.INIT:
|
||||
raise InvalidStateError('not in a connectable state')
|
||||
|
||||
# Check that we can start a new connection
|
||||
@@ -1114,7 +1113,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
if identifier in self.manager.le_coc_requests:
|
||||
raise RuntimeError('too many concurrent connection requests')
|
||||
|
||||
self.change_state(self.CONNECTING)
|
||||
self._change_state(self.State.CONNECTING)
|
||||
request = L2CAP_LE_Credit_Based_Connection_Request(
|
||||
identifier=identifier,
|
||||
le_psm=self.le_psm,
|
||||
@@ -1134,10 +1133,10 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
# Check that we're connected
|
||||
if self.state != self.CONNECTED:
|
||||
if self.state != self.State.CONNECTED:
|
||||
raise InvalidStateError('not connected')
|
||||
|
||||
self.change_state(self.DISCONNECTING)
|
||||
self._change_state(self.State.DISCONNECTING)
|
||||
self.flush_output()
|
||||
self.send_control_frame(
|
||||
L2CAP_Disconnection_Request(
|
||||
@@ -1153,15 +1152,15 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
return await self.disconnection_result
|
||||
|
||||
def abort(self) -> None:
|
||||
if self.state == self.CONNECTED:
|
||||
self.change_state(self.DISCONNECTED)
|
||||
if self.state == self.State.CONNECTED:
|
||||
self._change_state(self.State.DISCONNECTED)
|
||||
|
||||
def on_pdu(self, pdu: bytes) -> None:
|
||||
if self.sink is None:
|
||||
logger.warning('received pdu without a sink')
|
||||
return
|
||||
|
||||
if self.state != self.CONNECTED:
|
||||
if self.state != self.State.CONNECTED:
|
||||
logger.warning('received PDU while not connected, dropping')
|
||||
|
||||
# Manage the peer credits
|
||||
@@ -1240,7 +1239,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
self.credits = response.initial_credits
|
||||
self.connected = True
|
||||
self.connection_result.set_result(self)
|
||||
self.change_state(self.CONNECTED)
|
||||
self._change_state(self.State.CONNECTED)
|
||||
else:
|
||||
self.connection_result.set_exception(
|
||||
ProtocolError(
|
||||
@@ -1251,7 +1250,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
),
|
||||
)
|
||||
)
|
||||
self.change_state(self.CONNECTION_ERROR)
|
||||
self._change_state(self.State.CONNECTION_ERROR)
|
||||
|
||||
# Cleanup
|
||||
self.connection_result = None
|
||||
@@ -1271,11 +1270,11 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
source_cid=request.source_cid,
|
||||
)
|
||||
)
|
||||
self.change_state(self.DISCONNECTED)
|
||||
self._change_state(self.State.DISCONNECTED)
|
||||
self.flush_output()
|
||||
|
||||
def on_disconnection_response(self, response) -> None:
|
||||
if self.state != self.DISCONNECTING:
|
||||
if self.state != self.State.DISCONNECTING:
|
||||
logger.warning(color('invalid state', 'red'))
|
||||
return
|
||||
|
||||
@@ -1286,7 +1285,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
logger.warning('unexpected source or destination CID')
|
||||
return
|
||||
|
||||
self.change_state(self.DISCONNECTED)
|
||||
self._change_state(self.State.DISCONNECTED)
|
||||
if self.disconnection_result:
|
||||
self.disconnection_result.set_result(None)
|
||||
self.disconnection_result = None
|
||||
@@ -1339,7 +1338,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
return
|
||||
|
||||
def write(self, data: bytes) -> None:
|
||||
if self.state != self.CONNECTED:
|
||||
if self.state != self.State.CONNECTED:
|
||||
logger.warning('not connected, dropping data')
|
||||
return
|
||||
|
||||
@@ -1367,7 +1366,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f'CoC({self.source_cid}->{self.destination_cid}, '
|
||||
f'State={self.state_name(self.state)}, '
|
||||
f'State={self.state.name}, '
|
||||
f'PSM={self.le_psm}, '
|
||||
f'MTU={self.mtu}/{self.peer_mtu}, '
|
||||
f'MPS={self.mps}/{self.peer_mps}, '
|
||||
@@ -1375,15 +1374,67 @@ class LeConnectionOrientedChannel(EventEmitter):
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class ClassicChannelServer(EventEmitter):
|
||||
def __init__(
|
||||
self,
|
||||
manager: ChannelManager,
|
||||
psm: int,
|
||||
handler: Optional[Callable[[ClassicChannel], Any]],
|
||||
mtu: int,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.manager = manager
|
||||
self.handler = handler
|
||||
self.psm = psm
|
||||
self.mtu = mtu
|
||||
|
||||
def on_connection(self, channel: ClassicChannel) -> None:
|
||||
self.emit('connection', channel)
|
||||
if self.handler:
|
||||
self.handler(channel)
|
||||
|
||||
def close(self) -> None:
|
||||
if self.psm in self.manager.servers:
|
||||
del self.manager.servers[self.psm]
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class LeCreditBasedChannelServer(EventEmitter):
|
||||
def __init__(
|
||||
self,
|
||||
manager: ChannelManager,
|
||||
psm: int,
|
||||
handler: Optional[Callable[[LeCreditBasedChannel], Any]],
|
||||
max_credits: int,
|
||||
mtu: int,
|
||||
mps: int,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.manager = manager
|
||||
self.handler = handler
|
||||
self.psm = psm
|
||||
self.max_credits = max_credits
|
||||
self.mtu = mtu
|
||||
self.mps = mps
|
||||
|
||||
def on_connection(self, channel: LeCreditBasedChannel) -> None:
|
||||
self.emit('connection', channel)
|
||||
if self.handler:
|
||||
self.handler(channel)
|
||||
|
||||
def close(self) -> None:
|
||||
if self.psm in self.manager.le_coc_servers:
|
||||
del self.manager.le_coc_servers[self.psm]
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class ChannelManager:
|
||||
identifiers: Dict[int, int]
|
||||
channels: Dict[int, Dict[int, Union[Channel, LeConnectionOrientedChannel]]]
|
||||
servers: Dict[int, Callable[[Channel], Any]]
|
||||
le_coc_channels: Dict[int, Dict[int, LeConnectionOrientedChannel]]
|
||||
le_coc_servers: Dict[
|
||||
int, Tuple[Callable[[LeConnectionOrientedChannel], Any], int, int, int]
|
||||
]
|
||||
channels: Dict[int, Dict[int, Union[ClassicChannel, LeCreditBasedChannel]]]
|
||||
servers: Dict[int, ClassicChannelServer]
|
||||
le_coc_channels: Dict[int, Dict[int, LeCreditBasedChannel]]
|
||||
le_coc_servers: Dict[int, LeCreditBasedChannelServer]
|
||||
le_coc_requests: Dict[int, L2CAP_LE_Credit_Based_Connection_Request]
|
||||
fixed_channels: Dict[int, Optional[Callable[[int, bytes], Any]]]
|
||||
_host: Optional[Host]
|
||||
@@ -1462,21 +1513,6 @@ class ChannelManager:
|
||||
|
||||
raise RuntimeError('no free CID')
|
||||
|
||||
@staticmethod
|
||||
def check_le_coc_parameters(max_credits: int, mtu: int, mps: int) -> None:
|
||||
if (
|
||||
max_credits < 1
|
||||
or max_credits > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_CREDITS
|
||||
):
|
||||
raise ValueError('max credits out of range')
|
||||
if mtu < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MTU:
|
||||
raise ValueError('MTU too small')
|
||||
if (
|
||||
mps < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MPS
|
||||
or mps > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_MPS
|
||||
):
|
||||
raise ValueError('MPS out of range')
|
||||
|
||||
def next_identifier(self, connection: Connection) -> int:
|
||||
identifier = (self.identifiers.setdefault(connection.handle, 0) + 1) % 256
|
||||
self.identifiers[connection.handle] = identifier
|
||||
@@ -1491,8 +1527,22 @@ class ChannelManager:
|
||||
if cid in self.fixed_channels:
|
||||
del self.fixed_channels[cid]
|
||||
|
||||
def register_server(self, psm: int, server: Callable[[Channel], Any]) -> int:
|
||||
if psm == 0:
|
||||
@deprecated("Please use create_classic_server")
|
||||
def register_server(
|
||||
self,
|
||||
psm: int,
|
||||
server: Callable[[ClassicChannel], Any],
|
||||
) -> int:
|
||||
return self.create_classic_server(
|
||||
handler=server, spec=ClassicChannelSpec(psm=psm)
|
||||
).psm
|
||||
|
||||
def create_classic_server(
|
||||
self,
|
||||
spec: ClassicChannelSpec,
|
||||
handler: Optional[Callable[[ClassicChannel], Any]] = None,
|
||||
) -> ClassicChannelServer:
|
||||
if not spec.psm:
|
||||
# Find a free PSM
|
||||
for candidate in range(
|
||||
L2CAP_PSM_DYNAMIC_RANGE_START, L2CAP_PSM_DYNAMIC_RANGE_END + 1, 2
|
||||
@@ -1501,62 +1551,75 @@ class ChannelManager:
|
||||
continue
|
||||
if candidate in self.servers:
|
||||
continue
|
||||
psm = candidate
|
||||
spec.psm = candidate
|
||||
break
|
||||
else:
|
||||
raise InvalidStateError('no free PSM')
|
||||
else:
|
||||
# Check that the PSM isn't already in use
|
||||
if psm in self.servers:
|
||||
if spec.psm in self.servers:
|
||||
raise ValueError('PSM already in use')
|
||||
|
||||
# Check that the PSM is valid
|
||||
if psm % 2 == 0:
|
||||
if spec.psm % 2 == 0:
|
||||
raise ValueError('invalid PSM (not odd)')
|
||||
check = psm >> 8
|
||||
check = spec.psm >> 8
|
||||
while check:
|
||||
if check % 2 != 0:
|
||||
raise ValueError('invalid PSM')
|
||||
check >>= 8
|
||||
|
||||
self.servers[psm] = server
|
||||
self.servers[spec.psm] = ClassicChannelServer(self, spec.psm, handler, spec.mtu)
|
||||
|
||||
return psm
|
||||
return self.servers[spec.psm]
|
||||
|
||||
@deprecated("Please use create_le_credit_based_server()")
|
||||
def register_le_coc_server(
|
||||
self,
|
||||
psm: int,
|
||||
server: Callable[[LeConnectionOrientedChannel], Any],
|
||||
max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS,
|
||||
mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
|
||||
mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
|
||||
server: Callable[[LeCreditBasedChannel], Any],
|
||||
max_credits: int,
|
||||
mtu: int,
|
||||
mps: int,
|
||||
) -> int:
|
||||
self.check_le_coc_parameters(max_credits, mtu, mps)
|
||||
return self.create_le_credit_based_server(
|
||||
spec=LeCreditBasedChannelSpec(
|
||||
psm=None if psm == 0 else psm, mtu=mtu, mps=mps, max_credits=max_credits
|
||||
),
|
||||
handler=server,
|
||||
).psm
|
||||
|
||||
if psm == 0:
|
||||
def create_le_credit_based_server(
|
||||
self,
|
||||
spec: LeCreditBasedChannelSpec,
|
||||
handler: Optional[Callable[[LeCreditBasedChannel], Any]] = None,
|
||||
) -> LeCreditBasedChannelServer:
|
||||
if not spec.psm:
|
||||
# Find a free PSM
|
||||
for candidate in range(
|
||||
L2CAP_LE_PSM_DYNAMIC_RANGE_START, L2CAP_LE_PSM_DYNAMIC_RANGE_END + 1
|
||||
):
|
||||
if candidate in self.le_coc_servers:
|
||||
continue
|
||||
psm = candidate
|
||||
spec.psm = candidate
|
||||
break
|
||||
else:
|
||||
raise InvalidStateError('no free PSM')
|
||||
else:
|
||||
# Check that the PSM isn't already in use
|
||||
if psm in self.le_coc_servers:
|
||||
if spec.psm in self.le_coc_servers:
|
||||
raise ValueError('PSM already in use')
|
||||
|
||||
self.le_coc_servers[psm] = (
|
||||
server,
|
||||
max_credits or L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS,
|
||||
mtu or L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
|
||||
mps or L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
|
||||
self.le_coc_servers[spec.psm] = LeCreditBasedChannelServer(
|
||||
self,
|
||||
spec.psm,
|
||||
handler,
|
||||
max_credits=spec.max_credits,
|
||||
mtu=spec.mtu,
|
||||
mps=spec.mps,
|
||||
)
|
||||
|
||||
return psm
|
||||
return self.le_coc_servers[spec.psm]
|
||||
|
||||
def on_disconnection(self, connection_handle: int, _reason: int) -> None:
|
||||
logger.debug(f'disconnection from {connection_handle}, cleaning up channels')
|
||||
@@ -1683,13 +1746,13 @@ class ChannelManager:
|
||||
logger.debug(
|
||||
f'creating server channel with cid={source_cid} for psm {request.psm}'
|
||||
)
|
||||
channel = Channel(
|
||||
self, connection, cid, request.psm, source_cid, L2CAP_MIN_BR_EDR_MTU
|
||||
channel = ClassicChannel(
|
||||
self, connection, cid, request.psm, source_cid, server.mtu
|
||||
)
|
||||
connection_channels[source_cid] = channel
|
||||
|
||||
# Notify
|
||||
server(channel)
|
||||
server.on_connection(channel)
|
||||
channel.on_connection_request(request)
|
||||
else:
|
||||
logger.warning(
|
||||
@@ -1911,7 +1974,7 @@ class ChannelManager:
|
||||
self, connection: Connection, cid: int, request
|
||||
) -> None:
|
||||
if request.le_psm in self.le_coc_servers:
|
||||
(server, max_credits, mtu, mps) = self.le_coc_servers[request.le_psm]
|
||||
server = self.le_coc_servers[request.le_psm]
|
||||
|
||||
# Check that the CID isn't already used
|
||||
le_connection_channels = self.le_coc_channels.setdefault(
|
||||
@@ -1925,8 +1988,8 @@ class ChannelManager:
|
||||
L2CAP_LE_Credit_Based_Connection_Response(
|
||||
identifier=request.identifier,
|
||||
destination_cid=0,
|
||||
mtu=mtu,
|
||||
mps=mps,
|
||||
mtu=server.mtu,
|
||||
mps=server.mps,
|
||||
initial_credits=0,
|
||||
# pylint: disable=line-too-long
|
||||
result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
|
||||
@@ -1944,8 +2007,8 @@ class ChannelManager:
|
||||
L2CAP_LE_Credit_Based_Connection_Response(
|
||||
identifier=request.identifier,
|
||||
destination_cid=0,
|
||||
mtu=mtu,
|
||||
mps=mps,
|
||||
mtu=server.mtu,
|
||||
mps=server.mps,
|
||||
initial_credits=0,
|
||||
# pylint: disable=line-too-long
|
||||
result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
|
||||
@@ -1958,18 +2021,18 @@ class ChannelManager:
|
||||
f'creating LE CoC server channel with cid={source_cid} for psm '
|
||||
f'{request.le_psm}'
|
||||
)
|
||||
channel = LeConnectionOrientedChannel(
|
||||
channel = LeCreditBasedChannel(
|
||||
self,
|
||||
connection,
|
||||
request.le_psm,
|
||||
source_cid,
|
||||
request.source_cid,
|
||||
mtu,
|
||||
mps,
|
||||
server.mtu,
|
||||
server.mps,
|
||||
request.initial_credits,
|
||||
request.mtu,
|
||||
request.mps,
|
||||
max_credits,
|
||||
server.max_credits,
|
||||
True,
|
||||
)
|
||||
connection_channels[source_cid] = channel
|
||||
@@ -1982,16 +2045,16 @@ class ChannelManager:
|
||||
L2CAP_LE_Credit_Based_Connection_Response(
|
||||
identifier=request.identifier,
|
||||
destination_cid=source_cid,
|
||||
mtu=mtu,
|
||||
mps=mps,
|
||||
initial_credits=max_credits,
|
||||
mtu=server.mtu,
|
||||
mps=server.mps,
|
||||
initial_credits=server.max_credits,
|
||||
# pylint: disable=line-too-long
|
||||
result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_SUCCESSFUL,
|
||||
),
|
||||
)
|
||||
|
||||
# Notify
|
||||
server(channel)
|
||||
server.on_connection(channel)
|
||||
else:
|
||||
logger.info(
|
||||
f'No LE server for connection 0x{connection.handle:04X} '
|
||||
@@ -2046,37 +2109,51 @@ class ChannelManager:
|
||||
|
||||
channel.on_credits(credit.credits)
|
||||
|
||||
def on_channel_closed(self, channel: Channel) -> None:
|
||||
def on_channel_closed(self, channel: ClassicChannel) -> None:
|
||||
connection_channels = self.channels.get(channel.connection.handle)
|
||||
if connection_channels:
|
||||
if channel.source_cid in connection_channels:
|
||||
del connection_channels[channel.source_cid]
|
||||
|
||||
@deprecated("Please use create_le_credit_based_channel()")
|
||||
async def open_le_coc(
|
||||
self, connection: Connection, psm: int, max_credits: int, mtu: int, mps: int
|
||||
) -> LeConnectionOrientedChannel:
|
||||
self.check_le_coc_parameters(max_credits, mtu, mps)
|
||||
) -> LeCreditBasedChannel:
|
||||
return await self.create_le_credit_based_channel(
|
||||
connection=connection,
|
||||
spec=LeCreditBasedChannelSpec(
|
||||
psm=psm, max_credits=max_credits, mtu=mtu, mps=mps
|
||||
),
|
||||
)
|
||||
|
||||
async def create_le_credit_based_channel(
|
||||
self,
|
||||
connection: Connection,
|
||||
spec: LeCreditBasedChannelSpec,
|
||||
) -> LeCreditBasedChannel:
|
||||
# Find a free CID for the new channel
|
||||
connection_channels = self.channels.setdefault(connection.handle, {})
|
||||
source_cid = self.find_free_le_cid(connection_channels)
|
||||
if source_cid is None: # Should never happen!
|
||||
raise RuntimeError('all CIDs already in use')
|
||||
|
||||
if spec.psm is None:
|
||||
raise ValueError('PSM cannot be None')
|
||||
|
||||
# Create the channel
|
||||
logger.debug(f'creating coc channel with cid={source_cid} for psm {psm}')
|
||||
channel = LeConnectionOrientedChannel(
|
||||
logger.debug(f'creating coc channel with cid={source_cid} for psm {spec.psm}')
|
||||
channel = LeCreditBasedChannel(
|
||||
manager=self,
|
||||
connection=connection,
|
||||
le_psm=psm,
|
||||
le_psm=spec.psm,
|
||||
source_cid=source_cid,
|
||||
destination_cid=0,
|
||||
mtu=mtu,
|
||||
mps=mps,
|
||||
mtu=spec.mtu,
|
||||
mps=spec.mps,
|
||||
credits=0,
|
||||
peer_mtu=0,
|
||||
peer_mps=0,
|
||||
peer_credits=max_credits,
|
||||
peer_credits=spec.max_credits,
|
||||
connected=False,
|
||||
)
|
||||
connection_channels[source_cid] = channel
|
||||
@@ -2095,7 +2172,15 @@ class ChannelManager:
|
||||
|
||||
return channel
|
||||
|
||||
async def connect(self, connection: Connection, psm: int) -> Channel:
|
||||
@deprecated("Please use create_classic_channel()")
|
||||
async def connect(self, connection: Connection, psm: int) -> ClassicChannel:
|
||||
return await self.create_classic_channel(
|
||||
connection=connection, spec=ClassicChannelSpec(psm=psm)
|
||||
)
|
||||
|
||||
async def create_classic_channel(
|
||||
self, connection: Connection, spec: ClassicChannelSpec
|
||||
) -> ClassicChannel:
|
||||
# NOTE: this implementation hard-codes BR/EDR
|
||||
|
||||
# Find a free CID for a new channel
|
||||
@@ -2104,10 +2189,20 @@ class ChannelManager:
|
||||
if source_cid is None: # Should never happen!
|
||||
raise RuntimeError('all CIDs already in use')
|
||||
|
||||
if spec.psm is None:
|
||||
raise ValueError('PSM cannot be None')
|
||||
|
||||
# Create the channel
|
||||
logger.debug(f'creating client channel with cid={source_cid} for psm {psm}')
|
||||
channel = Channel(
|
||||
self, connection, L2CAP_SIGNALING_CID, psm, source_cid, L2CAP_MIN_BR_EDR_MTU
|
||||
logger.debug(
|
||||
f'creating client channel with cid={source_cid} for psm {spec.psm}'
|
||||
)
|
||||
channel = ClassicChannel(
|
||||
self,
|
||||
connection,
|
||||
L2CAP_SIGNALING_CID,
|
||||
spec.psm,
|
||||
source_cid,
|
||||
spec.mtu,
|
||||
)
|
||||
connection_channels[source_cid] = channel
|
||||
|
||||
@@ -2119,3 +2214,20 @@ class ChannelManager:
|
||||
raise e
|
||||
|
||||
return channel
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Deprecated Classes
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Channel(ClassicChannel):
|
||||
@deprecated("Please use ClassicChannel")
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class LeConnectionOrientedChannel(LeCreditBasedChannel):
|
||||
@deprecated("Please use LeCreditBasedChannel")
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
from bumble.pairing import PairingConfig, PairingDelegate
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
"""Generic & dependency free Bumble (reference) device."""
|
||||
|
||||
from __future__ import annotations
|
||||
from bumble import transport
|
||||
from bumble.core import (
|
||||
BT_GENERIC_AUDIO_SERVICE,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import bumble.device
|
||||
import grpc
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import contextlib
|
||||
import grpc
|
||||
@@ -233,7 +234,11 @@ class SecurityService(SecurityServicer):
|
||||
sc=config.pairing_sc_enable,
|
||||
mitm=config.pairing_mitm_enable,
|
||||
bonding=config.pairing_bonding_enable,
|
||||
identity_address_type=config.identity_address_type,
|
||||
identity_address_type=(
|
||||
PairingConfig.AddressType.PUBLIC
|
||||
if connection.self_address.is_public
|
||||
else config.identity_address_type
|
||||
),
|
||||
delegate=PairingDelegate(
|
||||
connection,
|
||||
self,
|
||||
@@ -446,21 +451,18 @@ class SecurityService(SecurityServicer):
|
||||
'security_request': pair,
|
||||
}
|
||||
|
||||
# register event handlers
|
||||
for event, listener in listeners.items():
|
||||
connection.on(event, listener)
|
||||
with contextlib.closing(EventWatcher()) as watcher:
|
||||
# register event handlers
|
||||
for event, listener in listeners.items():
|
||||
watcher.on(connection, event, listener)
|
||||
|
||||
# security level already reached
|
||||
if self.reached_security_level(connection, level):
|
||||
return WaitSecurityResponse(success=empty_pb2.Empty())
|
||||
# security level already reached
|
||||
if self.reached_security_level(connection, level):
|
||||
return WaitSecurityResponse(success=empty_pb2.Empty())
|
||||
|
||||
self.log.debug('Wait for security...')
|
||||
kwargs = {}
|
||||
kwargs[await wait_for_security] = empty_pb2.Empty()
|
||||
|
||||
# remove event handlers
|
||||
for event, listener in listeners.items():
|
||||
connection.remove_listener(event, listener) # type: ignore
|
||||
self.log.debug('Wait for security...')
|
||||
kwargs = {}
|
||||
kwargs[await wait_for_security] = empty_pb2.Empty()
|
||||
|
||||
# wait for `authenticate` to finish if any
|
||||
if authenticate_task is not None:
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
import contextlib
|
||||
import functools
|
||||
import grpc
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
import struct
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from bumble import l2cap
|
||||
from ..core import AdvertisingData
|
||||
from ..device import Device, Connection
|
||||
from ..gatt import (
|
||||
@@ -149,7 +151,10 @@ class AshaService(TemplateService):
|
||||
channel.sink = on_data
|
||||
|
||||
# let the server find a free PSM
|
||||
self.psm = self.device.register_l2cap_channel_server(self.psm, on_coc, 8)
|
||||
self.psm = device.create_l2cap_server(
|
||||
spec=l2cap.LeCreditBasedChannelSpec(psm=self.psm, max_credits=8),
|
||||
handler=on_coc,
|
||||
).psm
|
||||
self.le_psm_out_characteristic = Characteristic(
|
||||
GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC,
|
||||
Characteristic.Properties.READ,
|
||||
|
||||
@@ -42,12 +42,12 @@ class HeartRateService(TemplateService):
|
||||
RESET_ENERGY_EXPENDED = 0x01
|
||||
|
||||
class BodySensorLocation(IntEnum):
|
||||
OTHER = (0,)
|
||||
CHEST = (1,)
|
||||
WRIST = (2,)
|
||||
FINGER = (3,)
|
||||
HAND = (4,)
|
||||
EAR_LOBE = (5,)
|
||||
OTHER = 0
|
||||
CHEST = 1
|
||||
WRIST = 2
|
||||
FINGER = 3
|
||||
HAND = 4
|
||||
EAR_LOBE = 5
|
||||
FOOT = 6
|
||||
|
||||
class HeartRateMeasurement:
|
||||
|
||||
@@ -674,7 +674,7 @@ class Multiplexer(EventEmitter):
|
||||
acceptor: Optional[Callable[[int], bool]]
|
||||
dlcs: Dict[int, DLC]
|
||||
|
||||
def __init__(self, l2cap_channel: l2cap.Channel, role: Role) -> None:
|
||||
def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None:
|
||||
super().__init__()
|
||||
self.role = role
|
||||
self.l2cap_channel = l2cap_channel
|
||||
@@ -887,7 +887,7 @@ class Multiplexer(EventEmitter):
|
||||
# -----------------------------------------------------------------------------
|
||||
class Client:
|
||||
multiplexer: Optional[Multiplexer]
|
||||
l2cap_channel: Optional[l2cap.Channel]
|
||||
l2cap_channel: Optional[l2cap.ClassicChannel]
|
||||
|
||||
def __init__(self, device: Device, connection: Connection) -> None:
|
||||
self.device = device
|
||||
@@ -898,8 +898,8 @@ class Client:
|
||||
async def start(self) -> Multiplexer:
|
||||
# Create a new L2CAP connection
|
||||
try:
|
||||
self.l2cap_channel = await self.device.l2cap_channel_manager.connect(
|
||||
self.connection, RFCOMM_PSM
|
||||
self.l2cap_channel = await self.connection.create_l2cap_channel(
|
||||
spec=l2cap.ClassicChannelSpec(RFCOMM_PSM)
|
||||
)
|
||||
except ProtocolError as error:
|
||||
logger.warning(f'L2CAP connection failed: {error}')
|
||||
@@ -936,7 +936,9 @@ class Server(EventEmitter):
|
||||
self.acceptors = {}
|
||||
|
||||
# Register ourselves with the L2CAP channel manager
|
||||
device.register_l2cap_server(RFCOMM_PSM, self.on_connection)
|
||||
device.create_l2cap_server(
|
||||
spec=l2cap.ClassicChannelSpec(psm=RFCOMM_PSM), handler=self.on_connection
|
||||
)
|
||||
|
||||
def listen(self, acceptor: Callable[[DLC], None], channel: int = 0) -> int:
|
||||
if channel:
|
||||
@@ -960,11 +962,11 @@ class Server(EventEmitter):
|
||||
self.acceptors[channel] = acceptor
|
||||
return channel
|
||||
|
||||
def on_connection(self, l2cap_channel: l2cap.Channel) -> None:
|
||||
def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
|
||||
logger.debug(f'+++ new L2CAP connection: {l2cap_channel}')
|
||||
l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel))
|
||||
|
||||
def on_l2cap_channel_open(self, l2cap_channel: l2cap.Channel) -> None:
|
||||
def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
|
||||
logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}')
|
||||
|
||||
# Create a new multiplexer for the channel
|
||||
|
||||
@@ -167,7 +167,7 @@ class DataElement:
|
||||
UUID: lambda x: DataElement(
|
||||
DataElement.UUID, core.UUID.from_bytes(bytes(reversed(x)))
|
||||
),
|
||||
TEXT_STRING: lambda x: DataElement(DataElement.TEXT_STRING, x.decode('utf8')),
|
||||
TEXT_STRING: lambda x: DataElement(DataElement.TEXT_STRING, x),
|
||||
BOOLEAN: lambda x: DataElement(DataElement.BOOLEAN, x[0] == 1),
|
||||
SEQUENCE: lambda x: DataElement(
|
||||
DataElement.SEQUENCE, DataElement.list_from_bytes(x)
|
||||
@@ -229,7 +229,7 @@ class DataElement:
|
||||
return DataElement(DataElement.UUID, value)
|
||||
|
||||
@staticmethod
|
||||
def text_string(value: str) -> DataElement:
|
||||
def text_string(value: bytes) -> DataElement:
|
||||
return DataElement(DataElement.TEXT_STRING, value)
|
||||
|
||||
@staticmethod
|
||||
@@ -376,7 +376,7 @@ class DataElement:
|
||||
raise ValueError('invalid value_size')
|
||||
elif self.type == DataElement.UUID:
|
||||
data = bytes(reversed(bytes(self.value)))
|
||||
elif self.type in (DataElement.TEXT_STRING, DataElement.URL):
|
||||
elif self.type == DataElement.URL:
|
||||
data = self.value.encode('utf8')
|
||||
elif self.type == DataElement.BOOLEAN:
|
||||
data = bytes([1 if self.value else 0])
|
||||
@@ -758,7 +758,7 @@ class SDP_ServiceSearchAttributeResponse(SDP_PDU):
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class Client:
|
||||
channel: Optional[l2cap.Channel]
|
||||
channel: Optional[l2cap.ClassicChannel]
|
||||
|
||||
def __init__(self, device: Device) -> None:
|
||||
self.device = device
|
||||
@@ -766,8 +766,9 @@ class Client:
|
||||
self.channel = None
|
||||
|
||||
async def connect(self, connection: Connection) -> None:
|
||||
result = await self.device.l2cap_channel_manager.connect(connection, SDP_PSM)
|
||||
self.channel = result
|
||||
self.channel = await connection.create_l2cap_channel(
|
||||
spec=l2cap.ClassicChannelSpec(SDP_PSM)
|
||||
)
|
||||
|
||||
async def disconnect(self) -> None:
|
||||
if self.channel:
|
||||
@@ -921,7 +922,7 @@ class Client:
|
||||
# -----------------------------------------------------------------------------
|
||||
class Server:
|
||||
CONTINUATION_STATE = bytes([0x01, 0x43])
|
||||
channel: Optional[l2cap.Channel]
|
||||
channel: Optional[l2cap.ClassicChannel]
|
||||
Service = NewType('Service', List[ServiceAttribute])
|
||||
service_records: Dict[int, Service]
|
||||
current_response: Union[None, bytes, Tuple[int, List[int]]]
|
||||
@@ -933,7 +934,9 @@ class Server:
|
||||
self.current_response = None
|
||||
|
||||
def register(self, l2cap_channel_manager: l2cap.ChannelManager) -> None:
|
||||
l2cap_channel_manager.register_server(SDP_PSM, self.on_connection)
|
||||
l2cap_channel_manager.create_classic_server(
|
||||
spec=l2cap.ClassicChannelSpec(psm=SDP_PSM), handler=self.on_connection
|
||||
)
|
||||
|
||||
def send_response(self, response):
|
||||
logger.debug(f'{color(">>> Sending SDP Response", "blue")}: {response}')
|
||||
|
||||
@@ -97,10 +97,13 @@ async def open_android_emulator_transport(spec: Optional[str]) -> Transport:
|
||||
raise ValueError('invalid mode')
|
||||
|
||||
# Create the transport object
|
||||
transport = PumpedTransport(
|
||||
PumpedPacketSource(hci_device.read),
|
||||
PumpedPacketSink(hci_device.write),
|
||||
channel.close,
|
||||
class EmulatorTransport(PumpedTransport):
|
||||
async def close(self):
|
||||
await super().close()
|
||||
await channel.close()
|
||||
|
||||
transport = EmulatorTransport(
|
||||
PumpedPacketSource(hci_device.read), PumpedPacketSink(hci_device.write)
|
||||
)
|
||||
transport.start()
|
||||
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
import asyncio
|
||||
import atexit
|
||||
import logging
|
||||
import grpc.aio
|
||||
import os
|
||||
import pathlib
|
||||
import sys
|
||||
from typing import Optional
|
||||
from typing import Dict, Optional
|
||||
|
||||
import grpc.aio
|
||||
|
||||
from .common import (
|
||||
ParserSource,
|
||||
@@ -33,8 +34,8 @@ from .common import (
|
||||
)
|
||||
|
||||
# pylint: disable=no-name-in-module
|
||||
from .grpc_protobuf.packet_streamer_pb2_grpc import PacketStreamerStub
|
||||
from .grpc_protobuf.packet_streamer_pb2_grpc import (
|
||||
PacketStreamerStub,
|
||||
PacketStreamerServicer,
|
||||
add_PacketStreamerServicer_to_server,
|
||||
)
|
||||
@@ -43,6 +44,7 @@ from .grpc_protobuf.hci_packet_pb2 import HCIPacket
|
||||
from .grpc_protobuf.startup_pb2 import Chip, ChipInfo
|
||||
from .grpc_protobuf.common_pb2 import ChipKind
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -74,14 +76,20 @@ def get_ini_dir() -> Optional[pathlib.Path]:
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def find_grpc_port() -> int:
|
||||
def ini_file_name(instance_number: int) -> str:
|
||||
suffix = f'_{instance_number}' if instance_number > 0 else ''
|
||||
return f'netsim{suffix}.ini'
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def find_grpc_port(instance_number: int) -> int:
|
||||
if not (ini_dir := get_ini_dir()):
|
||||
logger.debug('no known directory for .ini file')
|
||||
return 0
|
||||
|
||||
ini_file = ini_dir / 'netsim.ini'
|
||||
ini_file = ini_dir / ini_file_name(instance_number)
|
||||
logger.debug(f'Looking for .ini file at {ini_file}')
|
||||
if ini_file.is_file():
|
||||
logger.debug(f'Found .ini file at {ini_file}')
|
||||
with open(ini_file, 'r') as ini_file_data:
|
||||
for line in ini_file_data.readlines():
|
||||
if '=' in line:
|
||||
@@ -90,12 +98,14 @@ def find_grpc_port() -> int:
|
||||
logger.debug(f'gRPC port = {value}')
|
||||
return int(value)
|
||||
|
||||
logger.debug('no grpc.port property found in .ini file')
|
||||
|
||||
# Not found
|
||||
return 0
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
def publish_grpc_port(grpc_port) -> bool:
|
||||
def publish_grpc_port(grpc_port: int, instance_number: int) -> bool:
|
||||
if not (ini_dir := get_ini_dir()):
|
||||
logger.debug('no known directory for .ini file')
|
||||
return False
|
||||
@@ -104,7 +114,7 @@ def publish_grpc_port(grpc_port) -> bool:
|
||||
logger.debug('ini directory does not exist')
|
||||
return False
|
||||
|
||||
ini_file = ini_dir / 'netsim.ini'
|
||||
ini_file = ini_dir / ini_file_name(instance_number)
|
||||
try:
|
||||
ini_file.write_text(f'grpc.port={grpc_port}\n')
|
||||
logger.debug(f"published gRPC port at {ini_file}")
|
||||
@@ -122,14 +132,15 @@ def publish_grpc_port(grpc_port) -> bool:
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def open_android_netsim_controller_transport(
|
||||
server_host: Optional[str], server_port: int
|
||||
server_host: Optional[str], server_port: int, options: Dict[str, str]
|
||||
) -> Transport:
|
||||
if not server_port:
|
||||
raise ValueError('invalid port')
|
||||
if server_host == '_' or not server_host:
|
||||
server_host = 'localhost'
|
||||
|
||||
if not publish_grpc_port(server_port):
|
||||
instance_number = int(options.get('instance', "0"))
|
||||
if not publish_grpc_port(server_port, instance_number):
|
||||
logger.warning("unable to publish gRPC port")
|
||||
|
||||
class HciDevice:
|
||||
@@ -186,15 +197,12 @@ async def open_android_netsim_controller_transport(
|
||||
logger.debug(f'<<< PACKET: {data.hex()}')
|
||||
self.on_data_received(data)
|
||||
|
||||
def send_packet(self, data):
|
||||
async def send():
|
||||
await self.context.write(
|
||||
PacketResponse(
|
||||
hci_packet=HCIPacket(packet_type=data[0], packet=data[1:])
|
||||
)
|
||||
async def send_packet(self, data):
|
||||
return await self.context.write(
|
||||
PacketResponse(
|
||||
hci_packet=HCIPacket(packet_type=data[0], packet=data[1:])
|
||||
)
|
||||
|
||||
self.loop.create_task(send())
|
||||
)
|
||||
|
||||
def terminate(self):
|
||||
self.task.cancel()
|
||||
@@ -228,17 +236,17 @@ async def open_android_netsim_controller_transport(
|
||||
logger.debug('gRPC server cancelled')
|
||||
await self.grpc_server.stop(None)
|
||||
|
||||
def on_packet(self, packet):
|
||||
async def send_packet(self, packet):
|
||||
if not self.device:
|
||||
logger.debug('no device, dropping packet')
|
||||
return
|
||||
|
||||
self.device.send_packet(packet)
|
||||
return await self.device.send_packet(packet)
|
||||
|
||||
async def StreamPackets(self, _request_iterator, context):
|
||||
logger.debug('StreamPackets request')
|
||||
|
||||
# Check that we won't already have a device
|
||||
# Check that we don't already have a device
|
||||
if self.device:
|
||||
logger.debug('busy, already serving a device')
|
||||
return PacketResponse(error='Busy')
|
||||
@@ -261,15 +269,42 @@ async def open_android_netsim_controller_transport(
|
||||
await server.start()
|
||||
asyncio.get_running_loop().create_task(server.serve())
|
||||
|
||||
class GrpcServerTransport(Transport):
|
||||
async def close(self):
|
||||
await super().close()
|
||||
|
||||
return GrpcServerTransport(server, server)
|
||||
sink = PumpedPacketSink(server.send_packet)
|
||||
sink.start()
|
||||
return Transport(server, sink)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def open_android_netsim_host_transport(server_host, server_port, options):
|
||||
async def open_android_netsim_host_transport_with_address(
|
||||
server_host: Optional[str],
|
||||
server_port: int,
|
||||
options: Optional[Dict[str, str]] = None,
|
||||
):
|
||||
if server_host == '_' or not server_host:
|
||||
server_host = 'localhost'
|
||||
|
||||
if not server_port:
|
||||
# Look for the gRPC config in a .ini file
|
||||
instance_number = 0 if options is None else int(options.get('instance', '0'))
|
||||
server_port = find_grpc_port(instance_number)
|
||||
if not server_port:
|
||||
raise RuntimeError('gRPC server port not found')
|
||||
|
||||
# Connect to the gRPC server
|
||||
server_address = f'{server_host}:{server_port}'
|
||||
logger.debug(f'Connecting to gRPC server at {server_address}')
|
||||
channel = grpc.aio.insecure_channel(server_address)
|
||||
|
||||
return await open_android_netsim_host_transport_with_channel(
|
||||
channel,
|
||||
options,
|
||||
)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def open_android_netsim_host_transport_with_channel(
|
||||
channel, options: Optional[Dict[str, str]] = None
|
||||
):
|
||||
# Wrapper for I/O operations
|
||||
class HciDevice:
|
||||
def __init__(self, name, manufacturer, hci_device):
|
||||
@@ -288,10 +323,12 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
|
||||
async def read(self):
|
||||
response = await self.hci_device.read()
|
||||
response_type = response.WhichOneof('response_type')
|
||||
|
||||
if response_type == 'error':
|
||||
logger.warning(f'received error: {response.error}')
|
||||
raise RuntimeError(response.error)
|
||||
elif response_type == 'hci_packet':
|
||||
|
||||
if response_type == 'hci_packet':
|
||||
return (
|
||||
bytes([response.hci_packet.packet_type])
|
||||
+ response.hci_packet.packet
|
||||
@@ -306,24 +343,9 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
|
||||
)
|
||||
)
|
||||
|
||||
name = options.get('name', DEFAULT_NAME)
|
||||
name = DEFAULT_NAME if options is None else options.get('name', DEFAULT_NAME)
|
||||
manufacturer = DEFAULT_MANUFACTURER
|
||||
|
||||
if server_host == '_' or not server_host:
|
||||
server_host = 'localhost'
|
||||
|
||||
if not server_port:
|
||||
# Look for the gRPC config in a .ini file
|
||||
server_host = 'localhost'
|
||||
server_port = find_grpc_port()
|
||||
if not server_port:
|
||||
raise RuntimeError('gRPC server port not found')
|
||||
|
||||
# Connect to the gRPC server
|
||||
server_address = f'{server_host}:{server_port}'
|
||||
logger.debug(f'Connecting to gRPC server at {server_address}')
|
||||
channel = grpc.aio.insecure_channel(server_address)
|
||||
|
||||
# Connect as a host
|
||||
service = PacketStreamerStub(channel)
|
||||
hci_device = HciDevice(
|
||||
@@ -334,10 +356,14 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
|
||||
await hci_device.start()
|
||||
|
||||
# Create the transport object
|
||||
transport = PumpedTransport(
|
||||
class GrpcTransport(PumpedTransport):
|
||||
async def close(self):
|
||||
await super().close()
|
||||
await channel.close()
|
||||
|
||||
transport = GrpcTransport(
|
||||
PumpedPacketSource(hci_device.read),
|
||||
PumpedPacketSink(hci_device.write),
|
||||
channel.close,
|
||||
)
|
||||
transport.start()
|
||||
|
||||
@@ -345,7 +371,7 @@ async def open_android_netsim_host_transport(server_host, server_port, options):
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def open_android_netsim_transport(spec):
|
||||
async def open_android_netsim_transport(spec: Optional[str]) -> Transport:
|
||||
'''
|
||||
Open a transport connection as a client or server, implementing Android's `netsim`
|
||||
simulator protocol over gRPC.
|
||||
@@ -359,6 +385,11 @@ async def open_android_netsim_transport(spec):
|
||||
to connect *to* a netsim server (netsim is the controller), or accept
|
||||
connections *as* a netsim-compatible server.
|
||||
|
||||
instance=<n>
|
||||
Specifies an instance number, with <n> > 0. This is used to determine which
|
||||
.init file to use. In `host` mode, it is ignored when the <host>:<port>
|
||||
specifier is present, since in that case no .ini file is used.
|
||||
|
||||
In `host` mode:
|
||||
The <host>:<port> part is optional. When not specified, the transport
|
||||
looks for a netsim .ini file, from which it will read the `grpc.backend.port`
|
||||
@@ -387,14 +418,15 @@ async def open_android_netsim_transport(spec):
|
||||
params = spec.split(',') if spec else []
|
||||
if params and ':' in params[0]:
|
||||
# Explicit <host>:<port>
|
||||
host, port = params[0].split(':')
|
||||
host, port_str = params[0].split(':')
|
||||
port = int(port_str)
|
||||
params_offset = 1
|
||||
else:
|
||||
host = None
|
||||
port = 0
|
||||
params_offset = 0
|
||||
|
||||
options = {}
|
||||
options: Dict[str, str] = {}
|
||||
for param in params[params_offset:]:
|
||||
if '=' not in param:
|
||||
raise ValueError('invalid parameter, expected <name>=<value>')
|
||||
@@ -403,10 +435,12 @@ async def open_android_netsim_transport(spec):
|
||||
|
||||
mode = options.get('mode', 'host')
|
||||
if mode == 'host':
|
||||
return await open_android_netsim_host_transport(host, port, options)
|
||||
return await open_android_netsim_host_transport_with_address(
|
||||
host, port, options
|
||||
)
|
||||
if mode == 'controller':
|
||||
if host is None:
|
||||
raise ValueError('<host>:<port> missing')
|
||||
return await open_android_netsim_controller_transport(host, port)
|
||||
return await open_android_netsim_controller_transport(host, port, options)
|
||||
|
||||
raise ValueError('invalid mode option')
|
||||
|
||||
@@ -339,8 +339,9 @@ class PumpedPacketSource(ParserSource):
|
||||
try:
|
||||
packet = await self.receive_function()
|
||||
self.parser.feed_data(packet)
|
||||
except asyncio.exceptions.CancelledError:
|
||||
except asyncio.CancelledError:
|
||||
logger.debug('source pump task done')
|
||||
self.terminated.set_result(None)
|
||||
break
|
||||
except Exception as error:
|
||||
logger.warning(f'exception while waiting for packet: {error}')
|
||||
@@ -370,7 +371,7 @@ class PumpedPacketSink:
|
||||
try:
|
||||
packet = await self.packet_queue.get()
|
||||
await self.send_function(packet)
|
||||
except asyncio.exceptions.CancelledError:
|
||||
except asyncio.CancelledError:
|
||||
logger.debug('sink pump task done')
|
||||
break
|
||||
except Exception as error:
|
||||
@@ -393,19 +394,13 @@ class PumpedTransport(Transport):
|
||||
self,
|
||||
source: PumpedPacketSource,
|
||||
sink: PumpedPacketSink,
|
||||
close_function,
|
||||
) -> None:
|
||||
super().__init__(source, sink)
|
||||
self.close_function = close_function
|
||||
|
||||
def start(self) -> None:
|
||||
self.source.start()
|
||||
self.sink.start()
|
||||
|
||||
async def close(self) -> None:
|
||||
await super().close()
|
||||
await self.close_function()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class SnoopingTransport(Transport):
|
||||
|
||||
@@ -24,9 +24,10 @@ import platform
|
||||
|
||||
import usb1
|
||||
|
||||
from .common import Transport, ParserSource
|
||||
from .. import hci
|
||||
from ..colors import color
|
||||
from bumble.transport.common import Transport, ParserSource
|
||||
from bumble import hci
|
||||
from bumble.colors import color
|
||||
from bumble.utils import AsyncRunner
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -113,7 +114,7 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
def __init__(self, device, acl_out):
|
||||
self.device = device
|
||||
self.acl_out = acl_out
|
||||
self.transfer = device.getTransfer()
|
||||
self.acl_out_transfer = device.getTransfer()
|
||||
self.packets = collections.deque() # Queue of packets waiting to be sent
|
||||
self.loop = asyncio.get_running_loop()
|
||||
self.cancel_done = self.loop.create_future()
|
||||
@@ -137,21 +138,20 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
# The queue was previously empty, re-prime the pump
|
||||
self.process_queue()
|
||||
|
||||
def on_packet_sent(self, transfer):
|
||||
def transfer_callback(self, transfer):
|
||||
status = transfer.getStatus()
|
||||
# logger.debug(f'<<< USB out transfer callback: status={status}')
|
||||
|
||||
# pylint: disable=no-member
|
||||
if status == usb1.TRANSFER_COMPLETED:
|
||||
self.loop.call_soon_threadsafe(self.on_packet_sent_)
|
||||
self.loop.call_soon_threadsafe(self.on_packet_sent)
|
||||
elif status == usb1.TRANSFER_CANCELLED:
|
||||
self.loop.call_soon_threadsafe(self.cancel_done.set_result, None)
|
||||
else:
|
||||
logger.warning(
|
||||
color(f'!!! out transfer not completed: status={status}', 'red')
|
||||
color(f'!!! OUT transfer not completed: status={status}', 'red')
|
||||
)
|
||||
|
||||
def on_packet_sent_(self):
|
||||
def on_packet_sent(self):
|
||||
if self.packets:
|
||||
self.packets.popleft()
|
||||
self.process_queue()
|
||||
@@ -163,22 +163,20 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
packet = self.packets[0]
|
||||
packet_type = packet[0]
|
||||
if packet_type == hci.HCI_ACL_DATA_PACKET:
|
||||
self.transfer.setBulk(
|
||||
self.acl_out, packet[1:], callback=self.on_packet_sent
|
||||
self.acl_out_transfer.setBulk(
|
||||
self.acl_out, packet[1:], callback=self.transfer_callback
|
||||
)
|
||||
logger.debug('submit ACL')
|
||||
self.transfer.submit()
|
||||
self.acl_out_transfer.submit()
|
||||
elif packet_type == hci.HCI_COMMAND_PACKET:
|
||||
self.transfer.setControl(
|
||||
self.acl_out_transfer.setControl(
|
||||
USB_RECIPIENT_DEVICE | USB_REQUEST_TYPE_CLASS,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
packet[1:],
|
||||
callback=self.on_packet_sent,
|
||||
callback=self.transfer_callback,
|
||||
)
|
||||
logger.debug('submit COMMAND')
|
||||
self.transfer.submit()
|
||||
self.acl_out_transfer.submit()
|
||||
else:
|
||||
logger.warning(color(f'unsupported packet type {packet_type}', 'red'))
|
||||
|
||||
@@ -193,11 +191,11 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
self.packets.clear()
|
||||
|
||||
# If we have a transfer in flight, cancel it
|
||||
if self.transfer.isSubmitted():
|
||||
if self.acl_out_transfer.isSubmitted():
|
||||
# Try to cancel the transfer, but that may fail because it may have
|
||||
# already completed
|
||||
try:
|
||||
self.transfer.cancel()
|
||||
self.acl_out_transfer.cancel()
|
||||
|
||||
logger.debug('waiting for OUT transfer cancellation to be done...')
|
||||
await self.cancel_done
|
||||
@@ -206,27 +204,22 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
logger.debug('OUT transfer likely already completed')
|
||||
|
||||
class UsbPacketSource(asyncio.Protocol, ParserSource):
|
||||
def __init__(self, context, device, metadata, acl_in, events_in):
|
||||
def __init__(self, device, metadata, acl_in, events_in):
|
||||
super().__init__()
|
||||
self.context = context
|
||||
self.device = device
|
||||
self.metadata = metadata
|
||||
self.acl_in = acl_in
|
||||
self.acl_in_transfer = None
|
||||
self.events_in = events_in
|
||||
self.events_in_transfer = None
|
||||
self.loop = asyncio.get_running_loop()
|
||||
self.queue = asyncio.Queue()
|
||||
self.dequeue_task = None
|
||||
self.closed = False
|
||||
self.event_loop_done = self.loop.create_future()
|
||||
self.cancel_done = {
|
||||
hci.HCI_EVENT_PACKET: self.loop.create_future(),
|
||||
hci.HCI_ACL_DATA_PACKET: self.loop.create_future(),
|
||||
}
|
||||
self.events_in_transfer = None
|
||||
self.acl_in_transfer = None
|
||||
|
||||
# Create a thread to process events
|
||||
self.event_thread = threading.Thread(target=self.run)
|
||||
self.closed = False
|
||||
|
||||
def start(self):
|
||||
# Set up transfer objects for input
|
||||
@@ -234,7 +227,7 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
self.events_in_transfer.setInterrupt(
|
||||
self.events_in,
|
||||
READ_SIZE,
|
||||
callback=self.on_packet_received,
|
||||
callback=self.transfer_callback,
|
||||
user_data=hci.HCI_EVENT_PACKET,
|
||||
)
|
||||
self.events_in_transfer.submit()
|
||||
@@ -243,22 +236,23 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
self.acl_in_transfer.setBulk(
|
||||
self.acl_in,
|
||||
READ_SIZE,
|
||||
callback=self.on_packet_received,
|
||||
callback=self.transfer_callback,
|
||||
user_data=hci.HCI_ACL_DATA_PACKET,
|
||||
)
|
||||
self.acl_in_transfer.submit()
|
||||
|
||||
self.dequeue_task = self.loop.create_task(self.dequeue())
|
||||
self.event_thread.start()
|
||||
|
||||
def on_packet_received(self, transfer):
|
||||
@property
|
||||
def usb_transfer_submitted(self):
|
||||
return (
|
||||
self.events_in_transfer.isSubmitted()
|
||||
or self.acl_in_transfer.isSubmitted()
|
||||
)
|
||||
|
||||
def transfer_callback(self, transfer):
|
||||
packet_type = transfer.getUserData()
|
||||
status = transfer.getStatus()
|
||||
# logger.debug(
|
||||
# f'<<< USB IN transfer callback: status={status} '
|
||||
# f'packet_type={packet_type} '
|
||||
# f'length={transfer.getActualLength()}'
|
||||
# )
|
||||
|
||||
# pylint: disable=no-member
|
||||
if status == usb1.TRANSFER_COMPLETED:
|
||||
@@ -267,18 +261,18 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
+ transfer.getBuffer()[: transfer.getActualLength()]
|
||||
)
|
||||
self.loop.call_soon_threadsafe(self.queue.put_nowait, packet)
|
||||
|
||||
# Re-submit the transfer so we can receive more data
|
||||
transfer.submit()
|
||||
elif status == usb1.TRANSFER_CANCELLED:
|
||||
self.loop.call_soon_threadsafe(
|
||||
self.cancel_done[packet_type].set_result, None
|
||||
)
|
||||
return
|
||||
else:
|
||||
logger.warning(
|
||||
color(f'!!! transfer not completed: status={status}', 'red')
|
||||
color(f'!!! IN transfer not completed: status={status}', 'red')
|
||||
)
|
||||
|
||||
# Re-submit the transfer so we can receive more data
|
||||
transfer.submit()
|
||||
self.loop.call_soon_threadsafe(self.on_transport_lost)
|
||||
|
||||
async def dequeue(self):
|
||||
while not self.closed:
|
||||
@@ -288,21 +282,6 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
return
|
||||
self.parser.feed_data(packet)
|
||||
|
||||
def run(self):
|
||||
logger.debug('starting USB event loop')
|
||||
while (
|
||||
self.events_in_transfer.isSubmitted()
|
||||
or self.acl_in_transfer.isSubmitted()
|
||||
):
|
||||
# pylint: disable=no-member
|
||||
try:
|
||||
self.context.handleEvents()
|
||||
except usb1.USBErrorInterrupted:
|
||||
pass
|
||||
|
||||
logger.debug('USB event loop done')
|
||||
self.loop.call_soon_threadsafe(self.event_loop_done.set_result, None)
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
@@ -331,15 +310,14 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
f'IN[{packet_type}] transfer likely already completed'
|
||||
)
|
||||
|
||||
# Wait for the thread to terminate
|
||||
await self.event_loop_done
|
||||
|
||||
class UsbTransport(Transport):
|
||||
def __init__(self, context, device, interface, setting, source, sink):
|
||||
super().__init__(source, sink)
|
||||
self.context = context
|
||||
self.device = device
|
||||
self.interface = interface
|
||||
self.loop = asyncio.get_running_loop()
|
||||
self.event_loop_done = self.loop.create_future()
|
||||
|
||||
# Get exclusive access
|
||||
device.claimInterface(interface)
|
||||
@@ -352,6 +330,22 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
source.start()
|
||||
sink.start()
|
||||
|
||||
# Create a thread to process events
|
||||
self.event_thread = threading.Thread(target=self.run)
|
||||
self.event_thread.start()
|
||||
|
||||
def run(self):
|
||||
logger.debug('starting USB event loop')
|
||||
while self.source.usb_transfer_submitted:
|
||||
# pylint: disable=no-member
|
||||
try:
|
||||
self.context.handleEvents()
|
||||
except usb1.USBErrorInterrupted:
|
||||
pass
|
||||
|
||||
logger.debug('USB event loop done')
|
||||
self.loop.call_soon_threadsafe(self.event_loop_done.set_result, None)
|
||||
|
||||
async def close(self):
|
||||
self.source.close()
|
||||
self.sink.close()
|
||||
@@ -361,6 +355,9 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
self.device.close()
|
||||
self.context.close()
|
||||
|
||||
# Wait for the thread to terminate
|
||||
await self.event_loop_done
|
||||
|
||||
# Find the device according to the spec moniker
|
||||
load_libusb()
|
||||
context = usb1.USBContext()
|
||||
@@ -540,7 +537,7 @@ async def open_usb_transport(spec: str) -> Transport:
|
||||
except usb1.USBError:
|
||||
logger.warning('failed to set configuration')
|
||||
|
||||
source = UsbPacketSource(context, device, device_metadata, acl_in, events_in)
|
||||
source = UsbPacketSource(device, device_metadata, acl_in, events_in)
|
||||
sink = UsbPacketSink(device, acl_out)
|
||||
return UsbTransport(context, device, interface, setting, source, sink)
|
||||
except usb1.USBError as error:
|
||||
|
||||
@@ -31,19 +31,21 @@ async def open_ws_client_transport(spec: str) -> Transport:
|
||||
'''
|
||||
Open a WebSocket client transport.
|
||||
The parameter string has this syntax:
|
||||
<remote-host>:<remote-port>
|
||||
<websocket-url>
|
||||
|
||||
Example: 127.0.0.1:9001
|
||||
Example: ws://localhost:7681/v1/websocket/bt
|
||||
'''
|
||||
|
||||
remote_host, remote_port = spec.split(':')
|
||||
uri = f'ws://{remote_host}:{remote_port}'
|
||||
websocket = await websockets.client.connect(uri)
|
||||
websocket = await websockets.client.connect(spec)
|
||||
|
||||
transport = PumpedTransport(
|
||||
class WsTransport(PumpedTransport):
|
||||
async def close(self):
|
||||
await super().close()
|
||||
await websocket.close()
|
||||
|
||||
transport = WsTransport(
|
||||
PumpedPacketSource(websocket.recv),
|
||||
PumpedPacketSink(websocket.send),
|
||||
websocket.close,
|
||||
)
|
||||
transport.start()
|
||||
return transport
|
||||
|
||||
@@ -21,6 +21,7 @@ import logging
|
||||
import traceback
|
||||
import collections
|
||||
import sys
|
||||
import warnings
|
||||
from typing import (
|
||||
Awaitable,
|
||||
Set,
|
||||
@@ -33,7 +34,7 @@ from typing import (
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
from functools import wraps
|
||||
from functools import wraps, partial
|
||||
from pyee import EventEmitter
|
||||
|
||||
from .colors import color
|
||||
@@ -410,3 +411,36 @@ class FlowControlAsyncPipe:
|
||||
self.resume_source()
|
||||
|
||||
self.check_pump()
|
||||
|
||||
|
||||
async def async_call(function, *args, **kwargs):
|
||||
"""
|
||||
Immediately calls the function with provided args and kwargs, wrapping it in an async function.
|
||||
Rust's `pyo3_asyncio` library needs functions to be marked async to properly inject a running loop.
|
||||
|
||||
result = await async_call(some_function, ...)
|
||||
"""
|
||||
return function(*args, **kwargs)
|
||||
|
||||
|
||||
def wrap_async(function):
|
||||
"""
|
||||
Wraps the provided function in an async function.
|
||||
"""
|
||||
return partial(async_call, function)
|
||||
|
||||
|
||||
def deprecated(msg: str):
|
||||
"""
|
||||
Throw deprecation warning before execution
|
||||
"""
|
||||
|
||||
def wrapper(function):
|
||||
@wraps(function)
|
||||
def inner(*args, **kwargs):
|
||||
warnings.warn(msg, DeprecationWarning)
|
||||
return function(*args, **kwargs)
|
||||
|
||||
return inner
|
||||
|
||||
return wrapper
|
||||
|
||||
@@ -10,7 +10,7 @@ nav:
|
||||
- Contributing: development/contributing.md
|
||||
- Code Style: development/code_style.md
|
||||
- Use Cases:
|
||||
- Overview: use_cases/index.md
|
||||
- use_cases/index.md
|
||||
- Use Case 1: use_cases/use_case_1.md
|
||||
- Use Case 2: use_cases/use_case_2.md
|
||||
- Use Case 3: use_cases/use_case_3.md
|
||||
@@ -23,7 +23,7 @@ nav:
|
||||
- GATT: components/gatt.md
|
||||
- Security Manager: components/security_manager.md
|
||||
- Transports:
|
||||
- Overview: transports/index.md
|
||||
- transports/index.md
|
||||
- Serial: transports/serial.md
|
||||
- USB: transports/usb.md
|
||||
- PTY: transports/pty.md
|
||||
@@ -37,14 +37,14 @@ nav:
|
||||
- Android Emulator: transports/android_emulator.md
|
||||
- File: transports/file.md
|
||||
- Drivers:
|
||||
- Overview: drivers/index.md
|
||||
- drivers/index.md
|
||||
- Realtek: drivers/realtek.md
|
||||
- API:
|
||||
- Guide: api/guide.md
|
||||
- Examples: api/examples.md
|
||||
- Reference: api/reference.md
|
||||
- Apps & Tools:
|
||||
- Overview: apps_and_tools/index.md
|
||||
- apps_and_tools/index.md
|
||||
- Console: apps_and_tools/console.md
|
||||
- Bench: apps_and_tools/bench.md
|
||||
- Speaker: apps_and_tools/speaker.md
|
||||
@@ -57,16 +57,24 @@ nav:
|
||||
- USB Probe: apps_and_tools/usb_probe.md
|
||||
- Link Relay: apps_and_tools/link_relay.md
|
||||
- Hardware:
|
||||
- Overview: hardware/index.md
|
||||
- hardware/index.md
|
||||
- Platforms:
|
||||
- Overview: platforms/index.md
|
||||
- platforms/index.md
|
||||
- macOS: platforms/macos.md
|
||||
- Linux: platforms/linux.md
|
||||
- Windows: platforms/windows.md
|
||||
- Android: platforms/android.md
|
||||
- Zephyr: platforms/zephyr.md
|
||||
- Examples:
|
||||
- Overview: examples/index.md
|
||||
- examples/index.md
|
||||
- Extras:
|
||||
- extras/index.md
|
||||
- Android Remote HCI: extras/android_remote_hci.md
|
||||
- Hive:
|
||||
- hive/index.md
|
||||
- Speaker: hive/web/speaker/speaker.html
|
||||
- Scanner: hive/web/scanner/scanner.html
|
||||
- Heart Rate Monitor: hive/web/heart_rate_monitor/heart_rate_monitor.html
|
||||
|
||||
copyright: Copyright 2021-2023 Google LLC
|
||||
|
||||
@@ -75,6 +83,8 @@ theme:
|
||||
logo: 'images/logo.png'
|
||||
favicon: 'images/favicon.ico'
|
||||
custom_dir: 'theme'
|
||||
features:
|
||||
- navigation.indexes
|
||||
|
||||
plugins:
|
||||
- mkdocstrings:
|
||||
@@ -99,6 +109,8 @@ markdown_extensions:
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:materialx.emoji.twemoji
|
||||
emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
- codehilite:
|
||||
guess_lang: false
|
||||
- toc:
|
||||
|
||||
141
docs/mkdocs/src/extras/android_remote_hci.md
Normal file
141
docs/mkdocs/src/extras/android_remote_hci.md
Normal file
@@ -0,0 +1,141 @@
|
||||
ANDROID REMOTE HCI APP
|
||||
======================
|
||||
|
||||
This application allows using an android phone's built-in Bluetooth controller with
|
||||
a Bumble host stack running outside the phone (typically a development laptop or desktop).
|
||||
The app runs an HCI proxy between a TCP socket on the "outside" and the Bluetooth HCI HAL
|
||||
on the "inside". (See [this page](https://source.android.com/docs/core/connect/bluetooth) for a high level
|
||||
description of the Android Bluetooth HCI HAL).
|
||||
The HCI packets received on the TCP socket are forwarded to the phone's controller, and the
|
||||
packets coming from the controller are forwarded to the TCP socket.
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
You can build the app by running `./gradlew build` (use `gradlew.bat` on Windows) from the `RemoteHCI` top level directory.
|
||||
You can also build with Android Studio: open the `RemoteHCI` project. You can build and/or debug from there.
|
||||
|
||||
If the build succeeds, you can find the app APKs (debug and release) at:
|
||||
|
||||
* [Release] ``app/build/outputs/apk/release/app-release-unsigned.apk``
|
||||
* [Debug] ``app/build/outputs/apk/debug/app-debug.apk``
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
### Preconditions
|
||||
When the proxy starts (tapping the "Start" button in the app's main activity), it will try to
|
||||
bind to the Bluetooth HAL. This requires disabling SELinux temporarily, and being the only HAL client.
|
||||
|
||||
#### Disabling SELinux
|
||||
Binding to the Bluetooth HCI HAL requires certain SELinux permissions that can't simply be changed
|
||||
on a device without rebuilding its system image. To bypass these restrictions, you will need
|
||||
to disable SELinux on your phone (please be aware that this is global, not just for the proxy app,
|
||||
so proceed with caution).
|
||||
In order to disable SELinux, you need to root the phone (it may be advisable to do this on a
|
||||
development phone).
|
||||
|
||||
!!! tip "Disabling SELinux Temporarily"
|
||||
Restart `adb` as root:
|
||||
```bash
|
||||
$ adb root
|
||||
```
|
||||
|
||||
Then disable SELinux
|
||||
```bash
|
||||
$ adb shell setenforce 0
|
||||
```
|
||||
|
||||
Once you're done using the proxy, you can restore SELinux, if you need to, with
|
||||
```bash
|
||||
$ adb shell setenforce 1
|
||||
```
|
||||
|
||||
This state will also reset to the normal SELinux enforcement when you reboot.
|
||||
|
||||
#### Stopping the bluetooth process
|
||||
Since the Bluetooth HAL service can only accept one client, and that in normal conditions
|
||||
that client is the Android's bluetooth stack, it is required to first shut down the
|
||||
Android bluetooth stack process.
|
||||
|
||||
!!! tip "Checking if the Bluetooth process is running"
|
||||
```bash
|
||||
$ adb shell "ps -A | grep com.google.android.bluetooth"
|
||||
```
|
||||
If the process is running, you will get a line like:
|
||||
```
|
||||
bluetooth 10759 876 17455796 136620 do_epoll_wait 0 S com.google.android.bluetooth
|
||||
```
|
||||
If you don't, it means that the process is not running and you are clear to proceed.
|
||||
|
||||
Simply turning Bluetooth off from the phone's settings does not ensure that the bluetooth process will exit.
|
||||
If the bluetooth process is still running after toggling Bluetooth off from the settings, you may try enabling
|
||||
Airplane Mode, then rebooting. The bluetooth process should, in theory, not restart after the reboot.
|
||||
|
||||
!!! tip "Stopping the bluetooth process with adb"
|
||||
```bash
|
||||
$ adb shell cmd bluetooth_manager disable
|
||||
```
|
||||
|
||||
### Starting the app
|
||||
You can start the app from the Android launcher, from Android Studio, or with `adb`
|
||||
|
||||
#### Launching from the launcher
|
||||
Just tap the app icon on the launcher, check the TCP port that is configured, and tap
|
||||
the "Start" button.
|
||||
|
||||
#### Launching with `adb`
|
||||
Using the `am` command, you can start the activity, and pass it arguments so that you can
|
||||
automatically start the proxy, and/or set the port number.
|
||||
|
||||
!!! tip "Launching from adb with auto-start"
|
||||
```bash
|
||||
$ adb shell am start -n com.github.google.bumble.remotehci/.MainActivity --ez autostart true
|
||||
```
|
||||
|
||||
!!! tip "Launching from adb with auto-start and a port"
|
||||
In this example, we auto-start the proxy upon launch, with the port set to 9995
|
||||
```bash
|
||||
$ adb shell am start -n com.github.google.bumble.remotehci/.MainActivity --ez autostart true --ei port 9995
|
||||
```
|
||||
|
||||
#### Selecting a TCP port
|
||||
The RemoteHCI app's main activity has a "TCP Port" setting where you can change the port on
|
||||
which the proxy is accepting connections. If the default value isn't suitable, you can
|
||||
change it there (you can also use the special value 0 to let the OS assign a port number for you).
|
||||
|
||||
### Connecting to the proxy
|
||||
To connect the Bumble stack to the proxy, you need to be able to reach the phone's network
|
||||
stack. This can be done over the phone's WiFi connection, or, alternatively, using an `adb`
|
||||
TCP forward (which should be faster than over WiFi).
|
||||
|
||||
!!! tip "Forwarding TCP with `adb`"
|
||||
To connect to the proxy via an `adb` TCP forward, use:
|
||||
```bash
|
||||
$ adb forward tcp:<outside-port> tcp:<inside-port>
|
||||
```
|
||||
Where ``<outside-port>`` is the port number for a listening socket on your laptop or
|
||||
desktop machine, and <inside-port> is the TCP port selected in the app's user interface.
|
||||
Those two ports may be the same, of course.
|
||||
For example, with the default TCP port 9993:
|
||||
```bash
|
||||
$ adb forward tcp:9993 tcp:9993
|
||||
```
|
||||
|
||||
Once you've ensured that you can reach the proxy's TCP port on the phone, either directly or
|
||||
via an `adb` forward, you can then use it as a Bumble transport, using the transport name:
|
||||
``tcp-client:<host>:<port>`` syntax.
|
||||
|
||||
!!! example "Connecting a Bumble client"
|
||||
Connecting the `bumble-controller-info` app to the phone's controller.
|
||||
Assuming you have set up an `adb` forward on port 9993:
|
||||
```bash
|
||||
$ bumble-controller-info tcp-client:localhost:9993
|
||||
```
|
||||
|
||||
Or over WiFi with, in this example, the IP address of the phone being ```192.168.86.27```
|
||||
```bash
|
||||
$ bumble-controller-info tcp-client:192.168.86.27:9993
|
||||
```
|
||||
11
docs/mkdocs/src/extras/index.md
Normal file
11
docs/mkdocs/src/extras/index.md
Normal file
@@ -0,0 +1,11 @@
|
||||
EXTRAS
|
||||
======
|
||||
|
||||
A collection of add-ons, apps and tools, to the Bumble project.
|
||||
|
||||
Android Remote HCI
|
||||
------------------
|
||||
|
||||
Allows using an Android phone's built-in Bluetooth controller with a Bumble
|
||||
stack running on a development machine.
|
||||
See [Android Remote HCI](android_remote_hci.md) for details.
|
||||
@@ -3,7 +3,7 @@ HARDWARE
|
||||
|
||||
The Bumble Host connects to a controller over an [HCI Transport](../transports/index.md).
|
||||
To use a hardware controller attached to the host on which the host application is running, the transport is typically either [HCI over UART](../transports/serial.md) or [HCI over USB](../transports/usb.md).
|
||||
On Linux, the [VHCI Transport](../transports/vhci.md) can be used to communicate with any controller hardware managed by the operating system. Alternatively, a remote controller (a phyiscal controller attached to a remote host) can be used by connecting one of the networked transports (such as the [TCP Client transport](../transports/tcp_client.md), the [TCP Server transport](../transports/tcp_server.md) or the [UDP Transport](../transports/udp.md)) to an [HCI Bridge](../apps_and_tools/hci_bridge) bridging the network transport to a physical controller on a remote host.
|
||||
On Linux, the [VHCI Transport](../transports/vhci.md) can be used to communicate with any controller hardware managed by the operating system. Alternatively, a remote controller (a phyiscal controller attached to a remote host) can be used by connecting one of the networked transports (such as the [TCP Client transport](../transports/tcp_client.md), the [TCP Server transport](../transports/tcp_server.md) or the [UDP Transport](../transports/udp.md)) to an [HCI Bridge](../apps_and_tools/hci_bridge.md) bridging the network transport to a physical controller on a remote host.
|
||||
|
||||
In theory, any controller that is compliant with the HCI over UART or HCI over USB protocols can be used.
|
||||
|
||||
|
||||
59
docs/mkdocs/src/hive/index.md
Normal file
59
docs/mkdocs/src/hive/index.md
Normal file
@@ -0,0 +1,59 @@
|
||||
HIVE
|
||||
====
|
||||
|
||||
Welcome to the Bumble Hive.
|
||||
This is a collection of apps and virtual devices that can run entirely in a browser page.
|
||||
The code for the apps and devices, as well as the Bumble runtime code, runs via [Pyodide](https://pyodide.org/).
|
||||
Pyodide is a Python distribution for the browser and Node.js based on WebAssembly.
|
||||
|
||||
The Bumble stack uses a WebSocket to exchange HCI packets with a virtual or physical
|
||||
Bluetooth controller.
|
||||
|
||||
The apps and devices in the hive can be accessed by following the links below. Each
|
||||
page has a settings button that may be used to configure the WebSocket URL to use for
|
||||
the virtual HCI connection. This will typically be the WebSocket URL for a `netsim`
|
||||
daemon.
|
||||
There is also a [TOML index](index.toml) that can be used by tools to know at which URL to access
|
||||
each of the apps and devices, as well as their names and short descriptions.
|
||||
|
||||
!!! tip "Using `netsim`"
|
||||
When the `netsimd` daemon is running (for example when using the Android Emulator that
|
||||
is included in Android Studio), the daemon listens for connections on a TCP port.
|
||||
To find out what this TCP port is, you can read the `netsim.ini` file that `netsimd`
|
||||
creates, it includes a line with `web.port=<tcp-port>` (for example `web.port=7681`).
|
||||
The location of the `netsim.ini` file is platform-specific.
|
||||
|
||||
=== "macOS"
|
||||
On macOS, the directory where `netsim.ini` is stored is $TMPDIR
|
||||
```bash
|
||||
$ cat $TMPDIR/netsim.ini
|
||||
```
|
||||
|
||||
=== "Linux"
|
||||
On Linux, the directory where `netsim.ini` is stored is $XDG_RUNTIME_DIR
|
||||
```bash
|
||||
$ cat $XDG_RUNTIME_DIR/netsim.ini
|
||||
```
|
||||
|
||||
|
||||
!!! tip "Using a local radio"
|
||||
You can connect the hive virtual apps and devices to a local Bluetooth radio, like,
|
||||
for example, a USB dongle.
|
||||
For that, you need to run a local HCI bridge to bridge a local HCI device to a WebSocket
|
||||
that a web page can connect to.
|
||||
Use the `bumble-hci-bridge` app, with the host transport set to a WebSocket server on an
|
||||
available port (ex: `ws-server:_:7682`) and the controller transport set to the transport
|
||||
name for the radio you want to use (ex: `usb:0` for the first USB dongle)
|
||||
|
||||
|
||||
Applications
|
||||
------------
|
||||
|
||||
* [Scanner](web/scanner/scanner.html) - Scans for BLE devices.
|
||||
|
||||
Virtual Devices
|
||||
---------------
|
||||
|
||||
* [Speaker](web/speaker/speaker.html) - Virtual speaker that plays audio in a browser page.
|
||||
* [Heart Rate Monitor](web/heart_rate_monitor/heart_rate_monitor.html) - Virtual heart rate monitor.
|
||||
|
||||
21
docs/mkdocs/src/hive/index.toml
Normal file
21
docs/mkdocs/src/hive/index.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
version = "1.0.0"
|
||||
base_url = "https://google.github.io/bumble/hive/web"
|
||||
default_hci_query_param = "hci"
|
||||
|
||||
[[index]]
|
||||
name = "speaker"
|
||||
description = "Bumble Virtual Speaker"
|
||||
type = "Device"
|
||||
url = "speaker/speaker.html"
|
||||
|
||||
[[index]]
|
||||
name = "scanner"
|
||||
description = "Simple Scanner Application"
|
||||
type = "Application"
|
||||
url = "scanner/scanner.html"
|
||||
|
||||
[[index]]
|
||||
name = "heart-rate-monitor"
|
||||
description = "Virtual Heart Rate Monitor"
|
||||
type = "Device"
|
||||
url = "heart_rate_monitor/heart_rate_monitor.html"
|
||||
1
docs/mkdocs/src/hive/web/bumble.js
Symbolic link
1
docs/mkdocs/src/hive/web/bumble.js
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../web/bumble.js
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../web/heart_rate_monitor/heart_rate_monitor.html
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../web/heart_rate_monitor/heart_rate_monitor.js
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../web/heart_rate_monitor/heart_rate_monitor.py
|
||||
1
docs/mkdocs/src/hive/web/scanner/scanner.css
Symbolic link
1
docs/mkdocs/src/hive/web/scanner/scanner.css
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../../web/scanner/scanner.css
|
||||
1
docs/mkdocs/src/hive/web/scanner/scanner.html
Symbolic link
1
docs/mkdocs/src/hive/web/scanner/scanner.html
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../../web/scanner/scanner.html
|
||||
1
docs/mkdocs/src/hive/web/scanner/scanner.js
Symbolic link
1
docs/mkdocs/src/hive/web/scanner/scanner.js
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../../web/scanner/scanner.js
|
||||
1
docs/mkdocs/src/hive/web/scanner/scanner.py
Symbolic link
1
docs/mkdocs/src/hive/web/scanner/scanner.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../../web/scanner/scanner.py
|
||||
1
docs/mkdocs/src/hive/web/speaker/logo.svg
Symbolic link
1
docs/mkdocs/src/hive/web/speaker/logo.svg
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../../web/speaker/logo.svg
|
||||
1
docs/mkdocs/src/hive/web/speaker/speaker.css
Symbolic link
1
docs/mkdocs/src/hive/web/speaker/speaker.css
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../../web/speaker/speaker.css
|
||||
1
docs/mkdocs/src/hive/web/speaker/speaker.html
Symbolic link
1
docs/mkdocs/src/hive/web/speaker/speaker.html
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../../web/speaker/speaker.html
|
||||
1
docs/mkdocs/src/hive/web/speaker/speaker.js
Symbolic link
1
docs/mkdocs/src/hive/web/speaker/speaker.js
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../../web/speaker/speaker.js
|
||||
1
docs/mkdocs/src/hive/web/speaker/speaker.py
Symbolic link
1
docs/mkdocs/src/hive/web/speaker/speaker.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../../web/speaker/speaker.py
|
||||
1
docs/mkdocs/src/hive/web/ui.js
Symbolic link
1
docs/mkdocs/src/hive/web/ui.js
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../../web/ui.js
|
||||
@@ -152,11 +152,23 @@ Some platforms support features that not all platforms support
|
||||
|
||||
See the [Platforms page](platforms/index.md) for details.
|
||||
|
||||
|
||||
Hive
|
||||
----
|
||||
|
||||
The Hive is a collection of example apps and virtual devices that are implemented using the
|
||||
Python Bumble API, running entirely in a web page. This is a convenient way to try out some
|
||||
of the examples without any Python installation, when you have some other virtual Bluetooth
|
||||
device that you can connect to or from, such as the Android Emulator.
|
||||
|
||||
See the [Bumble Hive](hive/index.md) for details.
|
||||
|
||||
Roadmap
|
||||
-------
|
||||
|
||||
Future features to be considered include:
|
||||
|
||||
* More profiles
|
||||
* More device examples
|
||||
* Add a new type of virtual link (beyond the two existing ones) to allow for link-level simulation (timing, loss, etc)
|
||||
* Bindings for languages other than Python
|
||||
|
||||
@@ -14,7 +14,7 @@ connections.
|
||||
|
||||
## Moniker
|
||||
The moniker syntax for an Android Emulator "netsim" transport is: `android-netsim:[<host>:<port>][<options>]`,
|
||||
where `<options>` is a ','-separated list of `<name>=<value>` pairs`.
|
||||
where `<options>` is a comma-separated list of `<name>=<value>` pairs.
|
||||
The `mode` parameter name can specify running as a host or a controller, and `<hostname>:<port>` can specify a host name (or IP address) and TCP port number on which to reach the gRPC server for the emulator (in "host" mode), or to accept gRPC connections (in "controller" mode).
|
||||
Both the `mode=<host|controller>` and `<hostname>:<port>` parameters are optional (so the moniker `android-netsim` by itself is a valid moniker, which will create a transport in `host` mode, connected to `localhost` on the default gRPC port for the Netsim background process).
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ from bumble.device import Device
|
||||
from bumble.transport import open_transport_or_link
|
||||
from bumble.profiles.device_information_service import DeviceInformationService
|
||||
from bumble.profiles.heart_rate_service import HeartRateService
|
||||
from bumble.utils import AsyncRunner
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -98,6 +99,17 @@ async def main():
|
||||
)
|
||||
)
|
||||
|
||||
# Notify subscribers of the current value as soon as they subscribe
|
||||
@heart_rate_service.heart_rate_measurement_characteristic.on('subscription')
|
||||
def on_subscription(connection, notify_enabled, indicate_enabled):
|
||||
if notify_enabled or indicate_enabled:
|
||||
AsyncRunner.spawn(
|
||||
device.notify_subscriber(
|
||||
connection,
|
||||
heart_rate_service.heart_rate_measurement_characteristic,
|
||||
)
|
||||
)
|
||||
|
||||
# Go!
|
||||
await device.power_on()
|
||||
await device.start_advertising(auto_restart=True)
|
||||
|
||||
248
examples/hid_key_map.py
Normal file
248
examples/hid_key_map.py
Normal file
@@ -0,0 +1,248 @@
|
||||
# shift map
|
||||
|
||||
# letters
|
||||
shift_map = {
|
||||
'a': 'A',
|
||||
'b': 'B',
|
||||
'c': 'C',
|
||||
'd': 'D',
|
||||
'e': 'E',
|
||||
'f': 'F',
|
||||
'g': 'G',
|
||||
'h': 'H',
|
||||
'i': 'I',
|
||||
'j': 'J',
|
||||
'k': 'K',
|
||||
'l': 'L',
|
||||
'm': 'M',
|
||||
'n': 'N',
|
||||
'o': 'O',
|
||||
'p': 'P',
|
||||
'q': 'Q',
|
||||
'r': 'R',
|
||||
's': 'S',
|
||||
't': 'T',
|
||||
'u': 'U',
|
||||
'v': 'V',
|
||||
'w': 'W',
|
||||
'x': 'X',
|
||||
'y': 'Y',
|
||||
'z': 'Z',
|
||||
# numbers
|
||||
'1': '!',
|
||||
'2': '@',
|
||||
'3': '#',
|
||||
'4': '$',
|
||||
'5': '%',
|
||||
'6': '^',
|
||||
'7': '&',
|
||||
'8': '*',
|
||||
'9': '(',
|
||||
'0': ')',
|
||||
# symbols
|
||||
'-': '_',
|
||||
'=': '+',
|
||||
'[': '{',
|
||||
']': '}',
|
||||
'\\': '|',
|
||||
';': ':',
|
||||
'\'': '"',
|
||||
',': '<',
|
||||
'.': '>',
|
||||
'/': '?',
|
||||
'`': '~',
|
||||
}
|
||||
|
||||
# hex map
|
||||
|
||||
# modifier keys
|
||||
mod_keys = {
|
||||
'00': '',
|
||||
'01': 'left_ctrl',
|
||||
'02': 'left_shift',
|
||||
'04': 'left_alt',
|
||||
'08': 'left_meta',
|
||||
'10': 'right_ctrl',
|
||||
'20': 'right_shift',
|
||||
'40': 'right_alt',
|
||||
'80': 'right_meta',
|
||||
}
|
||||
|
||||
# base keys
|
||||
|
||||
base_keys = {
|
||||
# meta
|
||||
'00': '', # none
|
||||
'01': 'error_ovf',
|
||||
# letters
|
||||
'04': 'a',
|
||||
'05': 'b',
|
||||
'06': 'c',
|
||||
'07': 'd',
|
||||
'08': 'e',
|
||||
'09': 'f',
|
||||
'0a': 'g',
|
||||
'0b': 'h',
|
||||
'0c': 'i',
|
||||
'0d': 'j',
|
||||
'0e': 'k',
|
||||
'0f': 'l',
|
||||
'10': 'm',
|
||||
'11': 'n',
|
||||
'12': 'o',
|
||||
'13': 'p',
|
||||
'14': 'q',
|
||||
'15': 'r',
|
||||
'16': 's',
|
||||
'17': 't',
|
||||
'18': 'u',
|
||||
'19': 'v',
|
||||
'1a': 'w',
|
||||
'1b': 'x',
|
||||
'1c': 'y',
|
||||
'1d': 'z',
|
||||
# numbers
|
||||
'1e': '1',
|
||||
'1f': '2',
|
||||
'20': '3',
|
||||
'21': '4',
|
||||
'22': '5',
|
||||
'23': '6',
|
||||
'24': '7',
|
||||
'25': '8',
|
||||
'26': '9',
|
||||
'27': '0',
|
||||
# misc
|
||||
'28': 'enter', # enter \n
|
||||
'29': 'esc',
|
||||
'2a': 'backspace',
|
||||
'2b': 'tab',
|
||||
'2c': 'spacebar', # space
|
||||
'2d': '-',
|
||||
'2e': '=',
|
||||
'2f': '[',
|
||||
'30': ']',
|
||||
'31': '\\',
|
||||
'32': '=',
|
||||
'33': '_SEMICOLON',
|
||||
'34': 'KEY_APOSTROPHE',
|
||||
'35': 'KEY_GRAVE',
|
||||
'36': 'KEY_COMMA',
|
||||
'37': 'KEY_DOT',
|
||||
'38': 'KEY_SLASH',
|
||||
'39': 'KEY_CAPSLOCK',
|
||||
'3a': 'KEY_F1',
|
||||
'3b': 'KEY_F2',
|
||||
'3c': 'KEY_F3',
|
||||
'3d': 'KEY_F4',
|
||||
'3e': 'KEY_F5',
|
||||
'3f': 'KEY_F6',
|
||||
'40': 'KEY_F7',
|
||||
'41': 'KEY_F8',
|
||||
'42': 'KEY_F9',
|
||||
'43': 'KEY_F10',
|
||||
'44': 'KEY_F11',
|
||||
'45': 'KEY_F12',
|
||||
'46': 'KEY_SYSRQ',
|
||||
'47': 'KEY_SCROLLLOCK',
|
||||
'48': 'KEY_PAUSE',
|
||||
'49': 'KEY_INSERT',
|
||||
'4a': 'KEY_HOME',
|
||||
'4b': 'KEY_PAGEUP',
|
||||
'4c': 'KEY_DELETE',
|
||||
'4d': 'KEY_END',
|
||||
'4e': 'KEY_PAGEDOWN',
|
||||
'4f': 'KEY_RIGHT',
|
||||
'50': 'KEY_LEFT',
|
||||
'51': 'KEY_DOWN',
|
||||
'52': 'KEY_UP',
|
||||
'53': 'KEY_NUMLOCK',
|
||||
'54': 'KEY_KPSLASH',
|
||||
'55': 'KEY_KPASTERISK',
|
||||
'56': 'KEY_KPMINUS',
|
||||
'57': 'KEY_KPPLUS',
|
||||
'58': 'KEY_KPENTER',
|
||||
'59': 'KEY_KP1',
|
||||
'5a': 'KEY_KP2',
|
||||
'5b': 'KEY_KP3',
|
||||
'5c': 'KEY_KP4',
|
||||
'5d': 'KEY_KP5',
|
||||
'5e': 'KEY_KP6',
|
||||
'5f': 'KEY_KP7',
|
||||
'60': 'KEY_KP8',
|
||||
'61': 'KEY_KP9',
|
||||
'62': 'KEY_KP0',
|
||||
'63': 'KEY_KPDOT',
|
||||
'64': 'KEY_102ND',
|
||||
'65': 'KEY_COMPOSE',
|
||||
'66': 'KEY_POWER',
|
||||
'67': 'KEY_KPEQUAL',
|
||||
'68': 'KEY_F13',
|
||||
'69': 'KEY_F14',
|
||||
'6a': 'KEY_F15',
|
||||
'6b': 'KEY_F16',
|
||||
'6c': 'KEY_F17',
|
||||
'6d': 'KEY_F18',
|
||||
'6e': 'KEY_F19',
|
||||
'6f': 'KEY_F20',
|
||||
'70': 'KEY_F21',
|
||||
'71': 'KEY_F22',
|
||||
'72': 'KEY_F23',
|
||||
'73': 'KEY_F24',
|
||||
'74': 'KEY_OPEN',
|
||||
'75': 'KEY_HELP',
|
||||
'76': 'KEY_PROPS',
|
||||
'77': 'KEY_FRONT',
|
||||
'78': 'KEY_STOP',
|
||||
'79': 'KEY_AGAIN',
|
||||
'7a': 'KEY_UNDO',
|
||||
'7b': 'KEY_CUT',
|
||||
'7c': 'KEY_COPY',
|
||||
'7d': 'KEY_PASTE',
|
||||
'7e': 'KEY_FIND',
|
||||
'7f': 'KEY_MUTE',
|
||||
'80': 'KEY_VOLUMEUP',
|
||||
'81': 'KEY_VOLUMEDOWN',
|
||||
'85': 'KEY_KPCOMMA',
|
||||
'87': 'KEY_RO',
|
||||
'88': 'KEY_KATAKANAHIRAGANA',
|
||||
'89': 'KEY_YEN',
|
||||
'8a': 'KEY_HENKAN',
|
||||
'8b': 'KEY_MUHENKAN',
|
||||
'8c': 'KEY_KPJPCOMMA',
|
||||
'90': 'KEY_HANGEUL',
|
||||
'91': 'KEY_HANJA',
|
||||
'92': 'KEY_KATAKANA',
|
||||
'93': 'KEY_HIRAGANA',
|
||||
'94': 'KEY_ZENKAKUHANKAKU',
|
||||
'b6': 'KEY_KPLEFTPAREN',
|
||||
'b7': 'KEY_KPRIGHTPAREN',
|
||||
'e0': 'KEY_LEFTCTRL',
|
||||
'e1': 'KEY_LEFTSHIFT',
|
||||
'e2': 'KEY_LEFTALT',
|
||||
'e3': 'KEY_LEFTMETA',
|
||||
'e4': 'KEY_RIGHTCTRL',
|
||||
'e5': 'KEY_RIGHTSHIFT',
|
||||
'e6': 'KEY_RIGHTALT',
|
||||
'e7': 'KEY_RIGHTMETA',
|
||||
'e8': 'KEY_MEDIA_PLAYPAUSE',
|
||||
'e9': 'KEY_MEDIA_STOPCD',
|
||||
'ea': 'KEY_MEDIA_PREVIOUSSONG',
|
||||
'eb': 'KEY_MEDIA_NEXTSONG',
|
||||
'ec': 'KEY_MEDIA_EJECTCD',
|
||||
'ed': 'KEY_MEDIA_VOLUMEUP',
|
||||
'ee': 'KEY_MEDIA_VOLUMEDOWN',
|
||||
'ef': 'KEY_MEDIA_MUTE',
|
||||
'f0': 'KEY_MEDIA_WWW',
|
||||
'f1': 'KEY_MEDIA_BACK',
|
||||
'f2': 'KEY_MEDIA_FORWARD',
|
||||
'f3': 'KEY_MEDIA_STOP',
|
||||
'f4': 'KEY_MEDIA_FIND',
|
||||
'f5': 'KEY_MEDIA_SCROLLUP',
|
||||
'f6': 'KEY_MEDIA_SCROLLDOWN',
|
||||
'f7': 'KEY_MEDIA_EDIT',
|
||||
'f8': 'KEY_MEDIA_SLEEP',
|
||||
'f9': 'KEY_MEDIA_COFFEE',
|
||||
'fa': 'KEY_MEDIA_REFRESH',
|
||||
'fb': 'KEY_MEDIA_CALC',
|
||||
}
|
||||
159
examples/hid_report_parser.py
Normal file
159
examples/hid_report_parser.py
Normal file
@@ -0,0 +1,159 @@
|
||||
from bumble.colors import color
|
||||
from hid_key_map import base_keys, mod_keys, shift_map
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
def get_key(modifier: str, key: str) -> str:
|
||||
if modifier == '22':
|
||||
modifier = '02'
|
||||
if modifier in mod_keys:
|
||||
modifier = mod_keys[modifier]
|
||||
else:
|
||||
return ''
|
||||
if key in base_keys:
|
||||
key = base_keys[key]
|
||||
else:
|
||||
return ''
|
||||
if (modifier == 'left_shift' or modifier == 'right_shift') and key in shift_map:
|
||||
key = shift_map[key]
|
||||
|
||||
return key
|
||||
|
||||
|
||||
class Keyboard:
|
||||
def __init__(self): # type: ignore
|
||||
self.report = [
|
||||
[ # Bit array for Modifier keys
|
||||
0, # Right GUI - (usually the Windows key)
|
||||
0, # Right ALT
|
||||
0, # Right Shift
|
||||
0, # Right Control
|
||||
0, # Left GUI - (usually the Windows key)
|
||||
0, # Left ALT
|
||||
0, # Left Shift
|
||||
0, # Left Control
|
||||
],
|
||||
0x00, # Vendor reserved
|
||||
'', # Rest is space for 6 keys
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
]
|
||||
|
||||
def decode_keyboard_report(self, input_report: bytes, report_length: int) -> None:
|
||||
if report_length >= 8:
|
||||
modifier = input_report[1]
|
||||
self.report[0] = [int(x) for x in '{0:08b}'.format(modifier)]
|
||||
self.report[0].reverse() # type: ignore
|
||||
|
||||
modifier_key = str((modifier & 0x22).to_bytes(1, "big").hex())
|
||||
keycodes = []
|
||||
for k in range(3, report_length):
|
||||
keycodes.append(str(input_report[k].to_bytes(1, "big").hex()))
|
||||
self.report[k - 1] = get_key(modifier_key, keycodes[k - 3])
|
||||
else:
|
||||
print(color('Warning: Not able to parse report', 'yellow'))
|
||||
|
||||
def print_keyboard_report(self) -> None:
|
||||
print(color('\tKeyboard Input Received', 'green', None, 'bold'))
|
||||
print(color(f'Keys:', 'white', None, 'bold'))
|
||||
for i in range(1, 7):
|
||||
print(
|
||||
color(f' Key{i}{" ":>8s}= ', 'cyan', None, 'bold'), self.report[i + 1]
|
||||
)
|
||||
print(color(f'\nModifier Keys:', 'white', None, 'bold'))
|
||||
print(
|
||||
color(f' Left Ctrl : ', 'cyan'),
|
||||
f'{self.report[0][0] == 1!s:<5}', # type: ignore
|
||||
color(f' Left Shift : ', 'cyan'),
|
||||
f'{self.report[0][1] == 1!s:<5}', # type: ignore
|
||||
color(f' Left ALT : ', 'cyan'),
|
||||
f'{self.report[0][2] == 1!s:<5}', # type: ignore
|
||||
color(f' Left GUI : ', 'cyan'),
|
||||
f'{self.report[0][3] == 1!s:<5}\n', # type: ignore
|
||||
color(f' Right Ctrl : ', 'cyan'),
|
||||
f'{self.report[0][4] == 1!s:<5}', # type: ignore
|
||||
color(f' Right Shift : ', 'cyan'),
|
||||
f'{self.report[0][5] == 1!s:<5}', # type: ignore
|
||||
color(f' Right ALT : ', 'cyan'),
|
||||
f'{self.report[0][6] == 1!s:<5}', # type: ignore
|
||||
color(f' Right GUI : ', 'cyan'),
|
||||
f'{self.report[0][7] == 1!s:<5}', # type: ignore
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class Mouse:
|
||||
def __init__(self): # type: ignore
|
||||
self.report = [
|
||||
[ # Bit array for Buttons
|
||||
0, # Button 1 (primary/trigger
|
||||
0, # Button 2 (secondary)
|
||||
0, # Button 3 (tertiary)
|
||||
0, # Button 4
|
||||
0, # Button 5
|
||||
0, # unused padding bits
|
||||
0, # unused padding bits
|
||||
0, # unused padding bits
|
||||
],
|
||||
0, # X
|
||||
0, # Y
|
||||
0, # Wheel
|
||||
0, # AC Pan
|
||||
]
|
||||
|
||||
def decode_mouse_report(self, input_report: bytes, report_length: int) -> None:
|
||||
self.report[0] = [int(x) for x in '{0:08b}'.format(input_report[1])]
|
||||
self.report[0].reverse() # type: ignore
|
||||
self.report[1] = input_report[2]
|
||||
self.report[2] = input_report[3]
|
||||
if report_length in [5, 6]:
|
||||
self.report[3] = input_report[4]
|
||||
self.report[4] = input_report[5] if report_length == 6 else 0
|
||||
|
||||
def print_mouse_report(self) -> None:
|
||||
print(color('\tMouse Input Received', 'green', None, 'bold'))
|
||||
print(
|
||||
color(f' Button 1 (primary/trigger) = ', 'cyan'),
|
||||
self.report[0][0] == 1, # type: ignore
|
||||
color(f'\n Button 2 (secondary) = ', 'cyan'),
|
||||
self.report[0][1] == 1, # type: ignore
|
||||
color(f'\n Button 3 (tertiary) = ', 'cyan'),
|
||||
self.report[0][2] == 1, # type: ignore
|
||||
color(f'\n Button4 = ', 'cyan'),
|
||||
self.report[0][3] == 1, # type: ignore
|
||||
color(f'\n Button5 = ', 'cyan'),
|
||||
self.report[0][4] == 1, # type: ignore
|
||||
color(f'\n X (X-axis displacement) = ', 'cyan'),
|
||||
self.report[1],
|
||||
color(f'\n Y (Y-axis displacement) = ', 'cyan'),
|
||||
self.report[2],
|
||||
color(f'\n Wheel = ', 'cyan'),
|
||||
self.report[3],
|
||||
color(f'\n AC PAN = ', 'cyan'),
|
||||
self.report[4],
|
||||
)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
class ReportParser:
|
||||
@staticmethod
|
||||
def parse_input_report(input_report: bytes) -> None:
|
||||
|
||||
report_id = input_report[0] # pylint: disable=unsubscriptable-object
|
||||
report_length = len(input_report)
|
||||
|
||||
# Keyboard input report (report id = 1)
|
||||
if report_id == 1 and report_length >= 8:
|
||||
keyboard = Keyboard() # type: ignore
|
||||
keyboard.decode_keyboard_report(input_report, report_length)
|
||||
keyboard.print_keyboard_report()
|
||||
# Mouse input report (report id = 2)
|
||||
elif report_id == 2 and report_length in [4, 5, 6]:
|
||||
mouse = Mouse() # type: ignore
|
||||
mouse.decode_mouse_report(input_report, report_length)
|
||||
mouse.print_mouse_report()
|
||||
else:
|
||||
print(color(f'Warning: Parse Error Report ID {report_id}', 'yellow'))
|
||||
@@ -131,7 +131,7 @@ async def main():
|
||||
await device.power_on()
|
||||
|
||||
# Create a listener to wait for AVDTP connections
|
||||
listener = Listener(Listener.create_registrar(device))
|
||||
listener = Listener.for_device(device)
|
||||
listener.on('connection', on_avdtp_connection)
|
||||
|
||||
if len(sys.argv) >= 5:
|
||||
|
||||
@@ -179,7 +179,7 @@ async def main():
|
||||
await stream_packets(read, protocol)
|
||||
else:
|
||||
# Create a listener to wait for AVDTP connections
|
||||
listener = Listener(Listener.create_registrar(device), version=(1, 2))
|
||||
listener = Listener.for_device(device=device, version=(1, 2))
|
||||
listener.on(
|
||||
'connection', lambda protocol: on_avdtp_connection(read, protocol)
|
||||
)
|
||||
|
||||
@@ -21,6 +21,7 @@ import sys
|
||||
import os
|
||||
import logging
|
||||
|
||||
from bumble import l2cap
|
||||
from bumble.core import AdvertisingData
|
||||
from bumble.device import Device
|
||||
from bumble.transport import open_transport_or_link
|
||||
@@ -95,8 +96,10 @@ async def main():
|
||||
|
||||
channel.sink = on_data
|
||||
|
||||
psm = device.register_l2cap_channel_server(0, on_coc, 8)
|
||||
print(f'### LE_PSM_OUT = {psm}')
|
||||
server = device.create_l2cap_server(
|
||||
spec=l2cap.LeCreditBasedChannelSpec(max_credits=8), handler=on_coc
|
||||
)
|
||||
print(f'### LE_PSM_OUT = {server.psm}')
|
||||
|
||||
# Add the ASHA service to the GATT server
|
||||
read_only_properties_characteristic = Characteristic(
|
||||
@@ -147,7 +150,7 @@ async def main():
|
||||
ASHA_LE_PSM_OUT_CHARACTERISTIC,
|
||||
Characteristic.Properties.READ,
|
||||
Characteristic.READABLE,
|
||||
struct.pack('<H', psm),
|
||||
struct.pack('<H', server.psm),
|
||||
)
|
||||
device.add_service(
|
||||
Service(
|
||||
|
||||
@@ -31,6 +31,7 @@ from bumble.core import (
|
||||
BT_BR_EDR_TRANSPORT,
|
||||
)
|
||||
from bumble import rfcomm, hfp
|
||||
from bumble.hci import HCI_SynchronousDataPacket
|
||||
from bumble.sdp import (
|
||||
Client as SDP_Client,
|
||||
DataElement,
|
||||
@@ -197,6 +198,13 @@ async def main():
|
||||
print('@@@ Disconnected from RFCOMM server')
|
||||
return
|
||||
|
||||
def on_sco(connection_handle: int, packet: HCI_SynchronousDataPacket):
|
||||
# Reset packet and loopback
|
||||
packet.packet_status = 0
|
||||
device.host.send_hci_packet(packet)
|
||||
|
||||
device.host.on('sco_packet', on_sco)
|
||||
|
||||
# Protocol loop (just for testing at this point)
|
||||
protocol = hfp.HfpProtocol(session)
|
||||
while True:
|
||||
|
||||
540
examples/run_hid_host.py
Normal file
540
examples/run_hid_host.py
Normal file
@@ -0,0 +1,540 @@
|
||||
# Copyright 2021-2022 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Imports
|
||||
# -----------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
|
||||
from bumble.colors import color
|
||||
|
||||
import bumble.core
|
||||
from bumble.device import Device
|
||||
from bumble.transport import open_transport_or_link
|
||||
from bumble.core import (
|
||||
BT_L2CAP_PROTOCOL_ID,
|
||||
BT_HIDP_PROTOCOL_ID,
|
||||
BT_HUMAN_INTERFACE_DEVICE_SERVICE,
|
||||
BT_BR_EDR_TRANSPORT,
|
||||
)
|
||||
from bumble.hci import Address
|
||||
from bumble.hid import Host, Message
|
||||
from bumble.sdp import (
|
||||
Client as SDP_Client,
|
||||
DataElement,
|
||||
ServiceAttribute,
|
||||
SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
|
||||
SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_ALL_ATTRIBUTES_RANGE,
|
||||
SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID,
|
||||
SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
|
||||
SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
|
||||
SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
|
||||
)
|
||||
from hid_report_parser import ReportParser
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# SDP attributes for Bluetooth HID devices
|
||||
SDP_HID_SERVICE_NAME_ATTRIBUTE_ID = 0x0100
|
||||
SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID = 0x0101
|
||||
SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID = 0x0102
|
||||
SDP_HID_DEVICE_RELEASE_NUMBER_ATTRIBUTE_ID = 0x0200 # [DEPRECATED]
|
||||
SDP_HID_PARSER_VERSION_ATTRIBUTE_ID = 0x0201
|
||||
SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID = 0x0202
|
||||
SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID = 0x0203
|
||||
SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID = 0x0204
|
||||
SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID = 0x0205
|
||||
SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID = 0x0206
|
||||
SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID = 0x0207
|
||||
SDP_HID_SDP_DISABLE_ATTRIBUTE_ID = 0x0208 # [DEPRECATED]
|
||||
SDP_HID_BATTERY_POWER_ATTRIBUTE_ID = 0x0209
|
||||
SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID = 0x020A
|
||||
SDP_HID_PROFILE_VERSION_ATTRIBUTE_ID = 0x020B # DEPRECATED]
|
||||
SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID = 0x020C
|
||||
SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID = 0x020D
|
||||
SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID = 0x020E
|
||||
SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID = 0x020F
|
||||
SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID = 0x0210
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def get_hid_device_sdp_record(device, connection):
|
||||
|
||||
# Connect to the SDP Server
|
||||
sdp_client = SDP_Client(device)
|
||||
await sdp_client.connect(connection)
|
||||
if sdp_client:
|
||||
print(color('Connected to SDP Server', 'blue'))
|
||||
else:
|
||||
print(color('Failed to connect to SDP Server', 'red'))
|
||||
|
||||
# List BT HID Device service in the root browse group
|
||||
service_record_handles = await sdp_client.search_services(
|
||||
[BT_HUMAN_INTERFACE_DEVICE_SERVICE]
|
||||
)
|
||||
|
||||
if len(service_record_handles) < 1:
|
||||
await sdp_client.disconnect()
|
||||
raise Exception(
|
||||
color(f'BT HID Device service not found on peer device!!!!', 'red')
|
||||
)
|
||||
|
||||
# For BT_HUMAN_INTERFACE_DEVICE_SERVICE service, get all its attributes
|
||||
for service_record_handle in service_record_handles:
|
||||
attributes = await sdp_client.get_attributes(
|
||||
service_record_handle, [SDP_ALL_ATTRIBUTES_RANGE]
|
||||
)
|
||||
print(color(f'SERVICE {service_record_handle:04X} attributes:', 'yellow'))
|
||||
print(color(f'SDP attributes for HID device', 'magenta'))
|
||||
for attribute in attributes:
|
||||
if attribute.id == SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' Service Record Handle : ', 'cyan'),
|
||||
hex(attribute.value.value),
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' Service Class : ', 'cyan'), attribute.value.value[0].value
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' SDP Browse Group List : ', 'cyan'),
|
||||
attribute.value.value[0].value,
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' BT_L2CAP_PROTOCOL_ID : ', 'cyan'),
|
||||
attribute.value.value[0].value[0].value,
|
||||
)
|
||||
print(
|
||||
color(' PSM for Bluetooth HID Control channel : ', 'cyan'),
|
||||
hex(attribute.value.value[0].value[1].value),
|
||||
)
|
||||
print(
|
||||
color(' BT_HIDP_PROTOCOL_ID : ', 'cyan'),
|
||||
attribute.value.value[1].value[0].value,
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' Lanugage : ', 'cyan'), hex(attribute.value.value[0].value)
|
||||
)
|
||||
print(
|
||||
color(' Encoding : ', 'cyan'), hex(attribute.value.value[1].value)
|
||||
)
|
||||
print(
|
||||
color(' PrimaryLanguageBaseID : ', 'cyan'),
|
||||
hex(attribute.value.value[2].value),
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' BT_HUMAN_INTERFACE_DEVICE_SERVICE ', 'cyan'),
|
||||
attribute.value.value[0].value[0].value,
|
||||
)
|
||||
print(
|
||||
color(' HID Profileversion number : ', 'cyan'),
|
||||
hex(attribute.value.value[0].value[1].value),
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' BT_L2CAP_PROTOCOL_ID : ', 'cyan'),
|
||||
attribute.value.value[0].value[0].value[0].value,
|
||||
)
|
||||
print(
|
||||
color(' PSM for Bluetooth HID Interrupt channel : ', 'cyan'),
|
||||
hex(attribute.value.value[0].value[0].value[1].value),
|
||||
)
|
||||
print(
|
||||
color(' BT_HIDP_PROTOCOL_ID : ', 'cyan'),
|
||||
attribute.value.value[0].value[1].value[0].value,
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_HID_SERVICE_NAME_ATTRIBUTE_ID:
|
||||
print(color(' Service Name: ', 'cyan'), attribute.value.value)
|
||||
|
||||
elif attribute.id == SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID:
|
||||
print(color(' Service Description: ', 'cyan'), attribute.value.value)
|
||||
|
||||
elif attribute.id == SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID:
|
||||
print(color(' Provider Name: ', 'cyan'), attribute.value.value)
|
||||
|
||||
elif attribute.id == SDP_HID_DEVICE_RELEASE_NUMBER_ATTRIBUTE_ID:
|
||||
print(color(' Release Number: ', 'cyan'), hex(attribute.value.value))
|
||||
|
||||
elif attribute.id == SDP_HID_PARSER_VERSION_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' HID Parser Version: ', 'cyan'), hex(attribute.value.value)
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' HIDDeviceSubclass: ', 'cyan'), hex(attribute.value.value)
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID:
|
||||
print(color(' HIDCountryCode: ', 'cyan'), hex(attribute.value.value))
|
||||
|
||||
elif attribute.id == SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID:
|
||||
print(color(' HIDVirtualCable: ', 'cyan'), attribute.value.value)
|
||||
|
||||
elif attribute.id == SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID:
|
||||
print(color(' HIDReconnectInitiate: ', 'cyan'), attribute.value.value)
|
||||
|
||||
elif attribute.id == SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' HID Report Descriptor type: ', 'cyan'),
|
||||
hex(attribute.value.value[0].value[0].value),
|
||||
)
|
||||
print(
|
||||
color(' HID Report DescriptorList: ', 'cyan'),
|
||||
attribute.value.value[0].value[1].value,
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' HID LANGID Base Language: ', 'cyan'),
|
||||
hex(attribute.value.value[0].value[0].value),
|
||||
)
|
||||
print(
|
||||
color(' HID LANGID Base Bluetooth String Offset: ', 'cyan'),
|
||||
hex(attribute.value.value[0].value[1].value),
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_HID_BATTERY_POWER_ATTRIBUTE_ID:
|
||||
print(color(' HIDBatteryPower: ', 'cyan'), attribute.value.value)
|
||||
|
||||
elif attribute.id == SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID:
|
||||
print(color(' HIDRemoteWake: ', 'cyan'), attribute.value.value)
|
||||
|
||||
elif attribute.id == SDP_HID_PROFILE_VERSION_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' HIDProfileVersion : ', 'cyan'), hex(attribute.value.value)
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' HIDSupervisionTimeout: ', 'cyan'),
|
||||
hex(attribute.value.value),
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' HIDNormallyConnectable: ', 'cyan'), attribute.value.value
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID:
|
||||
print(color(' HIDBootDevice: ', 'cyan'), attribute.value.value)
|
||||
|
||||
elif attribute.id == SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' HIDSSRHostMaxLatency: ', 'cyan'),
|
||||
hex(attribute.value.value),
|
||||
)
|
||||
|
||||
elif attribute.id == SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID:
|
||||
print(
|
||||
color(' HIDSSRHostMinTimeout: ', 'cyan'),
|
||||
hex(attribute.value.value),
|
||||
)
|
||||
|
||||
else:
|
||||
print(
|
||||
color(
|
||||
f' Warning: Attribute ID: {attribute.id} match not found.\n Attribute Info: {attribute}',
|
||||
'yellow',
|
||||
)
|
||||
)
|
||||
|
||||
await sdp_client.disconnect()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def get_stream_reader(pipe) -> asyncio.StreamReader:
|
||||
loop = asyncio.get_event_loop()
|
||||
reader = asyncio.StreamReader(loop=loop)
|
||||
protocol = asyncio.StreamReaderProtocol(reader)
|
||||
await loop.connect_read_pipe(lambda: protocol, pipe)
|
||||
return reader
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def main():
|
||||
if len(sys.argv) < 4:
|
||||
print(
|
||||
'Usage: run_hid_host.py <device-config> <transport-spec> '
|
||||
'<bluetooth-address> [test-mode]'
|
||||
)
|
||||
|
||||
print('example: run_hid_host.py classic1.json usb:0 E1:CA:72:48:C4:E8/P')
|
||||
return
|
||||
|
||||
def on_hid_data_cb(pdu):
|
||||
report_type = pdu[0] & 0x0F
|
||||
if len(pdu) == 1:
|
||||
print(color(f'Warning: No report received', 'yellow'))
|
||||
return
|
||||
report_length = len(pdu[1:])
|
||||
report_id = pdu[1]
|
||||
|
||||
if report_type != Message.ReportType.OTHER_REPORT:
|
||||
print(
|
||||
color(
|
||||
f' Report type = {report_type}, Report length = {report_length}, Report id = {report_id}',
|
||||
'blue',
|
||||
None,
|
||||
'bold',
|
||||
)
|
||||
)
|
||||
|
||||
if (report_length <= 1) or (report_id == 0):
|
||||
return
|
||||
|
||||
if report_type == Message.ReportType.INPUT_REPORT:
|
||||
ReportParser.parse_input_report(pdu[1:]) # type: ignore
|
||||
|
||||
async def handle_virtual_cable_unplug():
|
||||
await hid_host.disconnect_interrupt_channel()
|
||||
await hid_host.disconnect_control_channel()
|
||||
await device.keystore.delete(target_address) # type: ignore
|
||||
await connection.disconnect()
|
||||
|
||||
def on_hid_virtual_cable_unplug_cb():
|
||||
asyncio.create_task(handle_virtual_cable_unplug())
|
||||
|
||||
print('<<< connecting to HCI...')
|
||||
async with await open_transport_or_link(sys.argv[2]) as (hci_source, hci_sink):
|
||||
print('<<< CONNECTED')
|
||||
|
||||
# Create a device
|
||||
device = Device.from_config_file_with_hci(sys.argv[1], hci_source, hci_sink)
|
||||
device.classic_enabled = True
|
||||
await device.power_on()
|
||||
|
||||
# Connect to a peer
|
||||
target_address = sys.argv[3]
|
||||
print(f'=== Connecting to {target_address}...')
|
||||
connection = await device.connect(target_address, transport=BT_BR_EDR_TRANSPORT)
|
||||
print(f'=== Connected to {connection.peer_address}!')
|
||||
|
||||
# Request authentication
|
||||
print('*** Authenticating...')
|
||||
await connection.authenticate()
|
||||
print('*** Authenticated...')
|
||||
|
||||
# Enable encryption
|
||||
print('*** Enabling encryption...')
|
||||
await connection.encrypt()
|
||||
print('*** Encryption on')
|
||||
|
||||
await get_hid_device_sdp_record(device, connection)
|
||||
|
||||
# Create HID host and start it
|
||||
print('@@@ Starting HID Host...')
|
||||
hid_host = Host(device, connection)
|
||||
|
||||
# Register for HID data call back
|
||||
hid_host.on('data', on_hid_data_cb)
|
||||
|
||||
# Register for virtual cable unplug call back
|
||||
hid_host.on('virtual_cable_unplug', on_hid_virtual_cable_unplug_cb)
|
||||
|
||||
async def menu():
|
||||
reader = await get_stream_reader(sys.stdin)
|
||||
while True:
|
||||
print(
|
||||
"\n************************ HID Host Menu *****************************\n"
|
||||
)
|
||||
print(" 1. Connect Control Channel")
|
||||
print(" 2. Connect Interrupt Channel")
|
||||
print(" 3. Disconnect Control Channel")
|
||||
print(" 4. Disconnect Interrupt Channel")
|
||||
print(" 5. Get Report")
|
||||
print(" 6. Set Report")
|
||||
print(" 7. Set Protocol Mode")
|
||||
print(" 8. Get Protocol Mode")
|
||||
print(" 9. Send Report")
|
||||
print("10. Suspend")
|
||||
print("11. Exit Suspend")
|
||||
print("12. Virtual Cable Unplug")
|
||||
print("13. Disconnect device")
|
||||
print("14. Delete Bonding")
|
||||
print("15. Re-connect to device")
|
||||
print("\nEnter your choice : \n")
|
||||
|
||||
choice = await reader.readline()
|
||||
choice = choice.decode('utf-8').strip()
|
||||
|
||||
if choice == '1':
|
||||
await hid_host.connect_control_channel()
|
||||
|
||||
elif choice == '2':
|
||||
await hid_host.connect_interrupt_channel()
|
||||
|
||||
elif choice == '3':
|
||||
await hid_host.disconnect_control_channel()
|
||||
|
||||
elif choice == '4':
|
||||
await hid_host.disconnect_interrupt_channel()
|
||||
|
||||
elif choice == '5':
|
||||
print(" 1. Report ID 0x02")
|
||||
print(" 2. Report ID 0x03")
|
||||
print(" 3. Report ID 0x05")
|
||||
choice1 = await reader.readline()
|
||||
choice1 = choice1.decode('utf-8').strip()
|
||||
|
||||
if choice1 == '1':
|
||||
hid_host.get_report(1, 2, 3)
|
||||
|
||||
elif choice1 == '2':
|
||||
hid_host.get_report(2, 3, 2)
|
||||
|
||||
elif choice1 == '3':
|
||||
hid_host.get_report(3, 5, 3)
|
||||
|
||||
else:
|
||||
print('Incorrect option selected')
|
||||
|
||||
elif choice == '6':
|
||||
print(" 1. Report type 1 and Report id 0x01")
|
||||
print(" 2. Report type 2 and Report id 0x03")
|
||||
print(" 3. Report type 3 and Report id 0x05")
|
||||
choice1 = await reader.readline()
|
||||
choice1 = choice1.decode('utf-8').strip()
|
||||
|
||||
if choice1 == '1':
|
||||
# data includes first octet as report id
|
||||
data = bytearray(
|
||||
[0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01]
|
||||
)
|
||||
hid_host.set_report(1, data)
|
||||
|
||||
elif choice1 == '2':
|
||||
data = bytearray([0x03, 0x01, 0x01])
|
||||
hid_host.set_report(2, data)
|
||||
|
||||
elif choice1 == '3':
|
||||
data = bytearray([0x05, 0x01, 0x01, 0x01])
|
||||
hid_host.set_report(3, data)
|
||||
|
||||
else:
|
||||
print('Incorrect option selected')
|
||||
|
||||
elif choice == '7':
|
||||
print(" 0. Boot")
|
||||
print(" 1. Report")
|
||||
choice1 = await reader.readline()
|
||||
choice1 = choice1.decode('utf-8').strip()
|
||||
|
||||
if choice1 == '0':
|
||||
hid_host.set_protocol(Message.ProtocolMode.BOOT_PROTOCOL)
|
||||
|
||||
elif choice1 == '1':
|
||||
hid_host.set_protocol(Message.ProtocolMode.REPORT_PROTOCOL)
|
||||
|
||||
else:
|
||||
print('Incorrect option selected')
|
||||
|
||||
elif choice == '8':
|
||||
hid_host.get_protocol()
|
||||
|
||||
elif choice == '9':
|
||||
print(" 1. Report ID 0x01")
|
||||
print(" 2. Report ID 0x03")
|
||||
choice1 = await reader.readline()
|
||||
choice1 = choice1.decode('utf-8').strip()
|
||||
|
||||
if choice1 == '1':
|
||||
data = bytearray(
|
||||
[0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
)
|
||||
hid_host.send_data(data)
|
||||
|
||||
elif choice1 == '2':
|
||||
data = bytearray([0x03, 0x00, 0x0D, 0xFD, 0x00, 0x00])
|
||||
hid_host.send_data(data)
|
||||
|
||||
else:
|
||||
print('Incorrect option selected')
|
||||
|
||||
elif choice == '10':
|
||||
hid_host.suspend()
|
||||
|
||||
elif choice == '11':
|
||||
hid_host.exit_suspend()
|
||||
|
||||
elif choice == '12':
|
||||
hid_host.virtual_cable_unplug()
|
||||
try:
|
||||
await device.keystore.delete(target_address)
|
||||
except KeyError:
|
||||
print('Device not found or Device already unpaired.')
|
||||
|
||||
elif choice == '13':
|
||||
peer_address = Address.from_string_for_transport(
|
||||
target_address, transport=BT_BR_EDR_TRANSPORT
|
||||
)
|
||||
connection = device.find_connection_by_bd_addr(
|
||||
peer_address, transport=BT_BR_EDR_TRANSPORT
|
||||
)
|
||||
if connection is not None:
|
||||
await connection.disconnect()
|
||||
else:
|
||||
print("Already disconnected from device")
|
||||
|
||||
elif choice == '14':
|
||||
try:
|
||||
await device.keystore.delete(target_address)
|
||||
print("Unpair successful")
|
||||
except KeyError:
|
||||
print('Device not found or Device already unpaired.')
|
||||
|
||||
elif choice == '15':
|
||||
connection = await device.connect(
|
||||
target_address, transport=BT_BR_EDR_TRANSPORT
|
||||
)
|
||||
await connection.authenticate()
|
||||
await connection.encrypt()
|
||||
|
||||
else:
|
||||
print("Invalid option selected.")
|
||||
|
||||
if (len(sys.argv) > 4) and (sys.argv[4] == 'test-mode'):
|
||||
# Enabling menu for testing
|
||||
await menu()
|
||||
else:
|
||||
# HID Connection
|
||||
# Control channel
|
||||
await hid_host.connect_control_channel()
|
||||
# Interrupt Channel
|
||||
await hid_host.connect_interrupt_channel()
|
||||
|
||||
await hci_source.wait_for_termination()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper())
|
||||
asyncio.run(main())
|
||||
10
extras/android/RemoteHCI/.gitignore
vendored
Normal file
10
extras/android/RemoteHCI/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
1
extras/android/RemoteHCI/app/.gitignore
vendored
Normal file
1
extras/android/RemoteHCI/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
73
extras/android/RemoteHCI/app/build.gradle.kts
Normal file
73
extras/android/RemoteHCI/app/build.gradle.kts
Normal file
@@ -0,0 +1,73 @@
|
||||
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
|
||||
plugins {
|
||||
alias(libs.plugins.androidApplication)
|
||||
alias(libs.plugins.kotlinAndroid)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.github.google.bumble.remotehci"
|
||||
compileSdk = 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.github.google.bumble.remotehci"
|
||||
minSdk = 26
|
||||
targetSdk = 33
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
aidl = false
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.3"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("reflect"))
|
||||
implementation(libs.core.ktx)
|
||||
implementation(libs.lifecycle.runtime.ktx)
|
||||
implementation(libs.activity.compose)
|
||||
implementation(platform(libs.compose.bom))
|
||||
implementation(libs.ui)
|
||||
implementation(libs.ui.graphics)
|
||||
implementation(libs.ui.tooling.preview)
|
||||
implementation(libs.material3)
|
||||
compileOnly(project(":lib"))
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.test.ext.junit)
|
||||
androidTestImplementation(libs.espresso.core)
|
||||
androidTestImplementation(platform(libs.compose.bom))
|
||||
androidTestImplementation(libs.ui.test.junit4)
|
||||
debugImplementation(libs.ui.tooling)
|
||||
debugImplementation(libs.ui.test.manifest)
|
||||
//compileOnly(files("${project.rootDir.absolutePath}/sdk/framework.jar"))
|
||||
}
|
||||
21
extras/android/RemoteHCI/app/proguard-rules.pro
vendored
Normal file
21
extras/android/RemoteHCI/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
29
extras/android/RemoteHCI/app/src/main/AndroidManifest.xml
Normal file
29
extras/android/RemoteHCI/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.RemoteHCI"
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.RemoteHCI">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package android.hardware.bluetooth;
|
||||
@VintfStability
|
||||
interface IBluetoothHci {
|
||||
void close();
|
||||
void initialize(in android.hardware.bluetooth.IBluetoothHciCallbacks callback);
|
||||
void sendAclData(in byte[] data);
|
||||
void sendHciCommand(in byte[] command);
|
||||
void sendIsoData(in byte[] data);
|
||||
void sendScoData(in byte[] data);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package android.hardware.bluetooth;
|
||||
@VintfStability
|
||||
interface IBluetoothHciCallbacks {
|
||||
void aclDataReceived(in byte[] data);
|
||||
void hciEventReceived(in byte[] event);
|
||||
void initializationComplete(in android.hardware.bluetooth.Status status);
|
||||
void isoDataReceived(in byte[] data);
|
||||
void scoDataReceived(in byte[] data);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
|
||||
// two cases:
|
||||
// 1). this is a frozen version file - do not edit this in any case.
|
||||
// 2). this is a 'current' file. If you make a backwards compatible change to
|
||||
// the interface (from the latest frozen version), the build system will
|
||||
// prompt you to update this file with `m <name>-update-api`.
|
||||
//
|
||||
// You must not make a backward incompatible change to any AIDL file built
|
||||
// with the aidl_interface module type with versions property set. The module
|
||||
// type is used to build AIDL files in a way that they can be used across
|
||||
// independently updatable components of the system. If a device is shipped
|
||||
// with such a backward incompatible change, it has a high risk of breaking
|
||||
// later when a module using the interface is updated, e.g., Mainline modules.
|
||||
|
||||
package android.hardware.bluetooth;
|
||||
@Backing(type="int")
|
||||
@VintfStability
|
||||
enum Status {
|
||||
SUCCESS = 0,
|
||||
ALREADY_INITIALIZED = 1,
|
||||
UNABLE_TO_OPEN_INTERFACE = 2,
|
||||
HARDWARE_INITIALIZATION_ERROR = 3,
|
||||
UNKNOWN = 4,
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.hardware.bluetooth@1.1;
|
||||
|
||||
import @1.0::HciPacket;
|
||||
import @1.0::IBluetoothHci;
|
||||
import IBluetoothHciCallbacks;
|
||||
|
||||
/**
|
||||
* The Host Controller Interface (HCI) is the layer defined by the Bluetooth
|
||||
* specification between the software that runs on the host and the Bluetooth
|
||||
* controller chip. This boundary is the natural choice for a Hardware
|
||||
* Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies
|
||||
* the stack and abstracts away power management, initialization, and other
|
||||
* implementation-specific details related to the hardware.
|
||||
*/
|
||||
interface IBluetoothHci extends @1.0::IBluetoothHci {
|
||||
/**
|
||||
* Same as @1.0, but uses 1.1 Callbacks version
|
||||
*/
|
||||
initialize_1_1(@1.1::IBluetoothHciCallbacks callback);
|
||||
|
||||
/**
|
||||
* Send an ISO data packet (as specified in the Bluetooth Core
|
||||
* Specification v5.2) to the Bluetooth controller.
|
||||
* Packets must be processed in order.
|
||||
* @param data HCI data packet to be sent
|
||||
*/
|
||||
sendIsoData(HciPacket data);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.hardware.bluetooth@1.1;
|
||||
|
||||
import @1.0::HciPacket;
|
||||
import @1.0::IBluetoothHciCallbacks;
|
||||
|
||||
/**
|
||||
* The interface from the Bluetooth Controller to the stack.
|
||||
*/
|
||||
interface IBluetoothHciCallbacks extends @1.0::IBluetoothHciCallbacks {
|
||||
/**
|
||||
* Send a ISO data packet form the controller to the host.
|
||||
* @param data the ISO HCI packet to be passed to the host stack
|
||||
*/
|
||||
isoDataReceived(HciPacket data);
|
||||
};
|
||||
BIN
extras/android/RemoteHCI/app/src/main/ic_launcher-playstore.png
Normal file
BIN
extras/android/RemoteHCI/app/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* This file is auto-generated. DO NOT MODIFY.
|
||||
*/
|
||||
package android.hardware.bluetooth;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public interface IBluetoothHci extends android.os.IInterface
|
||||
{
|
||||
/** Default implementation for IBluetoothHci. */
|
||||
public static class Default implements android.hardware.bluetooth.IBluetoothHci
|
||||
{
|
||||
@Override public void close() throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override public void sendAclData(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override public void sendHciCommand(byte[] command) throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override public void sendIsoData(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override public void sendScoData(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override
|
||||
public android.os.IBinder asBinder() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/** Local-side IPC implementation stub class. */
|
||||
public static abstract class Stub extends android.os.Binder implements android.hardware.bluetooth.IBluetoothHci
|
||||
{
|
||||
/** Construct the stub at attach it to the interface. */
|
||||
public Stub()
|
||||
{
|
||||
//this.markVintfStability();
|
||||
try {
|
||||
Method method = this.getClass().getMethod("markVintfStability", (Class<?>[])null);
|
||||
method.invoke(this);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.attachInterface(this, DESCRIPTOR);
|
||||
}
|
||||
/**
|
||||
* Cast an IBinder object into an android.hardware.bluetooth.IBluetoothHci interface,
|
||||
* generating a proxy if needed.
|
||||
*/
|
||||
public static android.hardware.bluetooth.IBluetoothHci asInterface(android.os.IBinder obj)
|
||||
{
|
||||
if ((obj==null)) {
|
||||
return null;
|
||||
}
|
||||
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
|
||||
if (((iin!=null)&&(iin instanceof android.hardware.bluetooth.IBluetoothHci))) {
|
||||
return ((android.hardware.bluetooth.IBluetoothHci)iin);
|
||||
}
|
||||
return new android.hardware.bluetooth.IBluetoothHci.Stub.Proxy(obj);
|
||||
}
|
||||
@Override public android.os.IBinder asBinder()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
|
||||
{
|
||||
java.lang.String descriptor = DESCRIPTOR;
|
||||
if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
|
||||
data.enforceInterface(descriptor);
|
||||
}
|
||||
switch (code)
|
||||
{
|
||||
case INTERFACE_TRANSACTION:
|
||||
{
|
||||
reply.writeString(descriptor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
switch (code)
|
||||
{
|
||||
case TRANSACTION_close:
|
||||
{
|
||||
this.close();
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
case TRANSACTION_initialize:
|
||||
{
|
||||
android.hardware.bluetooth.IBluetoothHciCallbacks _arg0;
|
||||
_arg0 = android.hardware.bluetooth.IBluetoothHciCallbacks.Stub.asInterface(data.readStrongBinder());
|
||||
this.initialize(_arg0);
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
case TRANSACTION_sendAclData:
|
||||
{
|
||||
byte[] _arg0;
|
||||
_arg0 = data.createByteArray();
|
||||
this.sendAclData(_arg0);
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
case TRANSACTION_sendHciCommand:
|
||||
{
|
||||
byte[] _arg0;
|
||||
_arg0 = data.createByteArray();
|
||||
this.sendHciCommand(_arg0);
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
case TRANSACTION_sendIsoData:
|
||||
{
|
||||
byte[] _arg0;
|
||||
_arg0 = data.createByteArray();
|
||||
this.sendIsoData(_arg0);
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
case TRANSACTION_sendScoData:
|
||||
{
|
||||
byte[] _arg0;
|
||||
_arg0 = data.createByteArray();
|
||||
this.sendScoData(_arg0);
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return super.onTransact(code, data, reply, flags);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private static class Proxy implements android.hardware.bluetooth.IBluetoothHci
|
||||
{
|
||||
private android.os.IBinder mRemote;
|
||||
Proxy(android.os.IBinder remote)
|
||||
{
|
||||
mRemote = remote;
|
||||
}
|
||||
@Override public android.os.IBinder asBinder()
|
||||
{
|
||||
return mRemote;
|
||||
}
|
||||
public java.lang.String getInterfaceDescriptor()
|
||||
{
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
@Override public void close() throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_close, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
@Override public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeStrongInterface(callback);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_initialize, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
@Override public void sendAclData(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeByteArray(data);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_sendAclData, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
@Override public void sendHciCommand(byte[] command) throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeByteArray(command);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_sendHciCommand, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
@Override public void sendIsoData(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeByteArray(data);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_sendIsoData, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
@Override public void sendScoData(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeByteArray(data);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_sendScoData, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
static final int TRANSACTION_close = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
|
||||
static final int TRANSACTION_initialize = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
|
||||
static final int TRANSACTION_sendAclData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
|
||||
static final int TRANSACTION_sendHciCommand = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
|
||||
static final int TRANSACTION_sendIsoData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
|
||||
static final int TRANSACTION_sendScoData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5);
|
||||
}
|
||||
public static final java.lang.String DESCRIPTOR = "android$hardware$bluetooth$IBluetoothHci".replace('$', '.');
|
||||
public void close() throws android.os.RemoteException;
|
||||
public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException;
|
||||
public void sendAclData(byte[] data) throws android.os.RemoteException;
|
||||
public void sendHciCommand(byte[] command) throws android.os.RemoteException;
|
||||
public void sendIsoData(byte[] data) throws android.os.RemoteException;
|
||||
public void sendScoData(byte[] data) throws android.os.RemoteException;
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* This file is auto-generated. DO NOT MODIFY.
|
||||
*/
|
||||
package android.hardware.bluetooth;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public interface IBluetoothHciCallbacks extends android.os.IInterface
|
||||
{
|
||||
/** Default implementation for IBluetoothHciCallbacks. */
|
||||
public static class Default implements android.hardware.bluetooth.IBluetoothHciCallbacks
|
||||
{
|
||||
@Override public void aclDataReceived(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override public void hciEventReceived(byte[] event) throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override public void initializationComplete(int status) throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override public void isoDataReceived(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override public void scoDataReceived(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
}
|
||||
@Override
|
||||
public android.os.IBinder asBinder() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/** Local-side IPC implementation stub class. */
|
||||
public static abstract class Stub extends android.os.Binder implements android.hardware.bluetooth.IBluetoothHciCallbacks
|
||||
{
|
||||
/** Construct the stub at attach it to the interface. */
|
||||
public Stub()
|
||||
{
|
||||
//this.markVintfStability();
|
||||
try {
|
||||
Method method = this.getClass().getMethod("markVintfStability", (Class<?>[])null);
|
||||
method.invoke(this);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.attachInterface(this, DESCRIPTOR);
|
||||
}
|
||||
/**
|
||||
* Cast an IBinder object into an android.hardware.bluetooth.IBluetoothHciCallbacks interface,
|
||||
* generating a proxy if needed.
|
||||
*/
|
||||
public static android.hardware.bluetooth.IBluetoothHciCallbacks asInterface(android.os.IBinder obj)
|
||||
{
|
||||
if ((obj==null)) {
|
||||
return null;
|
||||
}
|
||||
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
|
||||
if (((iin!=null)&&(iin instanceof android.hardware.bluetooth.IBluetoothHciCallbacks))) {
|
||||
return ((android.hardware.bluetooth.IBluetoothHciCallbacks)iin);
|
||||
}
|
||||
return new android.hardware.bluetooth.IBluetoothHciCallbacks.Stub.Proxy(obj);
|
||||
}
|
||||
@Override public android.os.IBinder asBinder()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
|
||||
{
|
||||
java.lang.String descriptor = DESCRIPTOR;
|
||||
if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
|
||||
data.enforceInterface(descriptor);
|
||||
}
|
||||
switch (code)
|
||||
{
|
||||
case INTERFACE_TRANSACTION:
|
||||
{
|
||||
reply.writeString(descriptor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
switch (code)
|
||||
{
|
||||
case TRANSACTION_aclDataReceived:
|
||||
{
|
||||
byte[] _arg0;
|
||||
_arg0 = data.createByteArray();
|
||||
this.aclDataReceived(_arg0);
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
case TRANSACTION_hciEventReceived:
|
||||
{
|
||||
byte[] _arg0;
|
||||
_arg0 = data.createByteArray();
|
||||
this.hciEventReceived(_arg0);
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
case TRANSACTION_initializationComplete:
|
||||
{
|
||||
int _arg0;
|
||||
_arg0 = data.readInt();
|
||||
this.initializationComplete(_arg0);
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
case TRANSACTION_isoDataReceived:
|
||||
{
|
||||
byte[] _arg0;
|
||||
_arg0 = data.createByteArray();
|
||||
this.isoDataReceived(_arg0);
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
case TRANSACTION_scoDataReceived:
|
||||
{
|
||||
byte[] _arg0;
|
||||
_arg0 = data.createByteArray();
|
||||
this.scoDataReceived(_arg0);
|
||||
reply.writeNoException();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
return super.onTransact(code, data, reply, flags);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private static class Proxy implements android.hardware.bluetooth.IBluetoothHciCallbacks
|
||||
{
|
||||
private android.os.IBinder mRemote;
|
||||
Proxy(android.os.IBinder remote)
|
||||
{
|
||||
mRemote = remote;
|
||||
}
|
||||
@Override public android.os.IBinder asBinder()
|
||||
{
|
||||
return mRemote;
|
||||
}
|
||||
public java.lang.String getInterfaceDescriptor()
|
||||
{
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
@Override public void aclDataReceived(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeByteArray(data);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_aclDataReceived, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
@Override public void hciEventReceived(byte[] event) throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeByteArray(event);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_hciEventReceived, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
@Override public void initializationComplete(int status) throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeInt(status);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_initializationComplete, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
@Override public void isoDataReceived(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeByteArray(data);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_isoDataReceived, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
@Override public void scoDataReceived(byte[] data) throws android.os.RemoteException
|
||||
{
|
||||
android.os.Parcel _data = android.os.Parcel.obtain();
|
||||
android.os.Parcel _reply = android.os.Parcel.obtain();
|
||||
try {
|
||||
_data.writeInterfaceToken(DESCRIPTOR);
|
||||
_data.writeByteArray(data);
|
||||
boolean _status = mRemote.transact(Stub.TRANSACTION_scoDataReceived, _data, _reply, 0);
|
||||
_reply.readException();
|
||||
}
|
||||
finally {
|
||||
_reply.recycle();
|
||||
_data.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
static final int TRANSACTION_aclDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
|
||||
static final int TRANSACTION_hciEventReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
|
||||
static final int TRANSACTION_initializationComplete = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
|
||||
static final int TRANSACTION_isoDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
|
||||
static final int TRANSACTION_scoDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
|
||||
}
|
||||
public static final java.lang.String DESCRIPTOR = "android$hardware$bluetooth$IBluetoothHciCallbacks".replace('$', '.');
|
||||
public void aclDataReceived(byte[] data) throws android.os.RemoteException;
|
||||
public void hciEventReceived(byte[] event) throws android.os.RemoteException;
|
||||
public void initializationComplete(int status) throws android.os.RemoteException;
|
||||
public void isoDataReceived(byte[] data) throws android.os.RemoteException;
|
||||
public void scoDataReceived(byte[] data) throws android.os.RemoteException;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* This file is auto-generated. DO NOT MODIFY.
|
||||
*/
|
||||
package android.hardware.bluetooth;
|
||||
public @interface Status {
|
||||
public static final int SUCCESS = 0;
|
||||
public static final int ALREADY_INITIALIZED = 1;
|
||||
public static final int UNABLE_TO_OPEN_INTERFACE = 2;
|
||||
public static final int HARDWARE_INITIALIZATION_ERROR = 3;
|
||||
public static final int UNKNOWN = 4;
|
||||
}
|
||||
@@ -0,0 +1,816 @@
|
||||
package android.hardware.bluetooth.V1_0;
|
||||
|
||||
import android.os.HidlSupport;
|
||||
import android.os.HwBinder;
|
||||
import android.os.IHwBinder;
|
||||
import android.os.HwBlob;
|
||||
import android.os.HwParcel;
|
||||
import android.os.IHwInterface;
|
||||
import android.os.NativeHandle;
|
||||
|
||||
/**
|
||||
* The Host Controller Interface (HCI) is the layer defined by the Bluetooth
|
||||
* specification between the software that runs on the host and the Bluetooth
|
||||
* controller chip. This boundary is the natural choice for a Hardware
|
||||
* Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies
|
||||
* the stack and abstracts away power management, initialization, and other
|
||||
* implementation-specific details related to the hardware.
|
||||
*/
|
||||
public interface IBluetoothHci extends android.internal.hidl.base.V1_0.IBase {
|
||||
/**
|
||||
* Fully-qualified interface name for this interface.
|
||||
*/
|
||||
public static final String kInterfaceName = "android.hardware.bluetooth@1.0::IBluetoothHci";
|
||||
|
||||
/**
|
||||
* Does a checked conversion from a binder to this class.
|
||||
*/
|
||||
/* package private */ static IBluetoothHci asInterface(IHwBinder binder) {
|
||||
if (binder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IHwInterface iface =
|
||||
binder.queryLocalInterface(kInterfaceName);
|
||||
|
||||
if ((iface != null) && (iface instanceof IBluetoothHci)) {
|
||||
return (IBluetoothHci)iface;
|
||||
}
|
||||
|
||||
IBluetoothHci proxy = new IBluetoothHci.Proxy(binder);
|
||||
|
||||
try {
|
||||
for (String descriptor : proxy.interfaceChain()) {
|
||||
if (descriptor.equals(kInterfaceName)) {
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
} catch (android.os.RemoteException e) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a checked conversion from any interface to this class.
|
||||
*/
|
||||
public static IBluetoothHci castFrom(IHwInterface iface) {
|
||||
return (iface == null) ? null : IBluetoothHci.asInterface(iface.asBinder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwBinder asBinder();
|
||||
|
||||
/**
|
||||
* This will invoke the equivalent of the C++ getService(std::string) if retry is
|
||||
* true or tryGetService(std::string) if retry is false. If the service is
|
||||
* available on the device and retry is true, this will wait for the service to
|
||||
* start.
|
||||
*
|
||||
*/
|
||||
public static IBluetoothHci getService(String serviceName, boolean retry) throws android.os.RemoteException {
|
||||
return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHci", serviceName, retry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls getService("default",retry).
|
||||
*/
|
||||
public static IBluetoothHci getService(boolean retry) throws android.os.RemoteException {
|
||||
return getService("default", retry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this will not wait for the interface to come up if it hasn't yet
|
||||
* started. See getService(String,boolean) instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static IBluetoothHci getService(String serviceName) throws android.os.RemoteException {
|
||||
return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHci", serviceName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this will not wait for the interface to come up if it hasn't yet
|
||||
* started. See getService(boolean) instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static IBluetoothHci getService() throws android.os.RemoteException {
|
||||
return getService("default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the underlying HCI interface.
|
||||
*
|
||||
* This method should be used to initialize any hardware interfaces
|
||||
* required to communicate with the Bluetooth hardware in the
|
||||
* device.
|
||||
*
|
||||
* The |oninitializationComplete| callback must be invoked in response
|
||||
* to this function to indicate success before any other function
|
||||
* (sendHciCommand, sendAclData, * sendScoData) is invoked on this
|
||||
* interface.
|
||||
*
|
||||
* @param callback implements IBluetoothHciCallbacks which will
|
||||
* receive callbacks when incoming HCI packets are received
|
||||
* from the controller to be sent to the host.
|
||||
*/
|
||||
void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback)
|
||||
throws android.os.RemoteException;
|
||||
/**
|
||||
* Send an HCI command (as specified in the Bluetooth Specification
|
||||
* V4.2, Vol 2, Part 5, Section 5.4.1) to the Bluetooth controller.
|
||||
* Commands must be executed in order.
|
||||
*
|
||||
* @param command is the HCI command to be sent
|
||||
*/
|
||||
void sendHciCommand(java.util.ArrayList<Byte> command)
|
||||
throws android.os.RemoteException;
|
||||
/**
|
||||
* Send an HCI ACL data packet (as specified in the Bluetooth Specification
|
||||
* V4.2, Vol 2, Part 5, Section 5.4.2) to the Bluetooth controller.
|
||||
* Packets must be processed in order.
|
||||
* @param data HCI data packet to be sent
|
||||
*/
|
||||
void sendAclData(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException;
|
||||
/**
|
||||
* Send an SCO data packet (as specified in the Bluetooth Specification
|
||||
* V4.2, Vol 2, Part 5, Section 5.4.3) to the Bluetooth controller.
|
||||
* Packets must be processed in order.
|
||||
* @param data HCI data packet to be sent
|
||||
*/
|
||||
void sendScoData(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException;
|
||||
/**
|
||||
* Close the HCI interface
|
||||
*/
|
||||
void close()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides run-time type information for this object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceChain on an IChild object must yield the following:
|
||||
* ["android.hardware.foo@1.0::IChild",
|
||||
* "android.hardware.foo@1.0::IParent"
|
||||
* "android.internal.hidl.base@1.0::IBase"]
|
||||
*
|
||||
* @return descriptors a vector of descriptors of the run-time type of the
|
||||
* object.
|
||||
*/
|
||||
java.util.ArrayList<String> interfaceChain()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Emit diagnostic information to the given file.
|
||||
*
|
||||
* Optionally overriden.
|
||||
*
|
||||
* @param fd File descriptor to dump data to.
|
||||
* Must only be used for the duration of this call.
|
||||
* @param options Arguments for debugging.
|
||||
* Must support empty for default debug information.
|
||||
*/
|
||||
void debug(NativeHandle fd, java.util.ArrayList<String> options)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides run-time type information for this object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceDescriptor on an IChild object must yield
|
||||
* "android.hardware.foo@1.0::IChild"
|
||||
*
|
||||
* @return descriptor a descriptor of the run-time type of the
|
||||
* object (the first element of the vector returned by
|
||||
* interfaceChain())
|
||||
*/
|
||||
String interfaceDescriptor()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Returns hashes of the source HAL files that define the interfaces of the
|
||||
* runtime type information on the object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceChain on an IChild object must yield the following:
|
||||
* [(hash of IChild.hal),
|
||||
* (hash of IParent.hal)
|
||||
* (hash of IBase.hal)].
|
||||
*
|
||||
* SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
|
||||
* according to SHA-256 standard.
|
||||
*
|
||||
* @return hashchain a vector of SHA-1 digests
|
||||
*/
|
||||
java.util.ArrayList<byte[/* 32 */]> getHashChain()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* This method trigger the interface to enable/disable instrumentation based
|
||||
* on system property hal.instrumentation.enable.
|
||||
*/
|
||||
void setHALInstrumentation()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Registers a death recipient, to be called when the process hosting this
|
||||
* interface dies.
|
||||
*
|
||||
* @param recipient a hidl_death_recipient callback object
|
||||
* @param cookie a cookie that must be returned with the callback
|
||||
* @return success whether the death recipient was registered successfully.
|
||||
*/
|
||||
boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides way to determine if interface is running without requesting
|
||||
* any functionality.
|
||||
*/
|
||||
void ping()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Get debug information on references on this interface.
|
||||
* @return info debugging information. See comments of DebugInfo.
|
||||
*/
|
||||
android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* This method notifies the interface that one or more system properties
|
||||
* have changed. The default implementation calls
|
||||
* (C++) report_sysprop_change() in libcutils or
|
||||
* (Java) android.os.SystemProperties.reportSyspropChanged,
|
||||
* which in turn calls a set of registered callbacks (eg to update trace
|
||||
* tags).
|
||||
*/
|
||||
void notifySyspropsChanged()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Unregisters the registered death recipient. If this service was registered
|
||||
* multiple times with the same exact death recipient, this unlinks the most
|
||||
* recently registered one.
|
||||
*
|
||||
* @param recipient a previously registered hidl_death_recipient callback
|
||||
* @return success whether the death recipient was unregistered successfully.
|
||||
*/
|
||||
boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
|
||||
throws android.os.RemoteException;
|
||||
|
||||
public static final class Proxy implements IBluetoothHci {
|
||||
private IHwBinder mRemote;
|
||||
|
||||
public Proxy(IHwBinder remote) {
|
||||
mRemote = java.util.Objects.requireNonNull(remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwBinder asBinder() {
|
||||
return mRemote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return this.interfaceDescriptor() + "@Proxy";
|
||||
} catch (android.os.RemoteException ex) {
|
||||
/* ignored; handled below. */
|
||||
}
|
||||
return "[class or subclass of " + IBluetoothHci.kInterfaceName + "]@Proxy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(java.lang.Object other) {
|
||||
return HidlSupport.interfacesEqual(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return this.asBinder().hashCode();
|
||||
}
|
||||
|
||||
// Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHci follow.
|
||||
@Override
|
||||
public void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
_hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder());
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(1 /* initialize */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendHciCommand(java.util.ArrayList<Byte> command)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(command);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(2 /* sendHciCommand */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendAclData(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(data);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(3 /* sendAclData */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendScoData(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(data);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(4 /* sendScoData */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(5 /* close */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Methods from ::android::hidl::base::V1_0::IBase follow.
|
||||
@Override
|
||||
public java.util.ArrayList<String> interfaceChain()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
|
||||
return _hidl_out_descriptors;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(NativeHandle fd, java.util.ArrayList<String> options)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
_hidl_request.writeNativeHandle(fd);
|
||||
_hidl_request.writeStringVector(options);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String interfaceDescriptor()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
String _hidl_out_descriptor = _hidl_reply.readString();
|
||||
return _hidl_out_descriptor;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.ArrayList<byte[/* 32 */]> getHashChain()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = new java.util.ArrayList<byte[/* 32 */]>();
|
||||
{
|
||||
HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
|
||||
{
|
||||
int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
|
||||
HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
|
||||
_hidl_vec_size * 32,_hidl_blob.handle(),
|
||||
0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
|
||||
|
||||
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
|
||||
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
|
||||
byte[/* 32 */] _hidl_vec_element = new byte[32];
|
||||
{
|
||||
long _hidl_array_offset_1 = _hidl_index_0 * 32;
|
||||
childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
|
||||
_hidl_array_offset_1 += 32 * 1;
|
||||
}
|
||||
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _hidl_out_hashchain;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHALInstrumentation()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
|
||||
throws android.os.RemoteException {
|
||||
return mRemote.linkToDeath(recipient, cookie);
|
||||
}
|
||||
@Override
|
||||
public void ping()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
|
||||
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
|
||||
return _hidl_out_info;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifySyspropsChanged()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
|
||||
throws android.os.RemoteException {
|
||||
return mRemote.unlinkToDeath(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Stub extends HwBinder implements IBluetoothHci {
|
||||
@Override
|
||||
public IHwBinder asBinder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final java.util.ArrayList<String> interfaceChain() {
|
||||
return new java.util.ArrayList<String>(java.util.Arrays.asList(
|
||||
android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName,
|
||||
android.internal.hidl.base.V1_0.IBase.kInterfaceName));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String interfaceDescriptor() {
|
||||
return android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
|
||||
return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
|
||||
new byte[/* 32 */]{52,124,-25,70,-127,86,7,86,127,95,59,83,-28,-128,9,-104,-54,90,-71,53,81,65,-16,-120,15,-64,-49,12,31,-59,-61,85} /* 347ce746815607567f5f3b53e4800998ca5ab9355141f0880fc0cf0c1fc5c355 */,
|
||||
new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setHALInstrumentation() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void ping() {
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
|
||||
android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
|
||||
info.pid = HidlSupport.getPidIfSharable();
|
||||
info.ptr = 0;
|
||||
info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
|
||||
return info;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void notifySyspropsChanged() {
|
||||
HwBinder.enableInstrumentation();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwInterface queryLocalInterface(String descriptor) {
|
||||
if (kInterfaceName.equals(descriptor)) {
|
||||
return this;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void registerAsService(String serviceName) throws android.os.RemoteException {
|
||||
registerService(serviceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.interfaceDescriptor() + "@Stub";
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
|
||||
throws android.os.RemoteException {
|
||||
switch (_hidl_code) {
|
||||
case 1 /* initialize */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder());
|
||||
initialize(callback);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 2 /* sendHciCommand */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> command = _hidl_request.readInt8Vector();
|
||||
sendHciCommand(command);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 3 /* sendAclData */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
|
||||
sendAclData(data);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 4 /* sendScoData */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
|
||||
sendScoData(data);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 5 /* close */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
close();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256067662 /* interfaceChain */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.writeStringVector(_hidl_out_descriptors);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256131655 /* debug */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
NativeHandle fd = _hidl_request.readNativeHandle();
|
||||
java.util.ArrayList<String> options = _hidl_request.readStringVector();
|
||||
debug(fd, options);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256136003 /* interfaceDescriptor */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
String _hidl_out_descriptor = interfaceDescriptor();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.writeString(_hidl_out_descriptor);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256398152 /* getHashChain */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
{
|
||||
HwBlob _hidl_blob = new HwBlob(16 /* size */);
|
||||
{
|
||||
int _hidl_vec_size = _hidl_out_hashchain.size();
|
||||
_hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
|
||||
_hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
|
||||
HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
|
||||
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
|
||||
{
|
||||
long _hidl_array_offset_1 = _hidl_index_0 * 32;
|
||||
byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
|
||||
|
||||
if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
|
||||
throw new IllegalArgumentException("Array element is not of the expected length");
|
||||
}
|
||||
|
||||
childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
|
||||
_hidl_array_offset_1 += 32 * 1;
|
||||
}
|
||||
}
|
||||
_hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
|
||||
}
|
||||
_hidl_reply.writeBuffer(_hidl_blob);
|
||||
}
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256462420 /* setHALInstrumentation */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
setHALInstrumentation();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256660548 /* linkToDeath */:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
case 256921159 /* ping */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
ping();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257049926 /* getDebugInfo */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257120595 /* notifySyspropsChanged */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
notifySyspropsChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257250372 /* unlinkToDeath */:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,762 @@
|
||||
package android.hardware.bluetooth.V1_0;
|
||||
|
||||
import android.os.HidlSupport;
|
||||
import android.os.HwBinder;
|
||||
import android.os.IHwBinder;
|
||||
import android.os.HwBlob;
|
||||
import android.os.HwParcel;
|
||||
import android.os.IHwInterface;
|
||||
import android.os.NativeHandle;
|
||||
|
||||
/**
|
||||
* The interface from the Bluetooth Controller to the stack.
|
||||
*/
|
||||
public interface IBluetoothHciCallbacks extends android.internal.hidl.base.V1_0.IBase {
|
||||
/**
|
||||
* Fully-qualified interface name for this interface.
|
||||
*/
|
||||
public static final String kInterfaceName = "android.hardware.bluetooth@1.0::IBluetoothHciCallbacks";
|
||||
|
||||
/**
|
||||
* Does a checked conversion from a binder to this class.
|
||||
*/
|
||||
/* package private */ static IBluetoothHciCallbacks asInterface(IHwBinder binder) {
|
||||
if (binder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IHwInterface iface =
|
||||
binder.queryLocalInterface(kInterfaceName);
|
||||
|
||||
if ((iface != null) && (iface instanceof IBluetoothHciCallbacks)) {
|
||||
return (IBluetoothHciCallbacks)iface;
|
||||
}
|
||||
|
||||
IBluetoothHciCallbacks proxy = new IBluetoothHciCallbacks.Proxy(binder);
|
||||
|
||||
try {
|
||||
for (String descriptor : proxy.interfaceChain()) {
|
||||
if (descriptor.equals(kInterfaceName)) {
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
} catch (android.os.RemoteException e) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a checked conversion from any interface to this class.
|
||||
*/
|
||||
public static IBluetoothHciCallbacks castFrom(IHwInterface iface) {
|
||||
return (iface == null) ? null : IBluetoothHciCallbacks.asInterface(iface.asBinder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwBinder asBinder();
|
||||
|
||||
/**
|
||||
* This will invoke the equivalent of the C++ getService(std::string) if retry is
|
||||
* true or tryGetService(std::string) if retry is false. If the service is
|
||||
* available on the device and retry is true, this will wait for the service to
|
||||
* start.
|
||||
*
|
||||
*/
|
||||
public static IBluetoothHciCallbacks getService(String serviceName, boolean retry) throws android.os.RemoteException {
|
||||
return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHciCallbacks", serviceName, retry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls getService("default",retry).
|
||||
*/
|
||||
public static IBluetoothHciCallbacks getService(boolean retry) throws android.os.RemoteException {
|
||||
return getService("default", retry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this will not wait for the interface to come up if it hasn't yet
|
||||
* started. See getService(String,boolean) instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static IBluetoothHciCallbacks getService(String serviceName) throws android.os.RemoteException {
|
||||
return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHciCallbacks", serviceName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this will not wait for the interface to come up if it hasn't yet
|
||||
* started. See getService(boolean) instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static IBluetoothHciCallbacks getService() throws android.os.RemoteException {
|
||||
return getService("default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when the Bluetooth controller initialization has been
|
||||
* completed.
|
||||
*/
|
||||
void initializationComplete(int status)
|
||||
throws android.os.RemoteException;
|
||||
/**
|
||||
* This function is invoked when an HCI event is received from the
|
||||
* Bluetooth controller to be forwarded to the Bluetooth stack.
|
||||
* @param event is the HCI event to be sent to the Bluetooth stack.
|
||||
*/
|
||||
void hciEventReceived(java.util.ArrayList<Byte> event)
|
||||
throws android.os.RemoteException;
|
||||
/**
|
||||
* Send an ACL data packet form the controller to the host.
|
||||
* @param data the ACL HCI packet to be passed to the host stack
|
||||
*/
|
||||
void aclDataReceived(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException;
|
||||
/**
|
||||
* Send a SCO data packet form the controller to the host.
|
||||
* @param data the SCO HCI packet to be passed to the host stack
|
||||
*/
|
||||
void scoDataReceived(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides run-time type information for this object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceChain on an IChild object must yield the following:
|
||||
* ["android.hardware.foo@1.0::IChild",
|
||||
* "android.hardware.foo@1.0::IParent"
|
||||
* "android.internal.hidl.base@1.0::IBase"]
|
||||
*
|
||||
* @return descriptors a vector of descriptors of the run-time type of the
|
||||
* object.
|
||||
*/
|
||||
java.util.ArrayList<String> interfaceChain()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Emit diagnostic information to the given file.
|
||||
*
|
||||
* Optionally overriden.
|
||||
*
|
||||
* @param fd File descriptor to dump data to.
|
||||
* Must only be used for the duration of this call.
|
||||
* @param options Arguments for debugging.
|
||||
* Must support empty for default debug information.
|
||||
*/
|
||||
void debug(NativeHandle fd, java.util.ArrayList<String> options)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides run-time type information for this object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceDescriptor on an IChild object must yield
|
||||
* "android.hardware.foo@1.0::IChild"
|
||||
*
|
||||
* @return descriptor a descriptor of the run-time type of the
|
||||
* object (the first element of the vector returned by
|
||||
* interfaceChain())
|
||||
*/
|
||||
String interfaceDescriptor()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Returns hashes of the source HAL files that define the interfaces of the
|
||||
* runtime type information on the object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceChain on an IChild object must yield the following:
|
||||
* [(hash of IChild.hal),
|
||||
* (hash of IParent.hal)
|
||||
* (hash of IBase.hal)].
|
||||
*
|
||||
* SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
|
||||
* according to SHA-256 standard.
|
||||
*
|
||||
* @return hashchain a vector of SHA-1 digests
|
||||
*/
|
||||
java.util.ArrayList<byte[/* 32 */]> getHashChain()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* This method trigger the interface to enable/disable instrumentation based
|
||||
* on system property hal.instrumentation.enable.
|
||||
*/
|
||||
void setHALInstrumentation()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Registers a death recipient, to be called when the process hosting this
|
||||
* interface dies.
|
||||
*
|
||||
* @param recipient a hidl_death_recipient callback object
|
||||
* @param cookie a cookie that must be returned with the callback
|
||||
* @return success whether the death recipient was registered successfully.
|
||||
*/
|
||||
boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides way to determine if interface is running without requesting
|
||||
* any functionality.
|
||||
*/
|
||||
void ping()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Get debug information on references on this interface.
|
||||
* @return info debugging information. See comments of DebugInfo.
|
||||
*/
|
||||
android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* This method notifies the interface that one or more system properties
|
||||
* have changed. The default implementation calls
|
||||
* (C++) report_sysprop_change() in libcutils or
|
||||
* (Java) android.os.SystemProperties.reportSyspropChanged,
|
||||
* which in turn calls a set of registered callbacks (eg to update trace
|
||||
* tags).
|
||||
*/
|
||||
void notifySyspropsChanged()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Unregisters the registered death recipient. If this service was registered
|
||||
* multiple times with the same exact death recipient, this unlinks the most
|
||||
* recently registered one.
|
||||
*
|
||||
* @param recipient a previously registered hidl_death_recipient callback
|
||||
* @return success whether the death recipient was unregistered successfully.
|
||||
*/
|
||||
boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
|
||||
throws android.os.RemoteException;
|
||||
|
||||
public static final class Proxy implements IBluetoothHciCallbacks {
|
||||
private IHwBinder mRemote;
|
||||
|
||||
public Proxy(IHwBinder remote) {
|
||||
mRemote = java.util.Objects.requireNonNull(remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwBinder asBinder() {
|
||||
return mRemote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return this.interfaceDescriptor() + "@Proxy";
|
||||
} catch (android.os.RemoteException ex) {
|
||||
/* ignored; handled below. */
|
||||
}
|
||||
return "[class or subclass of " + IBluetoothHciCallbacks.kInterfaceName + "]@Proxy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(java.lang.Object other) {
|
||||
return HidlSupport.interfacesEqual(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return this.asBinder().hashCode();
|
||||
}
|
||||
|
||||
// Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHciCallbacks follow.
|
||||
@Override
|
||||
public void initializationComplete(int status)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
_hidl_request.writeInt32(status);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(1 /* initializationComplete */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hciEventReceived(java.util.ArrayList<Byte> event)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(event);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(2 /* hciEventReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void aclDataReceived(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(data);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(3 /* aclDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scoDataReceived(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(data);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(4 /* scoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Methods from ::android::hidl::base::V1_0::IBase follow.
|
||||
@Override
|
||||
public java.util.ArrayList<String> interfaceChain()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
|
||||
return _hidl_out_descriptors;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(NativeHandle fd, java.util.ArrayList<String> options)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
_hidl_request.writeNativeHandle(fd);
|
||||
_hidl_request.writeStringVector(options);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String interfaceDescriptor()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
String _hidl_out_descriptor = _hidl_reply.readString();
|
||||
return _hidl_out_descriptor;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.ArrayList<byte[/* 32 */]> getHashChain()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = new java.util.ArrayList<byte[/* 32 */]>();
|
||||
{
|
||||
HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
|
||||
{
|
||||
int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
|
||||
HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
|
||||
_hidl_vec_size * 32,_hidl_blob.handle(),
|
||||
0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
|
||||
|
||||
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
|
||||
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
|
||||
byte[/* 32 */] _hidl_vec_element = new byte[32];
|
||||
{
|
||||
long _hidl_array_offset_1 = _hidl_index_0 * 32;
|
||||
childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
|
||||
_hidl_array_offset_1 += 32 * 1;
|
||||
}
|
||||
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _hidl_out_hashchain;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHALInstrumentation()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
|
||||
throws android.os.RemoteException {
|
||||
return mRemote.linkToDeath(recipient, cookie);
|
||||
}
|
||||
@Override
|
||||
public void ping()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
|
||||
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
|
||||
return _hidl_out_info;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifySyspropsChanged()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
|
||||
throws android.os.RemoteException {
|
||||
return mRemote.unlinkToDeath(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Stub extends HwBinder implements IBluetoothHciCallbacks {
|
||||
@Override
|
||||
public IHwBinder asBinder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final java.util.ArrayList<String> interfaceChain() {
|
||||
return new java.util.ArrayList<String>(java.util.Arrays.asList(
|
||||
android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName,
|
||||
android.internal.hidl.base.V1_0.IBase.kInterfaceName));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String interfaceDescriptor() {
|
||||
return android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
|
||||
return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
|
||||
new byte[/* 32 */]{-125,95,65,-66,34,-127,-65,-78,47,62,51,-58,-6,-121,11,-34,123,-62,30,55,-27,-49,-70,-7,-93,111,-1,23,6,50,-9,84} /* 835f41be2281bfb22f3e33c6fa870bde7bc21e37e5cfbaf9a36fff170632f754 */,
|
||||
new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setHALInstrumentation() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void ping() {
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
|
||||
android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
|
||||
info.pid = HidlSupport.getPidIfSharable();
|
||||
info.ptr = 0;
|
||||
info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
|
||||
return info;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void notifySyspropsChanged() {
|
||||
HwBinder.enableInstrumentation();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwInterface queryLocalInterface(String descriptor) {
|
||||
if (kInterfaceName.equals(descriptor)) {
|
||||
return this;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void registerAsService(String serviceName) throws android.os.RemoteException {
|
||||
registerService(serviceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.interfaceDescriptor() + "@Stub";
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
|
||||
throws android.os.RemoteException {
|
||||
switch (_hidl_code) {
|
||||
case 1 /* initializationComplete */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
|
||||
int status = _hidl_request.readInt32();
|
||||
initializationComplete(status);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 2 /* hciEventReceived */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> event = _hidl_request.readInt8Vector();
|
||||
hciEventReceived(event);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 3 /* aclDataReceived */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
|
||||
aclDataReceived(data);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 4 /* scoDataReceived */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
|
||||
scoDataReceived(data);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256067662 /* interfaceChain */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.writeStringVector(_hidl_out_descriptors);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256131655 /* debug */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
NativeHandle fd = _hidl_request.readNativeHandle();
|
||||
java.util.ArrayList<String> options = _hidl_request.readStringVector();
|
||||
debug(fd, options);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256136003 /* interfaceDescriptor */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
String _hidl_out_descriptor = interfaceDescriptor();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.writeString(_hidl_out_descriptor);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256398152 /* getHashChain */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
{
|
||||
HwBlob _hidl_blob = new HwBlob(16 /* size */);
|
||||
{
|
||||
int _hidl_vec_size = _hidl_out_hashchain.size();
|
||||
_hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
|
||||
_hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
|
||||
HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
|
||||
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
|
||||
{
|
||||
long _hidl_array_offset_1 = _hidl_index_0 * 32;
|
||||
byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
|
||||
|
||||
if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
|
||||
throw new IllegalArgumentException("Array element is not of the expected length");
|
||||
}
|
||||
|
||||
childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
|
||||
_hidl_array_offset_1 += 32 * 1;
|
||||
}
|
||||
}
|
||||
_hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
|
||||
}
|
||||
_hidl_reply.writeBuffer(_hidl_blob);
|
||||
}
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256462420 /* setHALInstrumentation */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
setHALInstrumentation();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256660548 /* linkToDeath */:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
case 256921159 /* ping */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
ping();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257049926 /* getDebugInfo */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257120595 /* notifySyspropsChanged */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
notifySyspropsChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257250372 /* unlinkToDeath */:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package android.hardware.bluetooth.V1_0;
|
||||
|
||||
|
||||
public final class Status {
|
||||
public static final int SUCCESS = 0;
|
||||
public static final int TRANSPORT_ERROR = 1 /* ::android::hardware::bluetooth::V1_0::Status.SUCCESS implicitly + 1 */;
|
||||
public static final int INITIALIZATION_ERROR = 2 /* ::android::hardware::bluetooth::V1_0::Status.TRANSPORT_ERROR implicitly + 1 */;
|
||||
public static final int UNKNOWN = 3 /* ::android::hardware::bluetooth::V1_0::Status.INITIALIZATION_ERROR implicitly + 1 */;
|
||||
public static final String toString(int o) {
|
||||
if (o == SUCCESS) {
|
||||
return "SUCCESS";
|
||||
}
|
||||
if (o == TRANSPORT_ERROR) {
|
||||
return "TRANSPORT_ERROR";
|
||||
}
|
||||
if (o == INITIALIZATION_ERROR) {
|
||||
return "INITIALIZATION_ERROR";
|
||||
}
|
||||
if (o == UNKNOWN) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
return "0x" + Integer.toHexString(o);
|
||||
}
|
||||
|
||||
public static final String dumpBitfield(int o) {
|
||||
java.util.ArrayList<String> list = new java.util.ArrayList<>();
|
||||
int flipped = 0;
|
||||
list.add("SUCCESS"); // SUCCESS == 0
|
||||
if ((o & TRANSPORT_ERROR) == TRANSPORT_ERROR) {
|
||||
list.add("TRANSPORT_ERROR");
|
||||
flipped |= TRANSPORT_ERROR;
|
||||
}
|
||||
if ((o & INITIALIZATION_ERROR) == INITIALIZATION_ERROR) {
|
||||
list.add("INITIALIZATION_ERROR");
|
||||
flipped |= INITIALIZATION_ERROR;
|
||||
}
|
||||
if ((o & UNKNOWN) == UNKNOWN) {
|
||||
list.add("UNKNOWN");
|
||||
flipped |= UNKNOWN;
|
||||
}
|
||||
if (o != flipped) {
|
||||
list.add("0x" + Integer.toHexString(o & (~flipped)));
|
||||
}
|
||||
return String.join(" | ", list);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,840 @@
|
||||
package android.hardware.bluetooth.V1_1;
|
||||
|
||||
import android.os.HidlSupport;
|
||||
import android.os.HwBinder;
|
||||
import android.os.IHwBinder;
|
||||
import android.os.HwBlob;
|
||||
import android.os.HwParcel;
|
||||
import android.os.IHwInterface;
|
||||
import android.os.NativeHandle;
|
||||
|
||||
/**
|
||||
* The Host Controller Interface (HCI) is the layer defined by the Bluetooth
|
||||
* specification between the software that runs on the host and the Bluetooth
|
||||
* controller chip. This boundary is the natural choice for a Hardware
|
||||
* Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies
|
||||
* the stack and abstracts away power management, initialization, and other
|
||||
* implementation-specific details related to the hardware.
|
||||
*/
|
||||
public interface IBluetoothHci extends android.hardware.bluetooth.V1_0.IBluetoothHci {
|
||||
/**
|
||||
* Fully-qualified interface name for this interface.
|
||||
*/
|
||||
public static final String kInterfaceName = "android.hardware.bluetooth@1.1::IBluetoothHci";
|
||||
|
||||
/**
|
||||
* Does a checked conversion from a binder to this class.
|
||||
*/
|
||||
/* package private */ static IBluetoothHci asInterface(IHwBinder binder) {
|
||||
if (binder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IHwInterface iface =
|
||||
binder.queryLocalInterface(kInterfaceName);
|
||||
|
||||
if ((iface != null) && (iface instanceof IBluetoothHci)) {
|
||||
return (IBluetoothHci)iface;
|
||||
}
|
||||
|
||||
IBluetoothHci proxy = new IBluetoothHci.Proxy(binder);
|
||||
|
||||
try {
|
||||
for (String descriptor : proxy.interfaceChain()) {
|
||||
if (descriptor.equals(kInterfaceName)) {
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
} catch (android.os.RemoteException e) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a checked conversion from any interface to this class.
|
||||
*/
|
||||
public static IBluetoothHci castFrom(IHwInterface iface) {
|
||||
return (iface == null) ? null : IBluetoothHci.asInterface(iface.asBinder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwBinder asBinder();
|
||||
|
||||
/**
|
||||
* This will invoke the equivalent of the C++ getService(std::string) if retry is
|
||||
* true or tryGetService(std::string) if retry is false. If the service is
|
||||
* available on the device and retry is true, this will wait for the service to
|
||||
* start.
|
||||
*
|
||||
*/
|
||||
public static IBluetoothHci getService(String serviceName, boolean retry) throws android.os.RemoteException {
|
||||
return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHci", serviceName, retry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls getService("default",retry).
|
||||
*/
|
||||
public static IBluetoothHci getService(boolean retry) throws android.os.RemoteException {
|
||||
return getService("default", retry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this will not wait for the interface to come up if it hasn't yet
|
||||
* started. See getService(String,boolean) instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static IBluetoothHci getService(String serviceName) throws android.os.RemoteException {
|
||||
return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHci", serviceName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this will not wait for the interface to come up if it hasn't yet
|
||||
* started. See getService(boolean) instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static IBluetoothHci getService() throws android.os.RemoteException {
|
||||
return getService("default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as @1.0, but uses 1.1 Callbacks version
|
||||
*/
|
||||
void initialize_1_1(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback)
|
||||
throws android.os.RemoteException;
|
||||
/**
|
||||
* Send an ISO data packet (as specified in the Bluetooth Core
|
||||
* Specification v5.2) to the Bluetooth controller.
|
||||
* Packets must be processed in order.
|
||||
* @param data HCI data packet to be sent
|
||||
*/
|
||||
void sendIsoData(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides run-time type information for this object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceChain on an IChild object must yield the following:
|
||||
* ["android.hardware.foo@1.0::IChild",
|
||||
* "android.hardware.foo@1.0::IParent"
|
||||
* "android.internal.hidl.base@1.0::IBase"]
|
||||
*
|
||||
* @return descriptors a vector of descriptors of the run-time type of the
|
||||
* object.
|
||||
*/
|
||||
java.util.ArrayList<String> interfaceChain()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Emit diagnostic information to the given file.
|
||||
*
|
||||
* Optionally overriden.
|
||||
*
|
||||
* @param fd File descriptor to dump data to.
|
||||
* Must only be used for the duration of this call.
|
||||
* @param options Arguments for debugging.
|
||||
* Must support empty for default debug information.
|
||||
*/
|
||||
void debug(NativeHandle fd, java.util.ArrayList<String> options)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides run-time type information for this object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceDescriptor on an IChild object must yield
|
||||
* "android.hardware.foo@1.0::IChild"
|
||||
*
|
||||
* @return descriptor a descriptor of the run-time type of the
|
||||
* object (the first element of the vector returned by
|
||||
* interfaceChain())
|
||||
*/
|
||||
String interfaceDescriptor()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Returns hashes of the source HAL files that define the interfaces of the
|
||||
* runtime type information on the object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceChain on an IChild object must yield the following:
|
||||
* [(hash of IChild.hal),
|
||||
* (hash of IParent.hal)
|
||||
* (hash of IBase.hal)].
|
||||
*
|
||||
* SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
|
||||
* according to SHA-256 standard.
|
||||
*
|
||||
* @return hashchain a vector of SHA-1 digests
|
||||
*/
|
||||
java.util.ArrayList<byte[/* 32 */]> getHashChain()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* This method trigger the interface to enable/disable instrumentation based
|
||||
* on system property hal.instrumentation.enable.
|
||||
*/
|
||||
void setHALInstrumentation()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Registers a death recipient, to be called when the process hosting this
|
||||
* interface dies.
|
||||
*
|
||||
* @param recipient a hidl_death_recipient callback object
|
||||
* @param cookie a cookie that must be returned with the callback
|
||||
* @return success whether the death recipient was registered successfully.
|
||||
*/
|
||||
boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides way to determine if interface is running without requesting
|
||||
* any functionality.
|
||||
*/
|
||||
void ping()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Get debug information on references on this interface.
|
||||
* @return info debugging information. See comments of DebugInfo.
|
||||
*/
|
||||
android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* This method notifies the interface that one or more system properties
|
||||
* have changed. The default implementation calls
|
||||
* (C++) report_sysprop_change() in libcutils or
|
||||
* (Java) android.os.SystemProperties.reportSyspropChanged,
|
||||
* which in turn calls a set of registered callbacks (eg to update trace
|
||||
* tags).
|
||||
*/
|
||||
void notifySyspropsChanged()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Unregisters the registered death recipient. If this service was registered
|
||||
* multiple times with the same exact death recipient, this unlinks the most
|
||||
* recently registered one.
|
||||
*
|
||||
* @param recipient a previously registered hidl_death_recipient callback
|
||||
* @return success whether the death recipient was unregistered successfully.
|
||||
*/
|
||||
boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
|
||||
throws android.os.RemoteException;
|
||||
|
||||
public static final class Proxy implements IBluetoothHci {
|
||||
private IHwBinder mRemote;
|
||||
|
||||
public Proxy(IHwBinder remote) {
|
||||
mRemote = java.util.Objects.requireNonNull(remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwBinder asBinder() {
|
||||
return mRemote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return this.interfaceDescriptor() + "@Proxy";
|
||||
} catch (android.os.RemoteException ex) {
|
||||
/* ignored; handled below. */
|
||||
}
|
||||
return "[class or subclass of " + IBluetoothHci.kInterfaceName + "]@Proxy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(java.lang.Object other) {
|
||||
return HidlSupport.interfacesEqual(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return this.asBinder().hashCode();
|
||||
}
|
||||
|
||||
// Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHci follow.
|
||||
@Override
|
||||
public void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
_hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder());
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(1 /* initialize */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendHciCommand(java.util.ArrayList<Byte> command)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(command);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(2 /* sendHciCommand */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendAclData(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(data);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(3 /* sendAclData */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendScoData(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(data);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(4 /* sendScoData */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(5 /* close */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Methods from ::android::hardware::bluetooth::V1_1::IBluetoothHci follow.
|
||||
@Override
|
||||
public void initialize_1_1(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
|
||||
_hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder());
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(6 /* initialize_1_1 */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendIsoData(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(data);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(7 /* sendIsoData */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Methods from ::android::hidl::base::V1_0::IBase follow.
|
||||
@Override
|
||||
public java.util.ArrayList<String> interfaceChain()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
|
||||
return _hidl_out_descriptors;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(NativeHandle fd, java.util.ArrayList<String> options)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
_hidl_request.writeNativeHandle(fd);
|
||||
_hidl_request.writeStringVector(options);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String interfaceDescriptor()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
String _hidl_out_descriptor = _hidl_reply.readString();
|
||||
return _hidl_out_descriptor;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.ArrayList<byte[/* 32 */]> getHashChain()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = new java.util.ArrayList<byte[/* 32 */]>();
|
||||
{
|
||||
HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
|
||||
{
|
||||
int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
|
||||
HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
|
||||
_hidl_vec_size * 32,_hidl_blob.handle(),
|
||||
0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
|
||||
|
||||
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
|
||||
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
|
||||
byte[/* 32 */] _hidl_vec_element = new byte[32];
|
||||
{
|
||||
long _hidl_array_offset_1 = _hidl_index_0 * 32;
|
||||
childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
|
||||
_hidl_array_offset_1 += 32 * 1;
|
||||
}
|
||||
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _hidl_out_hashchain;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHALInstrumentation()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
|
||||
throws android.os.RemoteException {
|
||||
return mRemote.linkToDeath(recipient, cookie);
|
||||
}
|
||||
@Override
|
||||
public void ping()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
|
||||
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
|
||||
return _hidl_out_info;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifySyspropsChanged()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
|
||||
throws android.os.RemoteException {
|
||||
return mRemote.unlinkToDeath(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Stub extends HwBinder implements IBluetoothHci {
|
||||
@Override
|
||||
public IHwBinder asBinder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final java.util.ArrayList<String> interfaceChain() {
|
||||
return new java.util.ArrayList<String>(java.util.Arrays.asList(
|
||||
android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName,
|
||||
android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName,
|
||||
android.internal.hidl.base.V1_0.IBase.kInterfaceName));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String interfaceDescriptor() {
|
||||
return android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
|
||||
return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
|
||||
new byte[/* 32 */]{54,47,-47,-62,22,65,-62,34,79,59,-128,-61,13,-105,-105,-71,-120,-6,63,52,66,67,-43,49,-70,115,-59,83,119,-102,87,99} /* 362fd1c21641c2224f3b80c30d9797b988fa3f344243d531ba73c553779a5763 */,
|
||||
new byte[/* 32 */]{52,124,-25,70,-127,86,7,86,127,95,59,83,-28,-128,9,-104,-54,90,-71,53,81,65,-16,-120,15,-64,-49,12,31,-59,-61,85} /* 347ce746815607567f5f3b53e4800998ca5ab9355141f0880fc0cf0c1fc5c355 */,
|
||||
new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setHALInstrumentation() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void ping() {
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
|
||||
android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
|
||||
info.pid = HidlSupport.getPidIfSharable();
|
||||
info.ptr = 0;
|
||||
info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
|
||||
return info;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void notifySyspropsChanged() {
|
||||
HwBinder.enableInstrumentation();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwInterface queryLocalInterface(String descriptor) {
|
||||
if (kInterfaceName.equals(descriptor)) {
|
||||
return this;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void registerAsService(String serviceName) throws android.os.RemoteException {
|
||||
registerService(serviceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.interfaceDescriptor() + "@Stub";
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
|
||||
throws android.os.RemoteException {
|
||||
switch (_hidl_code) {
|
||||
case 1 /* initialize */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder());
|
||||
initialize(callback);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 2 /* sendHciCommand */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> command = _hidl_request.readInt8Vector();
|
||||
sendHciCommand(command);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 3 /* sendAclData */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
|
||||
sendAclData(data);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 4 /* sendScoData */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
|
||||
sendScoData(data);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 5 /* close */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
|
||||
|
||||
close();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 6 /* initialize_1_1 */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
|
||||
|
||||
android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder());
|
||||
initialize_1_1(callback);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 7 /* sendIsoData */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
|
||||
sendIsoData(data);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256067662 /* interfaceChain */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.writeStringVector(_hidl_out_descriptors);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256131655 /* debug */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
NativeHandle fd = _hidl_request.readNativeHandle();
|
||||
java.util.ArrayList<String> options = _hidl_request.readStringVector();
|
||||
debug(fd, options);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256136003 /* interfaceDescriptor */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
String _hidl_out_descriptor = interfaceDescriptor();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.writeString(_hidl_out_descriptor);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256398152 /* getHashChain */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
{
|
||||
HwBlob _hidl_blob = new HwBlob(16 /* size */);
|
||||
{
|
||||
int _hidl_vec_size = _hidl_out_hashchain.size();
|
||||
_hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
|
||||
_hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
|
||||
HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
|
||||
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
|
||||
{
|
||||
long _hidl_array_offset_1 = _hidl_index_0 * 32;
|
||||
byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
|
||||
|
||||
if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
|
||||
throw new IllegalArgumentException("Array element is not of the expected length");
|
||||
}
|
||||
|
||||
childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
|
||||
_hidl_array_offset_1 += 32 * 1;
|
||||
}
|
||||
}
|
||||
_hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
|
||||
}
|
||||
_hidl_reply.writeBuffer(_hidl_blob);
|
||||
}
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256462420 /* setHALInstrumentation */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
setHALInstrumentation();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256660548 /* linkToDeath */:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
case 256921159 /* ping */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
ping();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257049926 /* getDebugInfo */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257120595 /* notifySyspropsChanged */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
notifySyspropsChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257250372 /* unlinkToDeath */:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,774 @@
|
||||
package android.hardware.bluetooth.V1_1;
|
||||
|
||||
import android.os.HidlSupport;
|
||||
import android.os.HwBinder;
|
||||
import android.os.IHwBinder;
|
||||
import android.os.HwBlob;
|
||||
import android.os.HwParcel;
|
||||
import android.os.IHwInterface;
|
||||
import android.os.NativeHandle;
|
||||
|
||||
/**
|
||||
* The interface from the Bluetooth Controller to the stack.
|
||||
*/
|
||||
public interface IBluetoothHciCallbacks extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks {
|
||||
/**
|
||||
* Fully-qualified interface name for this interface.
|
||||
*/
|
||||
public static final String kInterfaceName = "android.hardware.bluetooth@1.1::IBluetoothHciCallbacks";
|
||||
|
||||
/**
|
||||
* Does a checked conversion from a binder to this class.
|
||||
*/
|
||||
/* package private */ static IBluetoothHciCallbacks asInterface(IHwBinder binder) {
|
||||
if (binder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IHwInterface iface =
|
||||
binder.queryLocalInterface(kInterfaceName);
|
||||
|
||||
if ((iface != null) && (iface instanceof IBluetoothHciCallbacks)) {
|
||||
return (IBluetoothHciCallbacks)iface;
|
||||
}
|
||||
|
||||
IBluetoothHciCallbacks proxy = new IBluetoothHciCallbacks.Proxy(binder);
|
||||
|
||||
try {
|
||||
for (String descriptor : proxy.interfaceChain()) {
|
||||
if (descriptor.equals(kInterfaceName)) {
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
} catch (android.os.RemoteException e) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a checked conversion from any interface to this class.
|
||||
*/
|
||||
public static IBluetoothHciCallbacks castFrom(IHwInterface iface) {
|
||||
return (iface == null) ? null : IBluetoothHciCallbacks.asInterface(iface.asBinder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwBinder asBinder();
|
||||
|
||||
/**
|
||||
* This will invoke the equivalent of the C++ getService(std::string) if retry is
|
||||
* true or tryGetService(std::string) if retry is false. If the service is
|
||||
* available on the device and retry is true, this will wait for the service to
|
||||
* start.
|
||||
*
|
||||
*/
|
||||
public static IBluetoothHciCallbacks getService(String serviceName, boolean retry) throws android.os.RemoteException {
|
||||
return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHciCallbacks", serviceName, retry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls getService("default",retry).
|
||||
*/
|
||||
public static IBluetoothHciCallbacks getService(boolean retry) throws android.os.RemoteException {
|
||||
return getService("default", retry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this will not wait for the interface to come up if it hasn't yet
|
||||
* started. See getService(String,boolean) instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static IBluetoothHciCallbacks getService(String serviceName) throws android.os.RemoteException {
|
||||
return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHciCallbacks", serviceName));
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated this will not wait for the interface to come up if it hasn't yet
|
||||
* started. See getService(boolean) instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static IBluetoothHciCallbacks getService() throws android.os.RemoteException {
|
||||
return getService("default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a ISO data packet form the controller to the host.
|
||||
* @param data the ISO HCI packet to be passed to the host stack
|
||||
*/
|
||||
void isoDataReceived(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides run-time type information for this object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceChain on an IChild object must yield the following:
|
||||
* ["android.hardware.foo@1.0::IChild",
|
||||
* "android.hardware.foo@1.0::IParent"
|
||||
* "android.internal.hidl.base@1.0::IBase"]
|
||||
*
|
||||
* @return descriptors a vector of descriptors of the run-time type of the
|
||||
* object.
|
||||
*/
|
||||
java.util.ArrayList<String> interfaceChain()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Emit diagnostic information to the given file.
|
||||
*
|
||||
* Optionally overriden.
|
||||
*
|
||||
* @param fd File descriptor to dump data to.
|
||||
* Must only be used for the duration of this call.
|
||||
* @param options Arguments for debugging.
|
||||
* Must support empty for default debug information.
|
||||
*/
|
||||
void debug(NativeHandle fd, java.util.ArrayList<String> options)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides run-time type information for this object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceDescriptor on an IChild object must yield
|
||||
* "android.hardware.foo@1.0::IChild"
|
||||
*
|
||||
* @return descriptor a descriptor of the run-time type of the
|
||||
* object (the first element of the vector returned by
|
||||
* interfaceChain())
|
||||
*/
|
||||
String interfaceDescriptor()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Returns hashes of the source HAL files that define the interfaces of the
|
||||
* runtime type information on the object.
|
||||
* For example, for the following interface definition:
|
||||
* package android.hardware.foo@1.0;
|
||||
* interface IParent {};
|
||||
* interface IChild extends IParent {};
|
||||
* Calling interfaceChain on an IChild object must yield the following:
|
||||
* [(hash of IChild.hal),
|
||||
* (hash of IParent.hal)
|
||||
* (hash of IBase.hal)].
|
||||
*
|
||||
* SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
|
||||
* according to SHA-256 standard.
|
||||
*
|
||||
* @return hashchain a vector of SHA-1 digests
|
||||
*/
|
||||
java.util.ArrayList<byte[/* 32 */]> getHashChain()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* This method trigger the interface to enable/disable instrumentation based
|
||||
* on system property hal.instrumentation.enable.
|
||||
*/
|
||||
void setHALInstrumentation()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Registers a death recipient, to be called when the process hosting this
|
||||
* interface dies.
|
||||
*
|
||||
* @param recipient a hidl_death_recipient callback object
|
||||
* @param cookie a cookie that must be returned with the callback
|
||||
* @return success whether the death recipient was registered successfully.
|
||||
*/
|
||||
boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Provides way to determine if interface is running without requesting
|
||||
* any functionality.
|
||||
*/
|
||||
void ping()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Get debug information on references on this interface.
|
||||
* @return info debugging information. See comments of DebugInfo.
|
||||
*/
|
||||
android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* This method notifies the interface that one or more system properties
|
||||
* have changed. The default implementation calls
|
||||
* (C++) report_sysprop_change() in libcutils or
|
||||
* (Java) android.os.SystemProperties.reportSyspropChanged,
|
||||
* which in turn calls a set of registered callbacks (eg to update trace
|
||||
* tags).
|
||||
*/
|
||||
void notifySyspropsChanged()
|
||||
throws android.os.RemoteException;
|
||||
/*
|
||||
* Unregisters the registered death recipient. If this service was registered
|
||||
* multiple times with the same exact death recipient, this unlinks the most
|
||||
* recently registered one.
|
||||
*
|
||||
* @param recipient a previously registered hidl_death_recipient callback
|
||||
* @return success whether the death recipient was unregistered successfully.
|
||||
*/
|
||||
boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
|
||||
throws android.os.RemoteException;
|
||||
|
||||
public static final class Proxy implements IBluetoothHciCallbacks {
|
||||
private IHwBinder mRemote;
|
||||
|
||||
public Proxy(IHwBinder remote) {
|
||||
mRemote = java.util.Objects.requireNonNull(remote);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwBinder asBinder() {
|
||||
return mRemote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return this.interfaceDescriptor() + "@Proxy";
|
||||
} catch (android.os.RemoteException ex) {
|
||||
/* ignored; handled below. */
|
||||
}
|
||||
return "[class or subclass of " + IBluetoothHciCallbacks.kInterfaceName + "]@Proxy";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(java.lang.Object other) {
|
||||
return HidlSupport.interfacesEqual(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return this.asBinder().hashCode();
|
||||
}
|
||||
|
||||
// Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHciCallbacks follow.
|
||||
@Override
|
||||
public void initializationComplete(int status)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
_hidl_request.writeInt32(status);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(1 /* initializationComplete */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hciEventReceived(java.util.ArrayList<Byte> event)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(event);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(2 /* hciEventReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void aclDataReceived(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(data);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(3 /* aclDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scoDataReceived(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(data);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(4 /* scoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Methods from ::android::hardware::bluetooth::V1_1::IBluetoothHciCallbacks follow.
|
||||
@Override
|
||||
public void isoDataReceived(java.util.ArrayList<Byte> data)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName);
|
||||
_hidl_request.writeInt8Vector(data);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(5 /* isoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
// Methods from ::android::hidl::base::V1_0::IBase follow.
|
||||
@Override
|
||||
public java.util.ArrayList<String> interfaceChain()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
|
||||
return _hidl_out_descriptors;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(NativeHandle fd, java.util.ArrayList<String> options)
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
_hidl_request.writeNativeHandle(fd);
|
||||
_hidl_request.writeStringVector(options);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String interfaceDescriptor()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
String _hidl_out_descriptor = _hidl_reply.readString();
|
||||
return _hidl_out_descriptor;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public java.util.ArrayList<byte[/* 32 */]> getHashChain()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = new java.util.ArrayList<byte[/* 32 */]>();
|
||||
{
|
||||
HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
|
||||
{
|
||||
int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
|
||||
HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
|
||||
_hidl_vec_size * 32,_hidl_blob.handle(),
|
||||
0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
|
||||
|
||||
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
|
||||
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
|
||||
byte[/* 32 */] _hidl_vec_element = new byte[32];
|
||||
{
|
||||
long _hidl_array_offset_1 = _hidl_index_0 * 32;
|
||||
childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
|
||||
_hidl_array_offset_1 += 32 * 1;
|
||||
}
|
||||
((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _hidl_out_hashchain;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHALInstrumentation()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
|
||||
throws android.os.RemoteException {
|
||||
return mRemote.linkToDeath(recipient, cookie);
|
||||
}
|
||||
@Override
|
||||
public void ping()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
|
||||
_hidl_reply.verifySuccess();
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
|
||||
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
|
||||
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
|
||||
return _hidl_out_info;
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifySyspropsChanged()
|
||||
throws android.os.RemoteException {
|
||||
HwParcel _hidl_request = new HwParcel();
|
||||
_hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
HwParcel _hidl_reply = new HwParcel();
|
||||
try {
|
||||
mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
|
||||
_hidl_request.releaseTemporaryStorage();
|
||||
} finally {
|
||||
_hidl_reply.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
|
||||
throws android.os.RemoteException {
|
||||
return mRemote.unlinkToDeath(recipient);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class Stub extends HwBinder implements IBluetoothHciCallbacks {
|
||||
@Override
|
||||
public IHwBinder asBinder() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final java.util.ArrayList<String> interfaceChain() {
|
||||
return new java.util.ArrayList<String>(java.util.Arrays.asList(
|
||||
android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName,
|
||||
android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName,
|
||||
android.internal.hidl.base.V1_0.IBase.kInterfaceName));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String interfaceDescriptor() {
|
||||
return android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
|
||||
return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
|
||||
new byte[/* 32 */]{64,-85,44,104,102,-63,-115,50,-70,-10,-28,-98,48,83,-108,-98,121,96,31,86,-106,58,121,30,-109,-26,-117,-98,-31,-113,113,-115} /* 40ab2c6866c18d32baf6e49e3053949e79601f56963a791e93e68b9ee18f718d */,
|
||||
new byte[/* 32 */]{-125,95,65,-66,34,-127,-65,-78,47,62,51,-58,-6,-121,11,-34,123,-62,30,55,-27,-49,-70,-7,-93,111,-1,23,6,50,-9,84} /* 835f41be2281bfb22f3e33c6fa870bde7bc21e37e5cfbaf9a36fff170632f754 */,
|
||||
new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setHALInstrumentation() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void ping() {
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
|
||||
android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
|
||||
info.pid = HidlSupport.getPidIfSharable();
|
||||
info.ptr = 0;
|
||||
info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
|
||||
return info;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void notifySyspropsChanged() {
|
||||
HwBinder.enableInstrumentation();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IHwInterface queryLocalInterface(String descriptor) {
|
||||
if (kInterfaceName.equals(descriptor)) {
|
||||
return this;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void registerAsService(String serviceName) throws android.os.RemoteException {
|
||||
registerService(serviceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.interfaceDescriptor() + "@Stub";
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
|
||||
throws android.os.RemoteException {
|
||||
switch (_hidl_code) {
|
||||
case 1 /* initializationComplete */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
|
||||
int status = _hidl_request.readInt32();
|
||||
initializationComplete(status);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 2 /* hciEventReceived */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> event = _hidl_request.readInt8Vector();
|
||||
hciEventReceived(event);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 3 /* aclDataReceived */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
|
||||
aclDataReceived(data);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 4 /* scoDataReceived */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
|
||||
scoDataReceived(data);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 5 /* isoDataReceived */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
|
||||
isoDataReceived(data);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256067662 /* interfaceChain */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.writeStringVector(_hidl_out_descriptors);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256131655 /* debug */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
NativeHandle fd = _hidl_request.readNativeHandle();
|
||||
java.util.ArrayList<String> options = _hidl_request.readStringVector();
|
||||
debug(fd, options);
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256136003 /* interfaceDescriptor */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
String _hidl_out_descriptor = interfaceDescriptor();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.writeString(_hidl_out_descriptor);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256398152 /* getHashChain */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
{
|
||||
HwBlob _hidl_blob = new HwBlob(16 /* size */);
|
||||
{
|
||||
int _hidl_vec_size = _hidl_out_hashchain.size();
|
||||
_hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
|
||||
_hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
|
||||
HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
|
||||
for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
|
||||
{
|
||||
long _hidl_array_offset_1 = _hidl_index_0 * 32;
|
||||
byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
|
||||
|
||||
if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
|
||||
throw new IllegalArgumentException("Array element is not of the expected length");
|
||||
}
|
||||
|
||||
childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
|
||||
_hidl_array_offset_1 += 32 * 1;
|
||||
}
|
||||
}
|
||||
_hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
|
||||
}
|
||||
_hidl_reply.writeBuffer(_hidl_blob);
|
||||
}
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256462420 /* setHALInstrumentation */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
setHALInstrumentation();
|
||||
break;
|
||||
}
|
||||
|
||||
case 256660548 /* linkToDeath */:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
case 256921159 /* ping */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
ping();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257049926 /* getDebugInfo */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
|
||||
_hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
|
||||
((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
|
||||
_hidl_reply.send();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257120595 /* notifySyspropsChanged */:
|
||||
{
|
||||
_hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
|
||||
|
||||
notifySyspropsChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
case 257250372 /* unlinkToDeath */:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package com.github.google.bumble.remotehci;
|
||||
|
||||
import android.hardware.bluetooth.V1_0.Status;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public interface HciHal {
|
||||
public enum Status {
|
||||
SUCCESS("SUCCESS"),
|
||||
ALREADY_INITIALIZED("ALREADY_INITIALIZED"),
|
||||
UNABLE_TO_OPEN_INTERFACE("UNABLE_TO_OPEN_INTERFACE"),
|
||||
INITIALIZATION_ERROR("INITIALIZATION_ERROR"),
|
||||
TRANSPORT_ERROR("TRANSPORT_ERROR"),
|
||||
UNKNOWN("UNKNOWN");
|
||||
|
||||
public final String label;
|
||||
|
||||
private Status(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
static final String TAG = "HciHal";
|
||||
public static HciHal create(HciHalCallback hciCallbacks) {
|
||||
// First try HIDL
|
||||
HciHal hciHal = HciHidlHal.create(hciCallbacks);
|
||||
if (hciHal != null) {
|
||||
Log.d(TAG, "Found HIDL HAL");
|
||||
return hciHal;
|
||||
}
|
||||
|
||||
// Then try AIDL
|
||||
hciHal = HciAidlHal.create(hciCallbacks);
|
||||
if (hciHal != null) {
|
||||
Log.d(TAG, "Found AIDL HAL");
|
||||
return hciHal;
|
||||
}
|
||||
|
||||
Log.d(TAG, "No HAL found");
|
||||
return null;
|
||||
}
|
||||
|
||||
public Status initialize() throws RemoteException, InterruptedException;
|
||||
public void sendPacket(HciPacket.Type type, byte[] packet);
|
||||
}
|
||||
|
||||
class HciHidlHal extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.Stub implements HciHal {
|
||||
private static final String TAG = "HciHidlHal";
|
||||
private final android.hardware.bluetooth.V1_0.IBluetoothHci mHciService;
|
||||
private final HciHalCallback mHciCallbacks;
|
||||
private int mInitializationStatus = -1;
|
||||
|
||||
|
||||
public static HciHidlHal create(HciHalCallback hciCallbacks) {
|
||||
// Get the HAL service.
|
||||
android.hardware.bluetooth.V1_0.IBluetoothHci hciService;
|
||||
try {
|
||||
hciService = android.hardware.bluetooth.V1_0.IBluetoothHci.getService(true);
|
||||
} catch (NoSuchElementException e) {
|
||||
Log.d(TAG, "HIDL HAL V1.0 not found");
|
||||
return null;
|
||||
} catch (android.os.RemoteException e) {
|
||||
Log.w(TAG, "Exception from getService: " + e);
|
||||
return null;
|
||||
}
|
||||
Log.d(TAG, "Found HIDL HAL V1.0");
|
||||
return new HciHidlHal(hciService, hciCallbacks);
|
||||
}
|
||||
|
||||
private HciHidlHal(android.hardware.bluetooth.V1_0.IBluetoothHci hciService, HciHalCallback hciCallbacks) {
|
||||
mHciService = hciService;
|
||||
mHciCallbacks = hciCallbacks;
|
||||
}
|
||||
|
||||
public Status initialize() throws RemoteException, InterruptedException {
|
||||
// Trigger the initialization.
|
||||
mHciService.initialize(this);
|
||||
|
||||
// Wait for the initialization to complete.
|
||||
Log.d(TAG, "Waiting for initialization status...");
|
||||
synchronized (this) {
|
||||
while (mInitializationStatus == -1) {
|
||||
wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Map the status code.
|
||||
switch (mInitializationStatus) {
|
||||
case android.hardware.bluetooth.V1_0.Status.SUCCESS:
|
||||
return Status.SUCCESS;
|
||||
|
||||
case android.hardware.bluetooth.V1_0.Status.TRANSPORT_ERROR:
|
||||
return Status.TRANSPORT_ERROR;
|
||||
|
||||
case android.hardware.bluetooth.V1_0.Status.INITIALIZATION_ERROR:
|
||||
return Status.INITIALIZATION_ERROR;
|
||||
|
||||
default:
|
||||
return Status.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(HciPacket.Type type, byte[] packet) {
|
||||
ArrayList<Byte> data = HciPacket.byteArrayToList(packet);
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case COMMAND:
|
||||
mHciService.sendHciCommand(data);
|
||||
break;
|
||||
|
||||
case ACL_DATA:
|
||||
mHciService.sendAclData(data);
|
||||
break;
|
||||
|
||||
case SCO_DATA:
|
||||
mHciService.sendScoData(data);
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException error) {
|
||||
Log.w(TAG, "failed to forward packet: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void initializationComplete(int status) throws RemoteException {
|
||||
mInitializationStatus = status;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hciEventReceived(ArrayList<Byte> event) throws RemoteException {
|
||||
byte[] packet = HciPacket.listToByteArray(event);
|
||||
mHciCallbacks.onPacket(HciPacket.Type.EVENT, packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void aclDataReceived(ArrayList<Byte> data) throws RemoteException {
|
||||
byte[] packet = HciPacket.listToByteArray(data);
|
||||
mHciCallbacks.onPacket(HciPacket.Type.ACL_DATA, packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scoDataReceived(ArrayList<Byte> data) throws RemoteException {
|
||||
byte[] packet = HciPacket.listToByteArray(data);
|
||||
mHciCallbacks.onPacket(HciPacket.Type.SCO_DATA, packet);
|
||||
}
|
||||
}
|
||||
|
||||
class HciAidlHal extends android.hardware.bluetooth.IBluetoothHciCallbacks.Stub implements HciHal {
|
||||
private static final String TAG = "HciAidlHal";
|
||||
private final android.hardware.bluetooth.IBluetoothHci mHciService;
|
||||
private final HciHalCallback mHciCallbacks;
|
||||
private int mInitializationStatus = android.hardware.bluetooth.Status.SUCCESS;
|
||||
|
||||
public static HciAidlHal create(HciHalCallback hciCallbacks) {
|
||||
IBinder binder = ServiceManager.getService("android.hardware.bluetooth.IBluetoothHci/default");
|
||||
if (binder == null) {
|
||||
Log.d(TAG, "AIDL HAL not found");
|
||||
return null;
|
||||
}
|
||||
android.hardware.bluetooth.IBluetoothHci hciService = android.hardware.bluetooth.IBluetoothHci.Stub.asInterface(binder);
|
||||
return new HciAidlHal(hciService, hciCallbacks);
|
||||
}
|
||||
|
||||
private HciAidlHal(android.hardware.bluetooth.IBluetoothHci hciService, HciHalCallback hciCallbacks) {
|
||||
super();
|
||||
mHciService = hciService;
|
||||
mHciCallbacks = hciCallbacks;
|
||||
}
|
||||
|
||||
public Status initialize() throws RemoteException, InterruptedException {
|
||||
// Trigger the initialization.
|
||||
mHciService.initialize(this);
|
||||
|
||||
// Wait for the initialization to complete.
|
||||
Log.d(TAG, "Waiting for initialization status...");
|
||||
synchronized (this) {
|
||||
while (mInitializationStatus == -1) {
|
||||
wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Map the status code.
|
||||
switch (mInitializationStatus) {
|
||||
case android.hardware.bluetooth.Status.SUCCESS:
|
||||
return Status.SUCCESS;
|
||||
|
||||
case android.hardware.bluetooth.Status.ALREADY_INITIALIZED:
|
||||
return Status.ALREADY_INITIALIZED;
|
||||
|
||||
case android.hardware.bluetooth.Status.UNABLE_TO_OPEN_INTERFACE:
|
||||
return Status.UNABLE_TO_OPEN_INTERFACE;
|
||||
|
||||
case android.hardware.bluetooth.Status.HARDWARE_INITIALIZATION_ERROR:
|
||||
return Status.INITIALIZATION_ERROR;
|
||||
|
||||
default:
|
||||
return Status.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// HciHal methods.
|
||||
@Override
|
||||
public void sendPacket(HciPacket.Type type, byte[] packet) {
|
||||
try {
|
||||
switch (type) {
|
||||
case COMMAND:
|
||||
mHciService.sendHciCommand(packet);
|
||||
break;
|
||||
|
||||
case ACL_DATA:
|
||||
mHciService.sendAclData(packet);
|
||||
break;
|
||||
|
||||
case SCO_DATA:
|
||||
mHciService.sendScoData(packet);
|
||||
break;
|
||||
|
||||
case ISO_DATA:
|
||||
mHciService.sendIsoData(packet);
|
||||
break;
|
||||
}
|
||||
} catch (RemoteException error) {
|
||||
Log.w(TAG, "failed to forward packet: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
// IBluetoothHciCallbacks methods.
|
||||
@Override
|
||||
public synchronized void initializationComplete(int status) throws RemoteException {
|
||||
mInitializationStatus = status;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hciEventReceived(byte[] event) throws RemoteException {
|
||||
mHciCallbacks.onPacket(HciPacket.Type.EVENT, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void aclDataReceived(byte[] data) throws RemoteException {
|
||||
mHciCallbacks.onPacket(HciPacket.Type.ACL_DATA, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scoDataReceived(byte[] data) throws RemoteException {
|
||||
mHciCallbacks.onPacket(HciPacket.Type.SCO_DATA, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void isoDataReceived(byte[] data) throws RemoteException {
|
||||
mHciCallbacks.onPacket(HciPacket.Type.ISO_DATA, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.github.google.bumble.remotehci;
|
||||
|
||||
public interface HciHalCallback {
|
||||
public void onPacket(HciPacket.Type type, byte[] packet);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.github.google.bumble.remotehci;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class HciPacket {
|
||||
public enum Type {
|
||||
COMMAND((byte) 1),
|
||||
ACL_DATA((byte) 2),
|
||||
SCO_DATA((byte) 3),
|
||||
EVENT((byte) 4),
|
||||
ISO_DATA((byte)5);
|
||||
|
||||
final byte value;
|
||||
final int lengthSize;
|
||||
final int lengthOffset;
|
||||
|
||||
Type(byte value) throws IllegalArgumentException {
|
||||
switch (value) {
|
||||
case 1:
|
||||
case 3:
|
||||
lengthSize = 1;
|
||||
lengthOffset = 2;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 5:
|
||||
lengthSize = 2;
|
||||
lengthOffset = 2;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
lengthSize = 1;
|
||||
lengthOffset = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
static Type fromValue(byte value) {
|
||||
for (Type type : values()) {
|
||||
if (type.value == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static ArrayList<Byte> byteArrayToList(byte[] byteArray) {
|
||||
ArrayList<Byte> list = new ArrayList<>();
|
||||
list.ensureCapacity(byteArray.length);
|
||||
for (byte x : byteArray) {
|
||||
list.add(x);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public static byte[] listToByteArray(ArrayList<Byte> byteList) {
|
||||
byte[] byteArray = new byte[byteList.size()];
|
||||
for (int i = 0; i < byteList.size(); i++) {
|
||||
byteArray[i] = byteList.get(i);
|
||||
}
|
||||
return byteArray;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.github.google.bumble.remotehci;
|
||||
|
||||
import static java.lang.Integer.min;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
|
||||
class HciParser {
|
||||
Sink sink;
|
||||
State state;
|
||||
int bytesNeeded;
|
||||
ByteArrayOutputStream packet = new ByteArrayOutputStream();
|
||||
HciPacket.Type packetType;
|
||||
|
||||
HciParser(Sink sink) {
|
||||
this.sink = sink;
|
||||
reset();
|
||||
}
|
||||
|
||||
void feedData(byte[] data, int dataSize) {
|
||||
int dataOffset = 0;
|
||||
int dataLeft = dataSize;
|
||||
|
||||
while (dataLeft > 0 && bytesNeeded > 0) {
|
||||
int consumed = min(dataLeft, bytesNeeded);
|
||||
if (state != State.NEED_TYPE) {
|
||||
packet.write(data, dataOffset, consumed);
|
||||
}
|
||||
bytesNeeded -= consumed;
|
||||
|
||||
if (bytesNeeded == 0) {
|
||||
if (state == State.NEED_TYPE) {
|
||||
packetType = HciPacket.Type.fromValue(data[dataOffset]);
|
||||
if (packetType == null) {
|
||||
throw new InvalidFormatException();
|
||||
}
|
||||
bytesNeeded = packetType.lengthOffset + packetType.lengthSize;
|
||||
state = State.NEED_LENGTH;
|
||||
} else if (state == State.NEED_LENGTH) {
|
||||
ByteBuffer lengthBuffer =
|
||||
ByteBuffer.wrap(packet.toByteArray())
|
||||
.order(ByteOrder.LITTLE_ENDIAN);
|
||||
bytesNeeded = packetType.lengthSize == 1 ?
|
||||
lengthBuffer.get(packetType.lengthOffset) & 0xFF :
|
||||
lengthBuffer.getShort(packetType.lengthOffset) & 0xFFFF;
|
||||
state = State.NEED_BODY;
|
||||
}
|
||||
|
||||
// Emit a packet if one is complete.
|
||||
if (state == State.NEED_BODY && bytesNeeded == 0) {
|
||||
if (sink != null) {
|
||||
sink.onPacket(packetType, packet.toByteArray());
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
dataOffset += consumed;
|
||||
dataLeft -= consumed;
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
state = State.NEED_TYPE;
|
||||
bytesNeeded = 1;
|
||||
packet.reset();
|
||||
packetType = null;
|
||||
}
|
||||
|
||||
enum State {
|
||||
NEED_TYPE, NEED_LENGTH, NEED_BODY
|
||||
}
|
||||
|
||||
interface Sink {
|
||||
void onPacket(HciPacket.Type type, byte[] packet);
|
||||
}
|
||||
|
||||
static class InvalidFormatException extends RuntimeException {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.github.google.bumble.remotehci;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HciProxy {
|
||||
private static final String TAG = "HciProxy";
|
||||
private final HciServer mServer;
|
||||
private final Listener mListener;
|
||||
private int mCommandPacketsReceived;
|
||||
private int mAclPacketsReceived;
|
||||
private int mScoPacketsReceived;
|
||||
private int mEventPacketsSent;
|
||||
private int mAclPacketsSent;
|
||||
private int mScoPacketsSent;
|
||||
|
||||
HciProxy(int port, Listener listener) throws HalException {
|
||||
this.mListener = listener;
|
||||
|
||||
// Instantiate a HAL to communicate with the hardware.
|
||||
HciHal hciHal = HciHal.create(new HciHalCallback() {
|
||||
@Override
|
||||
public void onPacket(HciPacket.Type type, byte[] packet) {
|
||||
mServer.sendPacket(type, packet);
|
||||
|
||||
switch (type) {
|
||||
case EVENT:
|
||||
mEventPacketsSent += 1;
|
||||
break;
|
||||
|
||||
case ACL_DATA:
|
||||
mAclPacketsSent += 1;
|
||||
break;
|
||||
|
||||
case SCO_DATA:
|
||||
mScoPacketsSent += 1;
|
||||
break;
|
||||
}
|
||||
updateHciPacketCount();
|
||||
}
|
||||
});
|
||||
if (hciHal == null) {
|
||||
String message = "Could not instantiate a HAL instance";
|
||||
Log.w(TAG, message);
|
||||
throw new HalException(message);
|
||||
}
|
||||
|
||||
// Initialize the HAL.
|
||||
HciHal.Status status = null;
|
||||
try {
|
||||
status = hciHal.initialize();
|
||||
} catch (RemoteException | InterruptedException e) {
|
||||
throw new HalException("Exception while initializing");
|
||||
}
|
||||
if (status != HciHal.Status.SUCCESS) {
|
||||
String message = "HAL initialization failed: " + status.label;
|
||||
Log.w(TAG, message);
|
||||
throw new HalException(message);
|
||||
}
|
||||
|
||||
// Create a server to accept clients.
|
||||
mServer = new HciServer(port, new HciServer.Listener() {
|
||||
@Override
|
||||
public void onHostConnectionState(boolean connected) {
|
||||
mListener.onHostConnectionState(connected);
|
||||
if (connected) {
|
||||
mCommandPacketsReceived = 0;
|
||||
mAclPacketsReceived = 0;
|
||||
mScoPacketsReceived = 0;
|
||||
mEventPacketsSent = 0;
|
||||
mAclPacketsSent = 0;
|
||||
mScoPacketsSent = 0;
|
||||
updateHciPacketCount();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(String message) {
|
||||
listener.onMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacket(HciPacket.Type type, byte[] packet) {
|
||||
Log.d(TAG, String.format("onPacket: type=%s, size=%d", type, packet.length));
|
||||
hciHal.sendPacket(type, packet);
|
||||
|
||||
switch (type) {
|
||||
case COMMAND:
|
||||
mCommandPacketsReceived += 1;
|
||||
break;
|
||||
|
||||
case ACL_DATA:
|
||||
mAclPacketsReceived += 1;
|
||||
break;
|
||||
|
||||
case SCO_DATA:
|
||||
mScoPacketsReceived += 1;
|
||||
break;
|
||||
}
|
||||
updateHciPacketCount();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void run() throws IOException {
|
||||
mServer.run();
|
||||
}
|
||||
|
||||
private void updateHciPacketCount() {
|
||||
mListener.onHciPacketCountChange(
|
||||
mCommandPacketsReceived,
|
||||
mAclPacketsReceived,
|
||||
mScoPacketsReceived,
|
||||
mEventPacketsSent,
|
||||
mAclPacketsSent,
|
||||
mScoPacketsSent
|
||||
);
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onHostConnectionState(boolean connected);
|
||||
|
||||
void onHciPacketCountChange(
|
||||
int commandPacketsReceived,
|
||||
int aclPacketsReceived,
|
||||
int scoPacketsReceived,
|
||||
int eventPacketsSent,
|
||||
int aclPacketsSent,
|
||||
int scoPacketsSent
|
||||
);
|
||||
|
||||
void onMessage(String message);
|
||||
}
|
||||
|
||||
public static class HalException extends RuntimeException {
|
||||
public final String message;
|
||||
public HalException(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.github.google.bumble.remotehci;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
|
||||
public class HciServer {
|
||||
private static final String TAG = "HciServer";
|
||||
private static final int BUFFER_SIZE = 1024;
|
||||
private final int mPort;
|
||||
private final Listener mListener;
|
||||
private OutputStream mOutputStream;
|
||||
|
||||
public interface Listener extends HciParser.Sink {
|
||||
void onHostConnectionState(boolean connected);
|
||||
void onMessage(String message);
|
||||
}
|
||||
|
||||
HciServer(int port, Listener listener) {
|
||||
this.mPort = port;
|
||||
this.mListener = listener;
|
||||
}
|
||||
|
||||
public void run() throws IOException {
|
||||
for (;;) {
|
||||
try {
|
||||
loop();
|
||||
} catch (IOException error) {
|
||||
mListener.onMessage("Cannot listen on port " + mPort);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loop() throws IOException {
|
||||
mListener.onHostConnectionState(false);
|
||||
try (ServerSocket serverSocket = new ServerSocket(mPort)) {
|
||||
mListener.onMessage("Waiting for connection on port " + serverSocket.getLocalPort());
|
||||
try (Socket clientSocket = serverSocket.accept()) {
|
||||
mListener.onHostConnectionState(true);
|
||||
mListener.onMessage("Connected");
|
||||
HciParser parser = new HciParser(mListener);
|
||||
InputStream inputStream = clientSocket.getInputStream();
|
||||
synchronized (this) {
|
||||
mOutputStream = clientSocket.getOutputStream();
|
||||
}
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
try {
|
||||
for (; ; ) {
|
||||
int bytesRead = inputStream.read(buffer);
|
||||
if (bytesRead < 0) {
|
||||
Log.d(TAG, "end of stream");
|
||||
break;
|
||||
}
|
||||
parser.feedData(buffer, bytesRead);
|
||||
}
|
||||
} catch (IOException error) {
|
||||
Log.d(TAG, "exception in read loop: " + error);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
synchronized (this) {
|
||||
mOutputStream = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPacket(HciPacket.Type type, byte[] packet) {
|
||||
// Create a combined data buffer so we can write it out in a single call.
|
||||
byte[] data = new byte[packet.length + 1];
|
||||
data[0] = type.value;
|
||||
System.arraycopy(packet, 0, data, 1, packet.length);
|
||||
|
||||
synchronized (this) {
|
||||
if (mOutputStream != null) {
|
||||
try {
|
||||
mOutputStream.write(data);
|
||||
} catch (IOException error) {
|
||||
Log.w(TAG, "failed to write packet: " + error);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "no client, dropping packet");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package com.github.google.bumble.remotehci
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.github.google.bumble.remotehci.HciProxy.HalException
|
||||
import com.github.google.bumble.remotehci.ui.theme.RemoteHCITheme
|
||||
import java.io.IOException
|
||||
import java.util.logging.Logger
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
const val DEFAULT_TCP_PORT = 9993
|
||||
const val TCP_PORT_PREF_KEY = "tcp_port"
|
||||
|
||||
class AppViewModel : ViewModel(), HciProxy.Listener {
|
||||
private var preferences: SharedPreferences? = null
|
||||
var tcpPort by mutableStateOf(DEFAULT_TCP_PORT)
|
||||
var canStart by mutableStateOf(true)
|
||||
var message by mutableStateOf("")
|
||||
var hostConnected by mutableStateOf(false)
|
||||
var hciCommandPacketsReceived by mutableStateOf(0)
|
||||
var hciAclPacketsReceived by mutableStateOf(0)
|
||||
var hciScoPacketsReceived by mutableStateOf(0)
|
||||
var hciEventPacketsSent by mutableStateOf(0)
|
||||
var hciAclPacketsSent by mutableStateOf(0)
|
||||
var hciScoPacketsSent by mutableStateOf(0)
|
||||
|
||||
fun loadPreferences(preferences: SharedPreferences) {
|
||||
this.preferences = preferences
|
||||
val savedTcpPortString = preferences.getString(TCP_PORT_PREF_KEY, null)
|
||||
if (savedTcpPortString != null) {
|
||||
val savedTcpPortInt = savedTcpPortString.toIntOrNull()
|
||||
if (savedTcpPortInt != null) {
|
||||
tcpPort = savedTcpPortInt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTcpPort(tcpPort: Int) {
|
||||
this.tcpPort = tcpPort
|
||||
|
||||
// Save the port to the preferences
|
||||
with (preferences!!.edit()) {
|
||||
putString(TCP_PORT_PREF_KEY, tcpPort.toString())
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onHostConnectionState(connected: Boolean) {
|
||||
hostConnected = connected
|
||||
}
|
||||
|
||||
override fun onHciPacketCountChange(
|
||||
commandPacketsReceived: Int,
|
||||
aclPacketsReceived: Int,
|
||||
scoPacketsReceived: Int,
|
||||
eventPacketsSent: Int,
|
||||
aclPacketsSent: Int,
|
||||
scoPacketsSent: Int
|
||||
) {
|
||||
hciCommandPacketsReceived = commandPacketsReceived
|
||||
hciAclPacketsReceived = aclPacketsReceived
|
||||
hciScoPacketsReceived = scoPacketsReceived
|
||||
hciEventPacketsSent = eventPacketsSent
|
||||
hciAclPacketsSent = aclPacketsSent
|
||||
hciScoPacketsSent = scoPacketsSent
|
||||
|
||||
}
|
||||
|
||||
override fun onMessage(message: String) {
|
||||
this.message = message
|
||||
}
|
||||
}
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val log = Logger.getLogger(MainActivity::class.java.name)
|
||||
private val appViewModel = AppViewModel()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
appViewModel.loadPreferences(getPreferences(Context.MODE_PRIVATE))
|
||||
|
||||
val tcpPort = intent.getIntExtra("port", -1)
|
||||
if (tcpPort >= 0) {
|
||||
appViewModel.tcpPort = tcpPort
|
||||
}
|
||||
|
||||
setContent {
|
||||
MainView(appViewModel, ::startProxy)
|
||||
}
|
||||
|
||||
if (intent.getBooleanExtra("autostart", false)) {
|
||||
startProxy()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startProxy() {
|
||||
// Run the proxy in a thread.
|
||||
appViewModel.message = ""
|
||||
thread {
|
||||
log.info("HCI Proxy thread starting")
|
||||
appViewModel.canStart = false
|
||||
try {
|
||||
val hciProxy = HciProxy(appViewModel.tcpPort, appViewModel)
|
||||
hciProxy.run()
|
||||
} catch (error: IOException) {
|
||||
log.warning("Exception while running HCI Server: $error")
|
||||
} catch (error: HalException) {
|
||||
log.warning("HAL exception: ${error.message}")
|
||||
appViewModel.message = "Cannot bind to HAL (${error.message}). You may need to use the command 'setenforce 0' in a root adb shell."
|
||||
}
|
||||
log.info("HCI Proxy thread ended")
|
||||
appViewModel.canStart = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActionButton(text: String, onClick: () -> Unit, enabled: Boolean) {
|
||||
Button(onClick = onClick, enabled = enabled) {
|
||||
Text(text = text)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun MainView(appViewModel: AppViewModel, startProxy: () -> Unit) {
|
||||
RemoteHCITheme {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
Text(
|
||||
text = "Bumble Remote HCI",
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Divider()
|
||||
Text(
|
||||
text = appViewModel.message
|
||||
)
|
||||
Divider()
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
TextField(
|
||||
label = {
|
||||
Text(text = "TCP Port")
|
||||
},
|
||||
value = appViewModel.tcpPort.toString(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
|
||||
onValueChange = {
|
||||
if (it.isNotEmpty()) {
|
||||
val tcpPort = it.toIntOrNull()
|
||||
if (tcpPort != null) {
|
||||
appViewModel.updateTcpPort(tcpPort)
|
||||
}
|
||||
}
|
||||
},
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {keyboardController?.hide()}
|
||||
)
|
||||
)
|
||||
Divider()
|
||||
val connectState = if (appViewModel.hostConnected) "CONNECTED" else "DISCONNECTED"
|
||||
Text(
|
||||
text = "HOST: $connectState",
|
||||
modifier = Modifier.background(color = if (appViewModel.hostConnected) Color.Green else Color.Red),
|
||||
color = Color.Black
|
||||
)
|
||||
Divider()
|
||||
Text(
|
||||
text = "Command Packets Received: ${appViewModel.hciCommandPacketsReceived}"
|
||||
)
|
||||
Text(
|
||||
text = "ACL Packets Received: ${appViewModel.hciAclPacketsReceived}"
|
||||
)
|
||||
Text(
|
||||
text = "SCO Packets Received: ${appViewModel.hciScoPacketsReceived}"
|
||||
)
|
||||
Text(
|
||||
text = "Event Packets Sent: ${appViewModel.hciEventPacketsSent}"
|
||||
)
|
||||
Text(
|
||||
text = "ACL Packets Sent: ${appViewModel.hciAclPacketsSent}"
|
||||
)
|
||||
Text(
|
||||
text = "SCO Packets Sent: ${appViewModel.hciScoPacketsSent}"
|
||||
)
|
||||
Divider()
|
||||
ActionButton(
|
||||
text = "Start", onClick = startProxy, enabled = appViewModel.canStart
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.github.google.bumble.remotehci.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.github.google.bumble.remotehci.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RemoteHCITheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
window.statusBarColor = colorScheme.primary.toArgb()
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.github.google.bumble.remotehci.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user