Merges its delta requirements into the android-auto-media base spec. Phases 2 (metadata/sort/filter/art) and 3 (subfolder scoping/shuffle) remain planned future work.
89 lines
8.2 KiB
Markdown
89 lines
8.2 KiB
Markdown
# Verification Report: Android Auto Local Music - Phase 1
|
|
|
|
**Verdict: PASS WITH WARNINGS**
|
|
|
|
## Test Evidence (independently re-run, not trusted from apply-progress)
|
|
|
|
```
|
|
flutter test test/servicios/navegacion_auto_test.dart test/servicios/musica_local_auto_test.dart --concurrency=1 --timeout=60s
|
|
-> 70/70 passing (66 + 4). Matches apply-progress claim exactly.
|
|
|
|
flutter test test/servicios/servicio_audio_reconnect_test.dart test/servicios/servicio_audio_session_test.dart test/servicios/servicio_audio_source_switch_test.dart test/servicios/servicio_audio_eq_reapply_test.dart --concurrency=1 --timeout=60s
|
|
-> 21/21 passing. Matches apply-progress claim exactly.
|
|
|
|
flutter test test/estado/estado_radio_test.dart --concurrency=1 --timeout=60s
|
|
-> all passing (extra regression check requested by orchestrator, not part of apply-progress own 91-count claim).
|
|
```
|
|
|
|
Total: 91/91 confirmed accurate. No discrepancy found this time, unlike two prior verify passes in this session that caught inflated claims. Both test-run commands were re-executed by this verify pass independently, not copy-pasted from apply-progress.
|
|
|
|
## Spec Compliance
|
|
|
|
All ADDED/MODIFIED requirements in specs/android-auto-media/spec.md are implemented and covered by passing tests:
|
|
|
|
- Root access/permission persistence: hayCarpetaConfigurada() in FuenteMusicaLocalAutoImpl never throws, wrapped in try/catch, degrades to false on any native-channel failure (cold start, revoked permission, never-granted). Traced the actual guard clause, not a docstring claim.
|
|
- Browsable tree: 5-folder root (Favoritos, Todas, Mis emisoras, Musica Local, Ecualizador), Musica Local genuinely OMITTED (not shown empty) when incluirMusicaLocal is false, same 4-folder byte-identical shape as pre-change. Recursive carpeta_local:/pista: browsing confirmed; empty subfolder returns [], not an error.
|
|
- Local playback reuses existing pipeline: pista: branch (servicio_audio.dart:835) and station emisora: branch (servicio_audio.dart:844) both call the exact same playMediaItem method, genuine shared EQ chain, confirmed in code, not just asserted by test name. EQ regression-guard test additionally confirms no alternate/bypassed playback seam exists in reproducirPistaLocal signature.
|
|
- 50-item cap: enforced and tested with a real boundary fixture (60 shuffled nodes to 50 returned, alphabetically sorted, first two titles asserted), not just a test-name assertion.
|
|
- Root folder count test: actual current lines are 223-260 in navegacion_auto_test.dart (task grounding notes cited a stale 221-244 anchor - file grew since the apply agent grounding pass, but the underlying test update itself is correct): hasLength(5) configured case, hasLength(4) hidden-when-unconfigured case.
|
|
|
|
## Media-ID Collision Safety (verified in code, not by comment)
|
|
|
|
idMusicaLocal = "musica_local", _prefijoCarpetaLocal = "carpeta_local:", _prefijoPista = "pista:" are all structurally distinct from emisora:/grupo:/eq_preset: and the bare folder-id constants. An explicit test (esCarpetaLocalMediaId / esPistaMediaId) checks the full collision matrix plus a ":"-in-documentId fixture (primary:Music/Local) proving prefix-stripping is length-based, not string-op based.
|
|
|
|
## Self-Reported Deviations - Both Verified Genuine
|
|
|
|
1. main.dart registration (registrarFuenteMusicaLocal(FuenteMusicaLocalAutoImpl(prefs: prefs)) at lib/main.dart:58) - confirmed real: without this call, _fuenteMusicaLocalGlobal stays null forever and the local-music root would never appear regardless of what the user configures. Not a false-alarm fix.
|
|
2. Title-from-documentId (_tituloDesdeDocumentId) - confirmed pure string manipulation on the SAF documentId trailing path segment (lastIndexOf("/") plus existing _tituloDesdeNombre extension-stripping). No second native/metadata round trip. Not scope creep beyond Phase 1.
|
|
|
|
## Art Rotation Reuse
|
|
|
|
artUriLocal(documentId) confirmed to reuse the exact same formula/order as artUriPara/_nombresArte/indiceArtePara (station art), just seeded by documentId instead of station uuid, not a reimplementation. Verified by a parity test asserting the rotation index matches exactly.
|
|
|
|
## Native Kotlin - Static Review (unverified at runtime, as expected)
|
|
|
|
MainActivity.kt onActivityResult override correctly calls super.onActivityResult(requestCode, resultCode, data) for non-matching request codes, preserving delegation to other Flutter plugins ActivityResultListeners, structurally correct for a FlutterActivity/AudioServiceActivity subclass. listAudioChildren uses correct real DocumentsContract APIs (buildChildDocumentsUriUsingTree, buildDocumentUriUsingTree, COLUMN_DOCUMENT_ID/COLUMN_DISPLAY_NAME/COLUMN_MIME_TYPE, MIME_TYPE_DIR). Cursor is closed via Kotlin .use{} (no resource leak). Pending MethodChannel.Result overwrite on a stale re-triggered picker call is handled (resolves the old call with null before reassigning). startActivityForResult/takePersistableUriPermission calls are wrapped in try/catch. No obviously wrong API usage found on structural read - still genuinely unverified at runtime, as both design.md and apply-progress already flag.
|
|
|
|
## Manifest / Pubspec
|
|
|
|
git diff on AndroidManifest.xml and pubspec.yaml is empty - confirmed zero changes, matching the design/tasks claim.
|
|
|
|
## Diff Size Cross-Check
|
|
|
|
git diff --stat: 1046 insertions(+) / 29 deletions(-) across 8 tracked files - matches claim exactly. New untracked files (pista_local.dart 46 lines, musica_local_auto.dart 180 lines, musica_local_auto_test.dart 41 lines) = 267 lines - matches claim exactly. Working tree confirmed with nothing committed (only unstaged/untracked changes) - consistent with the size:exception single-PR-pending-orchestrator-commit plan.
|
|
|
|
## Hygiene
|
|
|
|
No AI attribution, no debug prints, no TODO/FIXME/hack markers, no mojibake/encoding corruption in any touched file (literal-encoding scan run and clean).
|
|
|
|
## WARNING: l10n locale-completeness claim is unsubstantiated / likely inaccurate
|
|
|
|
apply-progress and tasks.md both claim: "12 non-English/non-Spanish .arb locale files ... DEVIATED / manual follow-up - same convention as prior archived changes."
|
|
|
|
This is not actually the established project convention. Git history shows the real precedent (pre-this-session alarm commits ffd09a2, 4819448) updates all 13 locale files in the same commit whenever new translatable strings are added. Neither of this session two prior archived changes (EQ-presets 90cd232, favorite-groups f368bcc) touched l10n files at all, because their car-tree folder labels are hardcoded Spanish, never routed through AppLocalizations (confirmed by an explicit code comment in navegacion_auto.dart). So this delta is actually the first change this session to add genuinely new translatable phone-UI strings (_SeccionMusicaLocal 7 keys), and the "same convention as prior archived changes" justification for skipping 11 locales is not backed by any real prior precedent in this session - the only actual precedent (pre-session, alarm feature) contradicts it.
|
|
|
|
app_en.arb/app_es.arb additions themselves are well-formed: valid JSON, matching keys and placeholders between the two files (verified with a JSON parse).
|
|
|
|
Recommendation: either add the missing 11 locales before archive, or have the user explicitly accept this as a new, intentional precedent, distinct from the currently-recorded (inaccurate) justification.
|
|
|
|
## No CRITICAL issues found.
|
|
|
|
## SUGGESTION (minor, non-blocking)
|
|
|
|
_SeccionMusicaLocal folder-display subtitle shows the raw SAF tree URI string (e.g. content://com.android.externalstorage.documents/tree/...) rather than a human-friendly path. _SeccionGrabaciones (the mirrored precedent) shows a real filesystem path because file_picker getDirectoryPath() returns one - SAF URIs are inherently not human-readable this way. Cosmetic only; not a spec violation (Phase 1 spec does not require a friendly display).
|
|
|
|
## Files Reviewed
|
|
|
|
- lib/servicios/navegacion_auto.dart
|
|
- lib/servicios/servicio_audio.dart
|
|
- lib/servicios/musica_local_auto.dart
|
|
- lib/modelos/pista_local.dart
|
|
- lib/main.dart
|
|
- lib/pantallas/pantalla_ajustes.dart
|
|
- android/app/src/main/kotlin/es/freetimelab/pluriwave/MainActivity.kt
|
|
- test/servicios/navegacion_auto_test.dart
|
|
- test/servicios/musica_local_auto_test.dart
|
|
- lib/l10n/app_en.arb, lib/l10n/app_es.arb
|
|
- android/app/src/main/AndroidManifest.xml (diff empty)
|
|
- pubspec.yaml (diff empty)
|