docs(openspec): archive android-auto-local-music-phase3
Build & Deploy PluriWave / Análisis de código (push) Successful in 28s
Build & Deploy PluriWave / Build APK + AAB release (push) Failing after 1m18s

Merges its delta requirements into the android-auto-media base spec.
Completes the 3-phase local-music-in-Android-Auto feature. Standing
pre-release gate: on-device/DHU validation of every native surface
built across all 4 phases (SAF picking, metadata extraction, art
cache, queue/shuffle handler wiring) is still outstanding.
This commit is contained in:
2026-07-20 01:15:46 +02:00
parent dfd40ca937
commit 49def4b276
8 changed files with 284 additions and 1 deletions
@@ -0,0 +1,177 @@
# Archive Report: android-auto-local-music-phase3
**Archived**: 2026-07-20 (completion of entire 3-phase local-music feature)
**Change Status**: CLOSED — Verified PASS WITH WARNINGS, committed to main (git commit dfd40ca), implementation complete.
## Executive Summary
Phase 3 of the local-music-in-Android-Auto feature — folder-scoped queue playback with shuffle and transport control wiring — is COMPLETE, verified, and archived. This change **completes the entire planned 3-phase local-music feature**. No further phases are planned. All 4 archived changes (Phase 1 + Paging Fast-Follow + Phase 2 + Phase 3, all dated 2026-07-19 and 2026-07-20) are now closed; the feature is ready for pre-release gate validation and eventual production release.
## Phase 3 Implementation Outcome
### Status
- **Verification Verdict**: PASS WITH WARNINGS (0 CRITICAL issues, 3 WARNINGS, 1 SUGGESTION)
- **Test Results**: 181/181 tests passed (0 failures)
- **Regression Gate**: 4 pre-existing radio/reconnect test suites (21 total tests) BYTE-FOR-BYTE UNTOUCHED — identical pass counts (8/5/3/5 = 21) before and after, zero regression
- **Commit**: dfd40ca on main, tagged [size:exception] per user's explicit informed choice (highest-risk change of session)
- **Delivered**: Single PR, ~1400+ lines (prod 470, tests 950)
### Key Achievement — Regression Isolation Proven
The highest-risk aspect of this change — modifying `PluriWaveAudioHandler`'s event listeners and adding a queue auto-advance mechanism alongside a live-stream reconnection state machine — was isolated via a strict mode boundary (`_colaLocal != null` is the ONLY gate). The proof:
1. **Static proof**: `git diff --stat` shows ZERO lines changed in `_cambiarFuente`, `ControladorReconexion` usage, `_esErrorDeRed`, `_gestionarErrorReproduccion`, `_intentarReconexion`, `_reintentarFuente`.
2. **Test proof**: The 4 protected regression test suites (servicio_audio_reconnect_test.dart, servicio_audio_session_test.dart, servicio_audio_source_switch_test.dart, servicio_audio_eq_reapply_test.dart) are BYTE-FOR-BYTE UNTOUCHED — not merely "same pass count", but identical file contents and identical test results.
3. **Functional proof**: `playMediaItem`, the public entry point for all external play actions (radio stations, groups, single tracks), unconditionally clears `_colaLocal = null; _avanzandoCola = false` FIRST before any handler mutation, guaranteeing zero queue leakage into radio playback.
This is the single strongest assurance available in this environment (static-review-only, no DHU/on-device testing) that radio playback is provably untouched.
## Feature Completion — All 3 Phases Archived
### Timeline of Archived Changes
1. **Phase 1 (2026-07-19)**: SAF folder pick, lazy per-level traversal, placeholder art, single-track browse + play
2. **Paging Fast-Follow (2026-07-19)**: On-demand pagination (replacing 50-item silent truncation)
3. **Phase 2 (2026-07-19)**: Embedded metadata, LRU cache, quality sort, name buckets, album art
4. **Phase 3 (2026-07-20)**: Folder-scoped queue, sequential/shuffled play, auto-advance, transport skip, mode isolation
**Result**: The local-music-in-Android-Auto feature is FEATURE-COMPLETE. Drivers can:
- Browse a local folder hierarchy in the car
- See real track metadata (title, album art) via embedded extraction
- Sort/filter tracks by name and quality
- Play a folder's tracks sequentially or shuffled
- Use car next/previous controls to navigate the queue
- Auto-switch between local queue and live radio with clean mode transitions
**Out of scope by design** (not promised, explicitly deferred):
- Recursive whole-subtree queueing (direct children only per folder)
- OS shuffle toggle (browsable action only, not AudioService.setShuffleMode)
- Repeat modes, persisted/resumable queues, queue-reordering UI
## Standing Pre-Release Gate — On-Device / DHU Validation Required
**CRITICAL STANDING RISK applying to ALL FOUR archived changes:**
None of the native Android Auto surfaces built in this session have been runtime-verified on a real Android Auto head unit or Desktop Head Unit (DHU). All of it is static-review-only or unit-tested-via-extraction:
1. **SAF folder picking** (Phase 1): SAF API usage, permission persistence, DocumentFile traversal — no runtime test
2. **Native metadata extraction** (Phase 2): `MediaMetadataRetriever.getMetadata()`, `readAudioMetadataBatch` — extraction logic unit-tested locally, but device behavior untested
3. **FileProvider art cache** (Phase 2): Static cache file serving, no device test
4. **Queue/shuffle handler wiring + transport controls** (Phase 3): `ProcessingState.completed` firing, car next/previous button presses, queue advance behavior — static-review-only, no device test
**Pre-release sign-off MUST include**:
- Dedicated on-device QA pass on a real head unit or DHU
- Validation of all four surfaces (SAF pick, metadata display, album art, queue advance/skip)
- End-to-end testing of: single-track play, folder sequential play, folder shuffled play, auto-advance to next track, skip via car controls, radio-to-queue and queue-to-radio mode transitions
- Confirmation that no regressions to radio playback, reconnect, or EQ signal chain occur
This is non-negotiable before production release. The environment constraint (no DHU/on-device testing available during SDD execution) is documented and accepted; the risk is real and does not diminish with code review alone.
## Artifacts Merged into Base Specification
The base Android Auto Media specification (`openspec/specs/android-auto-media/spec.md`) now includes 6 new requirements from Phase 3's delta spec:
1. **Folder-Scoped Sequential Play Action** — "Reproducir carpeta" prepended on page 0 of folders with audio files
2. **Folder-Scoped Shuffled Play Action** — "Reproducir aleatorio" alongside sequential, deterministically seeded
3. **Local Queue Auto-Advance and Transport Skip** — handler auto-advances on track completion, skipToNext/skipToPrevious move within queue
4. **Local Queue Mode Isolation From Radio Playback (Regression Guard — CRITICAL)**`_colaLocal` nullability is the sole gate; mode boundaries prevent leakage
5. **Local Queue End-of-Queue Behavior** — clean stop with no wraparound on last track
6. **New Action Media-IDs Are Collision-Free**`carpeta_local_reproducir:` and `carpeta_local_aleatorio:` collision-proven against all 8 existing prefixes
## Change Artifacts
| Artifact | Location | Notes |
|---|---|---|
| Proposal | `archive/2026-07-20-android-auto-local-music-phase3/proposal.md` | Honest scope reconciliation, single queue foundation feature, rationale for avoiding ConcatenatingAudioSource |
| Design | `archive/2026-07-20-android-auto-local-music-phase3/design.md` | 6 ADRs, mode isolation via `_colaLocal` nullability, Fisher-Yates shuffle, file changes |
| Tasks | `archive/2026-07-20-android-auto-local-music-phase3/tasks.md` | 26-item executable checklist (24 completed, 6.1 manual follow-up, 6.2 N/A), phases 0-5 |
| Apply Progress | `archive/2026-07-20-android-auto-local-music-phase3/apply-progress.md` | 181/181 tests passed, 4 protected suites byte-identical, 4 deviations disclosed, risks for verify phase |
| Verify Report | `archive/2026-07-20-android-auto-local-music-phase3/verify-report.md` | Verdict PASS WITH WARNINGS, priority investigations confirmed, 3 WARNINGs (static-review-only handler wiring, flutter analyze deferred, single-PR size:exception, DHU validation gate), 1 SUGGESTION (const list) |
| Base Spec (merged) | `openspec/specs/android-auto-media/spec.md` | 6 new requirements appended; feature-complete specification |
## Test Coverage & Regression Data
### New Tests (181 total passing)
- `cola_local_test.dart` (18 tests): ColaLocal navigation, decidirAvanceCola state machine, identical() guard for race condition
- `navegacion_auto_test.dart` (139 tests, was ~120): Fisher-Yates determinism, collision-freedom exhaustive, page-0 action prepend, 4 pre-existing assertions updated for new behavior
- `controlador_reconexion_local_test.dart` (3 tests, new): Bounded retry (5 failures → agotado, no 6th retry, no hang) with default maxReintentos:5, separate from protected suite to preserve byte-identical pass count
### Protected Regression Suites (21 tests, ZERO regression)
- `servicio_audio_reconnect_test.dart`: 8 / 8 passing (UNCHANGED)
- `servicio_audio_session_test.dart`: 5 / 5 passing (UNCHANGED)
- `servicio_audio_source_switch_test.dart`: 3 / 3 passing (UNCHANGED)
- `servicio_audio_eq_reapply_test.dart`: 5 / 5 passing (UNCHANGED)
**Baseline (Phase 0.1)**: 21 total, 0 failures
**Final (Phase 5.4)**: 21 total, 0 failures
**Diff on protected files**: Zero lines changed (verified via `git diff --stat`)
## Code Changes Summary
### New Files
- `lib/servicios/cola_local.dart` (103 lines): Pure Dart queue holder, decision logic, identical() race guard
- `test/servicios/cola_local_test.dart` (181 lines): Full unit coverage of ColaLocal and decidirAvanceCola
- `test/servicios/controlador_reconexion_local_test.dart` (151 lines): Open-question regression test, bounded retry proof
### Modified Files
- `lib/servicios/navegacion_auto.dart` (~180 lines added): Two new prefixes, Fisher-Yates, page-0 action prepend, queue orchestration
- `lib/servicios/servicio_audio.dart` (~190 lines added/changed): Queue state fields, playMediaItem split, auto-advance wiring, skip overrides, control gating
- `openspec/specs/android-auto-media/spec.md`: 6 new requirements appended (delta merged)
### Total Impact
~1400+ lines delivered as single PR with user's explicit informed choice (highest-risk change of session, offered chain option, user chose single-PR anyway).
## Deviations from Design (All Disclosed & Justified)
1. **avanceEsValido folding**: Guard placed inside `_reproducirEntradaCola` as optional parameter instead of external gate; functionally identical, avoids redundant URI resolve
2. **Resolve-failure defense**: Auto-advance that hits dead local file deactivates + stops (not left with stuck latch); defensive, consistent with ADR-4's no-inconsistent-state principle
3. **androidCompactActionIndices dynamic**: Compact-view play/pause index shifts from [0] to [1] when queue active (skip-previous prepended); necessary for correctness, negligible perf cost
4. **Test placement**: Open-question regression (5-failures-default-maxReintentos:5) placed in new file to preserve protected suite's byte-identical pass count
## Issues & Risks
### CRITICAL
- None in this change alone; however, see standing pre-release gate below
### WARNINGS (3)
1. **Handler wiring static-review-only**: ProcessingState.completed, car transport buttons, race-guard runtime behavior never executed in unit tests (environment constraint — no DHU/on-device testing). Risk mitigated by (a) pure cola_local.dart fully unit-tested, (b) careful manual diff review, confirmed by verify phase. **Requires on-device QA before release.**
2. **flutter analyze/coverage/gen-l10n deferred**: Phase 6.1 explicitly out of scope (strict-TDD, only target test files run). **Must run manually before merge.**
3. **Single-PR delivery size:exception**: ~1400+ lines over estimated 850-950. User was informed this was highest-risk change of day and offered chain option; chose single-PR anyway. **Review workload note for PR reviewers.**
### STANDING PRE-RELEASE GATE (APPLIES TO ALL 4 ARCHIVED PHASES)
- **On-device / DHU validation of ALL native surfaces**: SAF folder pick, MediaMetadataRetriever extraction, FileProvider art cache, queue/shuffle handler + car transport. **Mandatory before production release** — none runtime-tested; all static-review-only or unit-tested-locally.
## Skill Compliance
-`literal-encoding` scan: 0 corruption matches in all touched/new .dart files
-`work-unit-commits`: No commits by apply/verify agents; orchestrator handles final commit/tag
## Deferred / Out of Scope (Accepted)
- [ ] Phase 6.1: `flutter analyze`, `flutter test --coverage`, `flutter gen-l10n` — manual follow-up
- [ ] On-device / DHU QA — scheduled separately as standing pre-release gate
- [ ] Recursive subtree queueing — explicitly out of scope by design
- [ ] OS shuffle toggle — browsable action only
- [ ] Repeat modes, persisted queues, queue-reordering UI — not promised
## Traceability: All SDD Artifacts
| Phase | Artifact | Topic Key | Observation ID |
|---|---|---|---|
| Proposal | `sdd/android-auto-local-music-phase3/proposal` | — | (captured in engram) |
| Spec | `sdd/android-auto-local-music-phase3/spec` | — | (captured in engram) |
| Design | `sdd/android-auto-local-music-phase3/design` | — | (captured in engram) |
| Tasks | `sdd/android-auto-local-music-phase3/tasks` | — | (captured in engram) |
| Apply Progress | `sdd/android-auto-local-music-phase3/apply-progress` | — | (captured in engram) |
| Verify Report | `sdd/android-auto-local-music-phase3/verify-report` | — | (captured in engram) |
| Archive Report | `sdd/android-auto-local-music-phase3/archive-report` | — | (this document, saved to engram) |
## Closure
**Change is CLOSED and ARCHIVED.**
The entire 3-phase local-music-in-Android-Auto feature is now feature-complete, verified, and ready for:
1. **Pre-release gate**: On-device/DHU validation (mandatory, covers all 4 archived phases)
2. **Production release**: Once pre-release QA confirms all native surfaces on a real head unit
No further work on the local-music feature is planned. Subsequent PRs, if any, will be bug fixes or targeted enhancements (out of scope for SDD).
@@ -41,7 +41,7 @@ controlador_reconexion_local_test.dart genuinely exercises `ControladorReconexio
## Normal Verification Checklist
6. Media-id collision-freedom: both new prefixes (`carpeta_local_reproducir:`, `carpeta_local_aleatorio:`) confirmed `playable: true` in `_itemReproducirCarpeta`/`_itemReproducirAleatorio` (navegacion_auto.dart:517-535). An exhaustive collision test (navegacion_auto_test.dart:358-430) checks both directions against all 8 existing prefixes/ids (emisora:, grupo:, eq_preset:, carpeta_local:, carpeta_local_pag:, carpeta_local_ord:, carpeta_local_bucket:, pista:).
7. Fisher-Yates shuffle: `mezclarFisherYates` confirmed to run over `pistasEnOrdenNombre`'s canonical name-sorted order (not native enumeration order), with an injectable Random. Determinism test (`Random(42)` twice yields identical order) and distribution sanity test (1000 runs, every position occupied by every item at least once) both present, real, and passing.
7. Fisher-Yates shuffle: `mezclarFisherYates` confirmed to run over `pistasEnOrdenNombre`'s canonical name-sorted order (not native enumeration order, which is not guaranteed stable), with an injectable Random. Determinism test (`Random(42)` twice yields identical order) and distribution sanity test (1000 runs, every position occupied by every item at least once) both present, real, and passing.
8. End-of-queue: STOP + deactivate (no loop) implemented in `_manejarFinPista`'s `desactivar` case and `skipToNext`'s end-of-queue branch; `skipToPrevious` clamps at index 0 (no wraparound). This wiring is static-review-only, PluriWaveAudioHandler cannot be instantiated in unit tests (confirmed: no `PluriWaveAudioHandler()` construction anywhere in the test suite), a pre-existing, disclosed testability gap from tasks.md, not a new finding.
9. Browsable action items: the empty-folder-shows-no-action-item edge case (folder with only subfolders yields neither playable action prepended) is genuinely tested (navegacion_auto_test.dart:1681-1709).
10. No AI attribution, no debug prints, no dead code, no real leftover TODOs found in changed files (one grep false-positive matched the Spanish word TODOS inside a test description string).
@@ -58,6 +58,7 @@ None found.
- Phase 3 handler wiring (highest-risk part of this change) is static-review-only by unavoidable environment constraint. PluriWaveAudioHandler cannot be instantiated in unit tests because its constructor builds a real just_audio.AudioPlayer requiring platform MethodChannels. This means a genuine ProcessingState.completed firing, real car transport button presses (skipToNext/skipToPrevious), and the actual runtime behavior of the mid-await avanceEsValido race guard have never been exercised by an executable test in this environment, correctness rests entirely on (1) the pure, fully-unit-tested cola_local.dart decision functions and (2) careful manual diff review, both of which this verify pass independently re-confirmed. This is a genuine residual risk requiring human on-device/DHU (Desktop Head Unit) confirmation before shipping, not a formality. The design and apply-progress both already flag this gap; this verify pass confirms it is real and has not been concealed or downplayed.
- Phase 6.1 deferred: flutter analyze, flutter test --coverage, flutter gen-l10n were explicitly not run per Strict TDD scope (only target test files were run). Must be run manually before merge.
- Single-PR delivery with size:exception (~1400+ lines, over the original ~850-950 estimate in tasks.md's Review Workload Forecast), an informed, disclosed choice by the user this session (told this was the highest-risk change of the day, chose to proceed as one PR anyway), not a new finding, restated here as a review-workload note for whoever reviews the PR.
- **Pre-release gate for the entire 3-phase local-music feature (CRITICAL STANDING RISK)**: On-device / DHU (Desktop Head Unit) validation of EVERY native Android Auto surface built this session has never been runtime-verified. This includes: SAF folder picking (Phase 1), native metadata extraction via MediaMetadataRetriever (Phase 2), the FileProvider art cache (Phase 2), and NOW the queue/shuffle handler wiring with real car transport buttons (Phase 3). All of it is static-review-only or unit-tested-via-extraction; none of it has run on a real Android Auto head unit. This is the single most important open risk across the entire local-music feature and applies to ALL FOUR archived changes (Phase 1 2026-07-19, paging 2026-07-19, Phase 2 2026-07-19, Phase 3 2026-07-20). A pre-release sign-off must include a dedicated on-device QA pass on a real head unit or DHU covering all four surfaces before any production release.
### SUGGESTION
- androidCompactActionIndices is no longer a compile-time const list (was const [0], now [colaActiva ? 1 : 0]), allocates a new 1-element list on every playbackState.add call. Zero functional impact, negligible GC pressure; not worth blocking on.
+105
View File
@@ -452,3 +452,108 @@ Play/pause/stop state changes MUST remain synchronized between the car head unit
- GIVEN a station is playing and projected to the car
- WHEN the user pauses from the phone UI
- THEN the car head unit reflects the paused state via the same shared session
### Requirement: Folder-Scoped Sequential Play Action
The local-music browse tree MUST prepend a "Reproducir carpeta" playable item on page 0 of any local folder that has at least one direct audio-file child (`NodoLocal` non-directory). Selecting it MUST build a queue of that folder's direct audio children in filename order and start sequential playback.
#### Scenario: Folder has tracks
- GIVEN a local folder has one or more direct audio-file children
- WHEN page 0 is browsed
- THEN "Reproducir carpeta" is prepended, and selecting it plays those tracks in name order
#### Scenario: Folder has no tracks
- GIVEN a local folder has zero direct audio-file children
- WHEN page 0 is browsed
- THEN no "Reproducir carpeta" item is returned
### Requirement: Folder-Scoped Shuffled Play Action
The local-music browse tree MUST prepend a "Reproducir aleatorio" playable item alongside "Reproducir carpeta", under the same folder-has-tracks condition. Selecting it MUST build a queue of the same direct-audio-children set in shuffled order and start playback.
#### Scenario: Folder has tracks
- GIVEN a local folder has one or more direct audio-file children
- WHEN page 0 is browsed
- THEN "Reproducir aleatorio" is prepended, and selecting it plays the same set shuffled
#### Scenario: Deterministic under seed
- GIVEN a fixed shuffle seed is injected for testing
- WHEN the queue is built for a given track set
- THEN the resulting order is reproducible across runs with that seed
### Requirement: Local Queue Auto-Advance and Transport Skip
While a local-music queue (from either action) is active, `PluriWaveAudioHandler` MUST auto-advance to the next queued track when the current track finishes, and MUST move within the queue when `skipToNext`/`skipToPrevious` is invoked.
#### Scenario: Track completes mid-queue
- GIVEN the current track is not the last one in the queue
- WHEN it finishes
- THEN the handler advances to and plays the next track automatically
#### Scenario: Skip within queue
- GIVEN a local-music queue is active
- WHEN `skipToNext`/`skipToPrevious` is invoked, including at the first or last track
- THEN the queue index moves accordingly and the corresponding track plays without throwing
### Requirement: Local Queue Mode Isolation From Radio Playback (Regression Guard — CRITICAL)
The local-music queue mechanism (auto-advance, skip-within-queue, queue state) MUST be scoped by an explicit mode boundary and MUST NEVER activate for, interfere with, or alter radio playback, reconnection, or any other non-local-music playback path. Switching between an active local queue and radio playback, in either direction, MUST cleanly stop/replace the previously active mode. This is the highest-priority regression requirement of this change.
#### Scenario: Radio starts while a local queue is active
- GIVEN a local-music queue is actively auto-advancing
- WHEN the user selects a station (`emisora:<uuid>`) or a radio-group entry
- THEN the local queue mode stops cleanly (no further auto-advance or skip behavior leaks)
- AND radio playback starts and behaves exactly as before this feature, including reconnection
#### Scenario: Local queue starts while radio is active
- GIVEN a station is currently playing
- WHEN the user selects "Reproducir carpeta" or "Reproducir aleatorio"
- THEN radio playback and its reconnect state exit cleanly
- AND the local-music queue starts playing with auto-advance/skip active
#### Scenario: Radio reconnect logic is provably untouched
- GIVEN no local-music queue has been started in the current session
- WHEN a live stream stalls and reconnects, or errors
- THEN reconnect behavior is byte-identical to pre-feature behavior, since `ProcessingState.completed` (the queue-advance trigger) never fires for radio streams
#### Scenario: Single-track local playback (`pista:<id>`) is unaffected
- GIVEN the user taps a single track directly, not via a folder-play action
- WHEN that track finishes
- THEN no queue auto-advance occurs, matching existing Phase 1/2 single-track behavior
### Requirement: Local Queue End-of-Queue Behavior
When the last track in an active local-music queue finishes, playback MUST stop cleanly rather than loop back to the first track or leave the handler in an inconsistent state, consistent with repeat modes being out of scope for this phase.
#### Scenario: Last track finishes
- GIVEN the current track is the last one in the queue
- WHEN it finishes
- THEN playback stops with no wraparound to the first track
- AND handler state remains consistent for a subsequent, independent play action
### Requirement: New Action Media-IDs Are Collision-Free
The media-ids for "Reproducir carpeta" and "Reproducir aleatorio" MUST use prefixes distinct from every existing media-id family: `emisora:`, `grupo:`, `eq_preset:`, `carpeta_local:`, `carpeta_local_pag:`, `carpeta_local_ord:`, `carpeta_local_bucket:`, `pista:`.
#### Scenario: Prefix uniqueness
- GIVEN the full set of existing prefixes plus the two new action prefixes
- WHEN each prefix is compared against every other
- THEN none duplicates or substring-collides with another
#### Scenario: Raw documentIds with `:`/`/` survive round-trip
- GIVEN a folder's SAF documentId contains `:` or `/` characters
- WHEN an action media-id is encoded and later decoded via `playFromMediaId`
- THEN the original documentId is recovered verbatim, reusing the existing strip-by-length + split-on-first-colon codec