Android Auto no longer copies the launcher icon as placeholder art; it rotates through the same 4 on-brand station_art assets the phone UI already uses, keyed by the same per-station hash for visual parity. Malformed or unusable favicon URLs (including a Dart Uri quirk where 'http://' reports hasAuthority=true with an empty host) now fail the validity gate instead of being handed to the OS media browser as-is. Browsable items also show codec/bitrate as a subtitle when known.
69 lines
4.6 KiB
Markdown
69 lines
4.6 KiB
Markdown
# Proposal: Android Auto Media Art & Quality Polish
|
|
|
|
## Intent
|
|
|
|
Fast-follow to `android-auto-media`, closing 3 gaps that parent's verify-report flagged (PASS WITH WARNINGS) plus one requested capability. In the car head unit today: (1) stations whose favicon URL is present but dead render broken/blank art — Android Auto resolves `artUri` OUTSIDE the Flutter engine, so the phone's `CachedNetworkImage.errorWidget` fallback never runs (only empty-favicon Case A is handled); (2) the fallback art is a byte-for-byte copy of the launcher icon, reading as a bug; (3) browse items expose no audio-quality hint even though `Emisora.codec`/`bitrate` already exist. This picks up the "fallback artwork for stations without favicons" work deferred in engram #2290.
|
|
|
|
## Scope
|
|
|
|
### In Scope
|
|
- **A** — Degrade Android Auto art gracefully when favicon is present but unreachable (Case B), not only empty (Case A).
|
|
- **B** — Replace launcher-icon placeholder with the 4 on-brand `station_art_*` assets, reusing `_fallbackArtFor`'s deterministic per-station hash-by-uuid rotation for phone/car parity. Requires native Android drawable copies of the 4 PNGs (same `android.resource://` technique as existing `default_station_art.png`).
|
|
- **C** — Surface `codec` + `bitrate` (e.g. "128 kbps · MP3") via `MediaItem.displaySubtitle` on browse items; omit gracefully when unknown (never "null kbps").
|
|
- Strict-TDD Dart tests for rotation selection, subtitle formatting, and Case-B fallback path.
|
|
|
|
### Out of Scope
|
|
- Commissioning brand-new custom artwork (no image-gen tooling here, #2290; reuse existing approved assets — external follow-up may add custom art later).
|
|
- iOS CarPlay; voice search; phone-UI art changes (already correct).
|
|
- `flutter build`/`analyze`/`gen-l10n` (hang in this environment).
|
|
|
|
## Capabilities
|
|
|
|
### New Capabilities
|
|
- None.
|
|
|
|
### Modified Capabilities
|
|
- `android-auto-media`: browse-item requirements extended — artwork MUST degrade gracefully for both empty AND unreachable favicons using on-brand rotating placeholders, and playable items SHOULD carry an audio-quality subtitle.
|
|
|
|
## Approach
|
|
|
|
Reuse, don't rebuild — mirror the phone UI. Port `_fallbackArtFor`'s uuid-hash rotation into `navegacion_auto.dart` so `_artUriPara()` picks one of 4 native drawables. Add `displaySubtitle` in `itemEmisora()` from existing `codec`/`bitrate`. **Deferred to design.md — the real open question:** HOW to detect Case B (dead URL) with no Flutter image widget in the OS browser loop. Design must weigh proactive reachability check + cache (network cost, staleness, invalidation) vs. piggybacking the phone's `CachedNetworkImage` error signal (misses car-first-open stations) vs. documenting the gap as a known limitation this iteration.
|
|
|
|
## Affected Areas
|
|
|
|
| Area | Impact | Description |
|
|
|------|--------|-------------|
|
|
| `lib/servicios/navegacion_auto.dart` | Modified | `_artUriPara()` rotation + Case-B fallback; `itemEmisora()` subtitle |
|
|
| `android/app/src/main/res/drawable/station_art_*.png` | New | 4 native copies of brand assets |
|
|
| `android/app/src/main/res/drawable/default_station_art.png` | Removed/Kept | Retire launcher-icon copy (design confirms) |
|
|
| `test/` (Dart) | New | Rotation, subtitle, fallback tests |
|
|
|
|
## Risks
|
|
|
|
| Risk | Likelihood | Mitigation |
|
|
|------|------------|------------|
|
|
| Case-B detection adds unbounded network cost | Med | Design phase decides; may accept as documented gap |
|
|
| Native drawable / asset drift over time | Low | Comment linking copies to `assets/images/` source |
|
|
| Malformed codec/bitrate → ugly subtitle | Low | Format guard; omit when either is unknown |
|
|
|
|
## Split Judgment
|
|
|
|
Keep as ONE change: A+B+C all touch `navegacion_auto.dart` browse-item construction and share tests. If design finds Case-B reachability checking too costly/complex, split A into its own follow-up and ship B+C (pure, low-risk parity + subtitle) first.
|
|
|
|
## Rollback Plan
|
|
|
|
Additive. Revert PR: restore `_artUriPara()`, drop `displaySubtitle`, delete the 4 native drawables (and restore `default_station_art.png` if retired), remove new tests. Phone UI and audio pipeline untouched — zero residual state.
|
|
|
|
## Dependencies
|
|
|
|
- `audio_service ^0.18.18` (present; `MediaItem.displaySubtitle`).
|
|
- Existing `assets/images/station_art_*.png` (source for native copies).
|
|
|
|
## Success Criteria
|
|
|
|
- [ ] Unreachable favicon shows on-brand art in car, not broken/blank (or gap documented).
|
|
- [ ] Fallback uses rotating `station_art_*`, matching phone per-station selection.
|
|
- [ ] Launcher-icon-as-art no longer appears.
|
|
- [ ] Browse items show quality subtitle; unknown quality omits cleanly.
|
|
- [ ] Dart tests pass under `flutter test`.
|