Files
pluriwave/openspec/changes/archive/2026-07-11-bt-device-identity/proposal.md
T
FreeTLab 159334f997
Build & Deploy PluriWave / Análisis de código (push) Successful in 35s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 1m44s
docs(openspec): archive bt-device-identity and promote its spec
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.
2026-07-11 01:15:06 +02:00

72 lines
5.4 KiB
Markdown

# 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_CONNECT` in manifest + runtime request flow, mirroring `POST_NOTIFICATIONS`, triggered on opening the device-management UI.
- Placeholder detection in `deviceToMap()`: treat `02:00:00:00:00:00` as absent → fall back to `type+productName` composite id (id format unchanged).
- Cache per-device platform names in `EstadoEcualizador`; fix both `nombreVisible()` 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_CONNECT` runtime 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_handler` capability already used for `POST_NOTIFICATIONS`/`RECORD_AUDIO` (reuse existing pattern; no new package expected).
## Success Criteria
- [ ] With `BLUETOOTH_CONNECT` granted, distinct BT devices produce distinct MAC-based ids (no `02: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 analyze` clean; existing `servicio_ecualizador_test.dart` and EQ state tests pass.