Move the change folder to changes/archive/2026-07-11-bt-device-identity with the verified artifact set (verdict: pass with warnings, 0 critical, 102/102 targeted tests) and create the bt-device-identity capability spec under openspec/specs/. Phase 7 on-device QA remains the pending human gate before release.
5.4 KiB
Proposal: Canonical Bluetooth Device Identity
Intent
Renaming a Bluetooth audio device then re-pairing creates a duplicate device in the multi-device EQ list, and devices display raw ids instead of their own Bluetooth name. Two compounding bugs cause this: (1) on Android 12+ the app never requests BLUETOOTH_CONNECT, so getAddress() returns the placeholder 02:00:00:00:00:00 for every BT device and they all collapse onto one id bt_a2dp:02:00:00:00:00:00; (2) any unseen deviceId spawns a new entry, and the rename overlay is keyed separately so a rename never transfers. Fixing identity now unblocks reliable per-device EQ before more devices accumulate corrupted keys.
Scope
In Scope
- Declare
BLUETOOTH_CONNECTin manifest + runtime request flow, mirroringPOST_NOTIFICATIONS, triggered on opening the device-management UI. - Placeholder detection in
deviceToMap(): treat02:00:00:00:00:00as absent → fall back totype+productNamecomposite id (id format unchanged). - Cache per-device platform names in
EstadoEcualizador; fix bothnombreVisible()call sites so real BT names feed the fallback chain. - One-time destructive migration dropping placeholder-keyed entries from the three EQ persistence keys; keep stable-MAC entries.
- New user-visible strings (permission rationale, migration notice) in all 13 locales.
Out of Scope
- iOS changes —
stableKey()already implements uid-primary / type+name fallback. - Reworking the composite-key format or the 4-level EQ resolution hierarchy.
- Recovering data lost to the placeholder collision (unrecoverable by design).
- Auto-switch / autoswitch-UX behavior (separate change).
Capabilities
No standing
openspec/specs/*exists for audio devices yet (multi-device-eq shipped as an archived change). These are new capability specs.
New Capabilities
bt-device-identity: canonical MAC identity, placeholder detection + composite fallback,BLUETOOTH_CONNECTruntime flow, per-device name caching, and placeholder-key migration.
Modified Capabilities
- None.
Approach
Restore a stable BT MAC by acquiring BLUETOOTH_CONNECT at the point of intent (device-management UI), and make the id pipeline resilient when it is still absent. Kotlin deviceToMap() gains an explicit placeholder guard so 02:00:00:00:00:00 is treated as blank, producing a deterministic type:productName id instead of a colliding one. EstadoEcualizador caches each device's reported platform name so nombreVisible() prefers the device's own Bluetooth name over the raw id. A guarded one-time migration purges only placeholder-keyed entries from the three SharedPreferences maps and surfaces a rename-again notice to affected users. MAC stays canonical wherever available; the colon-delimited key format is preserved intact.
Affected Areas
| Area | Impact | Description |
|---|---|---|
android/app/src/main/AndroidManifest.xml |
Modified | Add BLUETOOTH_CONNECT (maxSdk-scoped as needed). |
android/app/.../MainActivity.kt deviceToMap() |
Modified | Placeholder guard → composite fallback; BT runtime-permission flow (mirror POST_NOTIFICATIONS). |
lib/estado/estado_ecualizador.dart |
Modified | Cache per-device platform names; feed nombreVisible(); stop spawning entries for placeholder ids. |
lib/pantallas/pantalla_ajustes.dart (L769, L846) |
Modified | Pass real platform name to nombreVisible() instead of ''. |
lib/servicios/servicio_ecualizador.dart (keys L42-44) |
Modified | Migration purging placeholder-keyed entries from 3 maps. |
lib/l10n/*.arb (13 files) |
Modified | Permission-rationale + migration-notice keys. |
Risks
| Risk | Likelihood | Mitigation |
|---|---|---|
| Migration deletes legitimate data | Low | Match ONLY exact bt_a2dp:02:00:00:00:00:00; keep all other keys. |
| BT permission denied → unstable ids | Med | Composite type:productName fallback keeps app functional; re-prompt on next UI open. |
| No BT-permission precedent in codebase | Med | Copy proven POST_NOTIFICATIONS/RECORD_AUDIO flow verbatim. |
| Composite-key colon-delimiter safety broken | Low | Id format unchanged; single leading type: segment preserved (ADR-3, multi-device-eq). |
Rollback Plan
Changes are additive/guarded and behind a versioned migration. To revert: restore prior MainActivity.kt, estado_ecualizador.dart, pantalla_ajustes.dart, remove the BLUETOOTH_CONNECT declaration and the migration + l10n keys. The migration is one-shot and gated by a run-once flag; already-deleted placeholder-collision entries were unrecoverable regardless, so revert restores behavior, not lost data. No schema/version bump of the persistence keys, so downgrade is clean.
Dependencies
permission_handlercapability already used forPOST_NOTIFICATIONS/RECORD_AUDIO(reuse existing pattern; no new package expected).
Success Criteria
- With
BLUETOOTH_CONNECTgranted, distinct BT devices produce distinct MAC-based ids (no02:00:00:00:00:00). - Renaming a device then re-pairing does NOT create a duplicate entry.
- The device list shows each device's own Bluetooth name by default; custom renames still override.
- Migration removes only placeholder-keyed entries; stable-MAC EQ/preset/name data is retained.
- Permission rationale + migration notice render correctly in all 13 locales.
flutter analyzeclean; existingservicio_ecualizador_test.dartand EQ state tests pass.