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

3.5 KiB

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.

  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