Fix Bluetooth pairing

This commit is contained in:
firestar5683
2026-09-01 14:32:13 -05:00
parent 75577ecfca
commit dff446fe5e
5 changed files with 55 additions and 18 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ fi
export QCOM_PRIORITY=12
if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="19.6.15"
export AGNOS_VERSION="19.6.16"
fi
if [ -z "$AGNOS_ACCEPTED_VERSIONS" ]; then
+6 -1
View File
@@ -38,6 +38,7 @@ class PairingAgent:
self._response: tuple[bool, str] | None = None
self._generation = 0
self._auto_accept_paths: set[str] = set()
self._auto_accept_incoming = False
@property
def prompt(self) -> dict[str, Any] | None:
@@ -94,9 +95,13 @@ class PairingAgent:
else:
self._auto_accept_paths.discard(device_path)
def set_auto_accept_incoming(self, enabled: bool) -> None:
with self._condition:
self._auto_accept_incoming = enabled
def auto_accept(self, kind: str, device_path: str) -> bool:
with self._condition:
return kind in {"confirmation", "authorization"} and device_path in self._auto_accept_paths
return kind in {"confirmation", "authorization"} and (self._auto_accept_incoming or device_path in self._auto_accept_paths)
class BlueZClient:
def __init__(self):
+16 -4
View File
@@ -58,12 +58,21 @@ class BluetoothController:
self._radio.start()
self._bluez = self._bluez_factory()
self._bluez.set_powered(True)
self._bluez.agent.set_auto_accept_incoming(self._offroad())
try:
self._bluez.set_discoverable(True)
except Exception as error:
cloudlog.warning(f"Bluetooth discoverability setup failed: {error}")
return self._bluez
def initialize(self) -> None:
if not self.params.get_bool("BluetoothEnabled"):
return
try:
self._client()
except Exception:
cloudlog.exception("Bluetooth initialization failed")
def _reset_client(self) -> None:
with self._lock:
if self._bluez is not None:
@@ -96,6 +105,7 @@ class BluetoothController:
try:
result.update(self._client().status())
result["available"] = True
self._bluez.agent.set_auto_accept_incoming(result["offroad"])
prompt = result.get("prompt")
if prompt is not None and self._pairing_address:
prompt["address"] = self._pairing_address
@@ -111,9 +121,9 @@ class BluetoothController:
if command in OFFROAD_COMMANDS and not self._offroad():
raise RuntimeError("Bluetooth settings can only be changed offroad")
def _pair_worker(self, address: str, device_path: str | None = None) -> None:
def _pair_worker(self, address: str) -> None:
try:
self._client().pair(address, device_path)
self._client().pair(address)
status = self._client().device_for_address(address)
if status.get("audio") and not self.params.get("BluetoothAudioAddress", encoding="utf-8"):
self.params.put("BluetoothAudioAddress", address)
@@ -189,10 +199,11 @@ class BluetoothController:
elif command == "pair":
if self._pairing_address:
raise RuntimeError("Another Bluetooth device is already pairing")
device = self._client().device_for_address(address)
self._client().device_for_address(address)
self._scan_deadline = 0.0
self._pairing_address = address
self._pairing_error = ""
threading.Thread(target=self._pair_worker, args=(address, device["path"]), daemon=True).start()
threading.Thread(target=self._pair_worker, args=(address,), daemon=True).start()
elif command == "connect":
self._client().connect(address)
elif command == "disconnect":
@@ -290,6 +301,7 @@ def main() -> None:
except FileNotFoundError:
pass
controller = BluetoothController()
threading.Thread(target=controller.initialize, daemon=True).start()
threading.Thread(target=controller.maintain_connections, daemon=True).start()
try:
with BluetoothServer(BLUETOOTH_SOCKET_PATH, controller) as server:
@@ -39,6 +39,9 @@ class FakeAgent:
def __init__(self):
self.responses = []
def set_auto_accept_incoming(self, _enabled):
pass
def respond(self, prompt_id, accepted, value):
self.responses.append((prompt_id, accepted, value))
return prompt_id == "prompt"
@@ -230,6 +233,9 @@ def test_desktop_fake_bluetooth_cannot_activate_on_device(monkeypatch, tmp_path)
def test_pairing_agent_accept_reject_and_timeout():
agent = PairingAgent()
agent.set_auto_accept_incoming(True)
assert agent.request("confirmation", "/incoming", "123456") == (True, "")
agent.set_auto_accept_incoming(False)
result = []
worker = threading.Thread(target=lambda: result.append(agent.request("confirmation", "/device", "123456", timeout=1.0)))
worker.start()
@@ -253,6 +259,18 @@ def test_disabled_status_does_not_start_radio_or_bluez():
assert radio.starts == 0 and created == []
def test_enabled_initialization_registers_bluetooth_agent_without_ui_poll():
params = FakeParams(IsOffroad=True, BluetoothEnabled=True)
radio = FakeRadio()
created = []
controller = BluetoothController(params, lambda: created.append(FakeBlueZ()) or created[-1], radio)
controller.initialize()
assert radio.starts == 1 and len(created) == 1
assert created[0].powered
def test_power_pair_audio_and_offroad_enforcement():
params = FakeParams(IsOffroad=True, BluetoothEnabled=False)
radio = FakeRadio()
@@ -401,7 +419,7 @@ def test_scan_stops_after_timeout():
assert not client.discovering and controller._scan_deadline == 0.0
def test_pair_stops_discovery_before_starting_pair():
def test_pair_keeps_discovery_until_pair_starts():
params = FakeParams(IsOffroad=True, BluetoothEnabled=True)
client = FakeBlueZ()
controller = BluetoothController(params, lambda: client, FakeRadio())
@@ -412,7 +430,9 @@ def test_pair_stops_discovery_before_starting_pair():
while not any(action[0] == "pair" for action in client.actions) and time.monotonic() < deadline:
time.sleep(0.01)
assert client.actions.index(("stop_scan", "")) < client.actions.index(("pair", client.device["address"]))
pair_index = client.actions.index(("pair", client.device["address"]))
assert client.actions[-1] == ("stop_scan", "")
assert pair_index < len(client.actions) - 1
def test_audio_queue_is_nonblocking_and_falls_back():
+10 -10
View File
@@ -56,30 +56,30 @@
},
{
"name": "boot",
"url": "https://files.firestar.link/x/n2jco20tm4oh/boot23.img.xz",
"url": "https://files.firestar.link/x/83cmdtzb2sav/boot25.img.xz",
"fallback_urls": [
"https://files-east.firestar.link/x/rjthk2ij2j8a/boot23.img.xz"
"https://files-east.firestar.link/x/o3r7dpay7yj5/boot25.img.xz"
],
"hash": "e0cae2edf9953ba5b94ffd868d74a9b8ab4fa253625e7120641b49d6e7052586",
"hash_raw": "e0cae2edf9953ba5b94ffd868d74a9b8ab4fa253625e7120641b49d6e7052586",
"hash": "846d9e41fe0eb3543a01cb9a55a6bb540e4097dd838ada890674b654bd97a49a",
"hash_raw": "846d9e41fe0eb3543a01cb9a55a6bb540e4097dd838ada890674b654bd97a49a",
"size": 48302080,
"sparse": false,
"full_check": true,
"has_ab": true,
"ondevice_hash": "e0cae2edf9953ba5b94ffd868d74a9b8ab4fa253625e7120641b49d6e7052586"
"ondevice_hash": "846d9e41fe0eb3543a01cb9a55a6bb540e4097dd838ada890674b654bd97a49a"
},
{
"name": "system",
"url": "https://files.firestar.link/x/5y3tzu7fic02/system23.img.xz",
"url": "https://files.firestar.link/x/ser56g04xioj/system25.img.xz",
"fallback_urls": [
"https://files-east.firestar.link/x/nuxu72xr6z2y/system23.img.xz"
"https://files-east.firestar.link/x/90fsutnuzy13/system25.img.xz"
],
"hash": "a896e86181114e32b028dc08a27c16571a9f58a120af50018d0a410323013ce7",
"hash_raw": "a896e86181114e32b028dc08a27c16571a9f58a120af50018d0a410323013ce7",
"hash": "f81ac06c0634ed0dadd5b4778f3b5052a2779d69669b7e70e21505c951ce6ccc",
"hash_raw": "f81ac06c0634ed0dadd5b4778f3b5052a2779d69669b7e70e21505c951ce6ccc",
"size": 4718592000,
"sparse": false,
"full_check": false,
"has_ab": true,
"ondevice_hash": "a896e86181114e32b028dc08a27c16571a9f58a120af50018d0a410323013ce7"
"ondevice_hash": "f81ac06c0634ed0dadd5b4778f3b5052a2779d69669b7e70e21505c951ce6ccc"
}
]