Files
pluriwave/openspec/changes/bt-device-identity/explore.md
T
FreeTLab 747738d20a
Build & Deploy PluriWave / Análisis de código (push) Successful in 35s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m38s
docs(openspec): add SDD artifact trails for bt-device-identity and alarm-volume-ramp-restore
In-progress artifact sets from the current SDD cycles: exploration,
proposal, spec, design, tasks, and verify reports as produced so far.
Also drops a leftover working copy of eq-device-disconnect-revert
whose contents were already committed under changes/archive/.
2026-07-11 00:56:22 +02:00

31 lines
3.5 KiB
Markdown

# Exploration: bt-device-identity
## Root Cause — two independent, compounding bugs
### Bug 1 — Missing `BLUETOOTH_CONNECT` permission (Android 12+/API 31+)
`AndroidManifest.xml` declares zero Bluetooth permissions. On API 31+, without `BLUETOOTH_CONNECT`, `AudioDeviceInfo.getAddress()` for BT devices returns the fixed placeholder `"02:00:00:00:00:00"` — not null, not empty, no exception. In `MainActivity.kt` `deviceToMap()` (L742), the guard `takeIf { it.isNotBlank() }` lets the placeholder pass through since it is non-blank. Result: every BT A2DP device collapses onto the identical id `bt_a2dp:02:00:00:00:00:00`. No runtime BT permission request flow exists anywhere in the Kotlin code (only `RECORD_AUDIO`/`POST_NOTIFICATIONS` have flows) — completely silent failure. Origin: `multi-device-eq/design.md` L17 claimed "no extra permission needed".
### Bug 2 — Duplicate device-list entry on any never-before-seen `deviceId`
`estado_ecualizador.dart` `_onDispositivoCambiado()` (L210-227) creates a new `_presetsDispositivo` map entry whenever the incoming `dispositivo.id` isn't already a key. The "known devices" list in `pantalla_ajustes.dart` is keyed off this map; the rename overlay (`_nombresDispositivos`) is a SEPARATE map also keyed by `deviceId` — a rename under one id string does nothing for a different id string. This produces the literal "duplicate after re-pairing" symptom (likely via transient builtin_speaker/wired_headset reports during A2DP reconnect handshake) and gets structurally worse once Bug 1 is fixed until placeholder entries are cleaned.
## Device name (symptoms 2/4) — smaller fix than expected
`productName` already flows end-to-end from Kotlin into `DispositivoAudio.nombre`. But `pantalla_ajustes.dart` calls `eq.nombreVisible(deviceId, '')` at both call sites (L769, L846) — always passing an empty platform name — and `EstadoEcualizador` never caches per-device platform names. The fallback chain in `nombreVisible()` is correct; it's just starved of input.
## iOS is already the reference pattern
`AudioDevicesPlugin.swift` `stableKey()` (L153-165) uses `uid` as primary key with `portType+portName` fallback when uid is empty — exactly the pattern Android needs. No iOS changes required.
## Recommended fix (5 points)
1. Declare `BLUETOOTH_CONNECT` in manifest + runtime request flow (mirror existing POST_NOTIFICATIONS pattern)
2. Detect the `02:00:00:00:00:00` placeholder in `deviceToMap()` and fall back to type+productName composite id instead
3. Cache per-device platform names in `EstadoEcualizador` so `nombreVisible()` gets real input
4. MAC remains the canonical id where available (id format unchanged — preserves colon-delimiter key safety from multi-device-eq design L35)
5. Migration: detect and DROP placeholder-keyed entries (collision destroyed per-device info at capture time — unrecoverable), keep legitimate stable-MAC entries as-is, one-time "please rename your devices again" notice for affected users only
## Risks
- Migration is destructive by necessity for the placeholder-collision subset
- New runtime-request flow has no BT precedent in codebase (copy RECORD_AUDIO/POST_NOTIFICATIONS patterns)
- Any id-format change must preserve `eq_presets_matriz_v1` colon-delimiter key safety
## Files (read during exploration)
`dispositivo_audio.dart`, `servicio_dispositivo_audio.dart`, `MainActivity.kt`, `AndroidManifest.xml`, `estado_ecualizador.dart`, `servicio_ecualizador.dart`, `pantalla_ajustes.dart`, `AudioDevicesPlugin.swift`