# Delta for Android Auto Media Scope: Phase 1 (foundational plumbing) of `android-auto-local-music` only. See "Not in this delta" for explicit exclusions. ## ADDED Requirements ### Requirement: Local Music Root Access and Permission Persistence The system MUST let the user select a local-music root folder via SAF (`ACTION_OPEN_DOCUMENT_TREE`), persist the granted URI permission, and MUST NOT crash or leave the local-music root folder in a broken state if that permission is later lost or was never granted. #### Scenario: User picks a local music root folder - GIVEN the user opens the local-music setup flow - WHEN they complete the SAF folder picker and grant access - THEN the selected folder's URI permission is persisted - AND the local-music root becomes browsable in Android Auto without re-prompting the picker #### Scenario: Persisted permission survives app restart - GIVEN a local music root was previously picked and its permission persisted - WHEN the app is restarted (cold start) - THEN the local-music root remains browsable in Android Auto without re-prompting the SAF picker #### Scenario: Permission revoked or never granted - GIVEN the SAF permission for the local-music root was revoked outside the app, or no root was ever picked - WHEN the local-music root folder is browsed in Android Auto - THEN `getChildren` returns an empty or explanatory list, not an error - AND the audio handler does not throw or crash ### Requirement: Local Music Browsable Tree The Android Auto browse tree MUST expose a new non-playable local-music root folder alongside the existing station folders. Browsing that root and any nested subfolder MUST recursively mirror the picked SAF folder's structure: subfolders as non-playable `carpeta_local:` items and audio files as playable `pista:` items resolving to `PistaLocal` instances, using the raw filename as the displayed title. #### Scenario: Car browses the local-music root - GIVEN a local music root folder was picked and its permission is valid - WHEN `getChildren` is called with the local-music root folder id - THEN it returns the root's immediate subfolders as `carpeta_local:` items and audio files as `pista:` playable items - AND each playable item's title is the file's raw filename #### Scenario: Car browses a nested subfolder - GIVEN a `carpeta_local:` folder returned from a prior browse call - WHEN `getChildren` is called with that folder id - THEN it returns that subfolder's own contents (nested folders and/or tracks), recursively mirroring the on-device structure - AND browsing an empty subfolder returns an empty list, not an error ### Requirement: Local Track Playback Reuses Existing Pipeline Selecting a `pista:` item MUST resolve to the corresponding `PistaLocal` and play it through the existing `playMediaItem`/`PluriWaveAudioHandler` pipeline, unchanged, including the shared EQ signal chain. #### Scenario: User selects a local track - GIVEN the user taps a `pista:` playable item on the car head unit - WHEN `playFromMediaId(id)` is called - THEN the id resolves to a `PistaLocal` and playback starts through the existing `playMediaItem` internal path - AND standard transport (play/pause/stop) and fallback album art work as they do for stations #### Scenario: EQ still applies to local track playback (regression guard) - GIVEN a non-flat EQ preset is currently the active/principal preset - WHEN a `pista:` item is played - THEN the audible output passes through the same shared EQ signal chain used for station playback, with no separate or bypassed path for local tracks #### Scenario: Unknown or stale track id - GIVEN `playFromMediaId` is called with a `pista:` that no longer resolves to a known `PistaLocal` - WHEN resolution fails - THEN playback does not start and no unhandled exception propagates from the handler ### Requirement: Local Music Folder Item Cap A local-music folder (root or nested) whose immediate children exceed `_maxItemsPorCarpeta` (50) MUST be capped to that limit rather than returning unbounded results; pagination is out of scope for this delta. #### Scenario: Folder exceeds the item cap - GIVEN a picked or nested local-music folder contains more than 50 immediate children (subfolders and/or tracks combined) - WHEN `getChildren` is called with that folder id - THEN at most 50 items are returned, consistent with the existing `_maxItemsPorCarpeta` cap applied to station folders - AND no error or crash occurs as a result of the truncation ## MODIFIED Requirements ### Requirement: Browsable Media Tree `getChildren` MUST return a browsable tree rooted at `AudioService.browsableRootId`, organized into non-playable folders (Favoritos, Todas las emisoras, Mis emisoras, Ecualizador, and the new local-music root) containing playable items. Playable station items SHOULD carry an audio-quality subtitle when known. The `Favoritos` folder additionally MAY contain non-playable favorite-group sub-folders; `Todas las emisoras` and `Mis emisoras` remain flat. The `Ecualizador` folder is flat, non-playable, and contains only the 6 fixed EQ preset items. The local-music root folder is non-playable and may itself be nested (see "Local Music Browsable Tree"). (Previously: root contained exactly four folders — Favoritos, Todas las emisoras, Mis emisoras, Ecualizador — with no local-music root.) #### Scenario: Car requests the root - GIVEN the car head unit connects and requests the root (`AudioService.browsableRootId`) - WHEN `getChildren` is called with the root id - THEN it returns five folder `MediaItem`s (Favoritos, Todas las emisoras, Mis emisoras, Ecualizador, and the local-music root), each with `playable: false` #### Scenario: Car requests a folder with no stations - GIVEN the user has zero favorite stations - WHEN `getChildren` is called with the Favoritos folder id - THEN it returns an empty list, not an error #### Scenario: Browse requested before app state is loaded - GIVEN the audio handler starts cold and station/favorites Provider state has not finished loading - WHEN `getChildren` is called (root or any folder) - THEN it returns a valid, possibly empty, list without throwing and without blocking or crashing the service #### Scenario: Station has known codec and bitrate - GIVEN a station's `Emisora.codec` and `Emisora.bitrate` are both known (non-null) - WHEN it is mapped to a playable `MediaItem` - THEN `displaySubtitle` SHALL contain a human-readable quality hint combining bitrate and codec #### Scenario: Station has unknown codec or bitrate - GIVEN a station's `Emisora.codec` or `Emisora.bitrate` (or both) is null/unknown - WHEN it is mapped to a playable `MediaItem` - THEN `displaySubtitle` SHALL omit the quality hint gracefully, and MUST NOT render literal placeholder text such as "null kbps" #### Scenario: Ungrouped station appears exactly as before (regression guard) - GIVEN a station's `Emisora.grupoFavoritosId` equals `GrupoFavoritos.sinAsignarId` - WHEN the `Favoritos`, `Todas las emisoras`, or `Mis emisoras` folders are browsed - THEN that station appears as a playable `emisora:` item exactly as before, unaffected by the local-music root's existence or content ## Not in this delta Phase 1 does NOT specify: local-track metadata display beyond the raw filename (no title/artist tagging), album art beyond the existing generic on-brand fallback, sort/filter of local tracks or folders, or shuffle. These are deferred to Phase 2/3 per the proposal and MUST NOT be implemented against this spec.