Merges its delta requirements into the android-auto-media base spec. This closes out Phase-1 polish for local music; Phase 2 (metadata, sort/filter, real art) and Phase 3 (subfolder scoping, shuffle) remain the only planned future work for this feature.
384 lines
23 KiB
Markdown
384 lines
23 KiB
Markdown
# Android Auto Media Specification
|
|
|
|
## Purpose
|
|
|
|
PluriWave MUST expose a browsable station tree and play-by-media-id interface to Android Auto (projected) via the existing `PluriWaveAudioHandler` (`audio_service` MediaBrowserService/MediaSession), so a driver can browse and play stations from the car head unit without rebuilding the audio pipeline.
|
|
|
|
## Requirements
|
|
|
|
### Requirement: Android Auto Discovery Declaration
|
|
|
|
The app MUST declare itself as a media app to Android Auto so the car head unit discovers it.
|
|
|
|
#### Scenario: Android Auto scans installed apps
|
|
|
|
- GIVEN PluriWave is installed on the phone
|
|
- WHEN Android Auto scans installed apps for car-app support
|
|
- THEN it finds `res/xml/automotive_app_desc.xml` declaring `<uses name="media"/>`
|
|
- AND the `AndroidManifest.xml` `<application>` block declares `<meta-data android:name="com.google.android.gms.car.application" android:resource="@xml/automotive_app_desc"/>`
|
|
- AND PluriWave appears in the car's media app list
|
|
|
|
### Requirement: Favorite Group Sub-Folders
|
|
|
|
The Android Auto browse tree MUST expose favorite groups (`GrupoFavoritos`, as already modeled by `Emisora.grupoFavoritosId` and surfaced by `EstadoRadio.gruposFavoritos`) as browsable, non-playable sub-folders reachable from the existing `Favoritos` folder, without altering the 3 root folders (`Favoritos`, `Todas las emisoras`, `Mis emisoras`).
|
|
|
|
#### Scenario: Car requests the Favoritos folder and groups exist
|
|
|
|
- GIVEN the user has one or more favorite groups with at least one member station each
|
|
- WHEN `getChildren` is called with the `Favoritos` folder id
|
|
- THEN it returns one non-playable folder `MediaItem` per surfaced group, in addition to (or instead of, per the design's structural decision) any ungrouped favorite stations
|
|
- AND each group folder's id follows a browsable `grupo:<id>` scheme distinct from the `emisora:<uuid>` playable-item scheme
|
|
|
|
#### Scenario: Car requests a group folder's stations
|
|
|
|
- GIVEN a favorite group folder with id `grupo:<id>` was returned under `Favoritos`
|
|
- WHEN `getChildren` is called with that `grupo:<id>` folder id
|
|
- THEN it returns the playable `MediaItem`s for exactly the stations whose `Emisora.grupoFavoritosId` matches `<id>`
|
|
- AND those items are sorted and capped using the same ordering and item-count rules already applied to the other folders (`ordenarEmisoras`, 50-item cap)
|
|
- AND selecting one of those items plays the corresponding station via the existing `emisora:<uuid>` playback path, unchanged
|
|
|
|
#### Scenario: Car requests an unknown or stale group id
|
|
|
|
- GIVEN a `grupo:<id>` id that does not match any group known to the current snapshot
|
|
- WHEN `getChildren` is called with that id
|
|
- THEN it returns an empty list, not an error
|
|
|
|
### Requirement: Empty Favorite Group Handling
|
|
|
|
The system MUST produce a browsable tree that never presents a user-selectable folder promising content it cannot deliver: for any favorite group with zero member stations, the tree MUST either omit that group's folder from `Favoritos`'s children, or include it and return an empty (not erroring) child list when browsed. The specific choice between omitting empty-group folders and showing-but-empty, and any related folder-count/flat-vs-nested structural decision for `Favoritos`, is deferred to `sdd-design`; whichever mechanism design selects MUST satisfy both scenarios below.
|
|
|
|
#### Scenario: Empty group folder is browsed (if shown)
|
|
|
|
- GIVEN a favorite group has zero member stations and the design's chosen mechanism surfaces it as a folder under `Favoritos`
|
|
- WHEN `getChildren` is called with that group's `grupo:<id>`
|
|
- THEN it returns an empty list, not an error
|
|
|
|
#### Scenario: No user-facing dead end
|
|
|
|
- GIVEN the full set of favorite groups, including any empty ones
|
|
- WHEN the `Favoritos` folder is browsed and then each of its returned children is browsed
|
|
- THEN no returned folder child ever throws, hangs, or surfaces a driver-facing error state
|
|
- AND the car head unit's total folder/item count presented under `Favoritos` remains within the driver-distraction-safe bounds design establishes
|
|
|
|
### 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 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 (see "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 (see "EQ Preset Browsable Folder"). The local-music root folder is non-playable and may itself be nested (see "Local Music Browsable Tree").
|
|
(Previously: root contained exactly 3 folders — Favoritos, Todas las emisoras, Mis emisoras — with no EQ or local-music folder; Favoritos was a flat folder of playable station items only, with no sub-folder nesting.)
|
|
|
|
#### 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 (e.g. "128 kbps · MP3")
|
|
|
|
#### 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 (no subtitle, or a subtitle with no quality fragment)
|
|
- AND the subtitle MUST NOT render literal placeholder text such as "null kbps" or "null · null"
|
|
|
|
#### Scenario: Ungrouped station appears exactly as before (regression guard)
|
|
|
|
- GIVEN a station's `Emisora.grupoFavoritosId` equals `GrupoFavoritos.sinAsignarId` (`'sin_asignar'`, the default when no group is assigned)
|
|
- WHEN the `Favoritos`, `Todas las emisoras`, or `Mis emisoras` folders are browsed
|
|
- THEN that station appears as a playable `emisora:<uuid>` item in exactly the same folder(s), position (subject to existing sort rules), title, art, and subtitle as it did before favorite-group folders were introduced
|
|
- AND its presence and shape are unaffected by the existence, emptiness, or content of any favorite group
|
|
|
|
### Requirement: EQ Preset Browsable Folder
|
|
|
|
The Android Auto browse tree MUST expose the 6 fixed EQ presets (`PresetEcualizador.presets`) as playable items inside the `Ecualizador` root folder, each using a distinct `eq_preset:<nombre>` media-id scheme, separate from `emisora:<uuid>` and `grupo:<id>`.
|
|
|
|
#### Scenario: Car requests the Ecualizador folder
|
|
|
|
- GIVEN the `Ecualizador` folder id was returned under the root
|
|
- WHEN `getChildren` is called with the `Ecualizador` folder id
|
|
- THEN it returns exactly 6 playable `MediaItem`s, one per `PresetEcualizador.presets` entry, titled with the preset's name
|
|
- AND each item's id is `eq_preset:<nombre>`, where `<nombre>` is that preset's unique name
|
|
|
|
### Requirement: EQ Preset Selection Applies Without Disturbing Playback
|
|
|
|
Selecting an `eq_preset:<nombre>` item MUST apply that preset immediately through the headless-safe EQ seam (e.g. `PluriWaveAudioHandler.aplicarPreset`), via a branch in `playFromMediaId` intercepted BEFORE the `emisora:`/`grupo:` routing. It MUST NOT call `playMediaItem`, MUST NOT mutate the now-playing `mediaItem`, and MUST NOT start, stop, restart, or otherwise alter current playback or playback position.
|
|
|
|
#### Scenario: User selects a preset while a station is playing
|
|
|
|
- GIVEN a station is currently playing and projected to the car
|
|
- WHEN the user taps an `eq_preset:<nombre>` item, resolved via `playFromMediaId`
|
|
- THEN the matching preset is applied via the headless-safe apply seam
|
|
- AND the currently playing station continues uninterrupted, with no change to playback position or now-playing metadata
|
|
|
|
#### Scenario: User selects a preset while nothing is playing
|
|
|
|
- GIVEN no station is currently playing
|
|
- WHEN the user taps an `eq_preset:<nombre>` item
|
|
- THEN the matching preset is applied via the headless-safe apply seam
|
|
- AND no playback starts as a result of the selection
|
|
|
|
#### Scenario: Unknown or stale preset id
|
|
|
|
- GIVEN `playFromMediaId` receives an `eq_preset:<nombre>` id whose `<nombre>` matches no entry in the current `PresetEcualizador.presets`
|
|
- WHEN resolution fails to find a matching preset
|
|
- THEN the selection is a no-op: no preset is applied, no playback state changes, and no unhandled exception propagates from the handler
|
|
|
|
### Requirement: EQ Preset Persistence and Phone/Car Parity
|
|
|
|
An EQ preset applied from the car MUST persist as the principal preset, with the same observable outcome as the phone's existing `ServicioEcualizador.guardarPrincipal`/`cambiarPresetPrincipal` path, and MUST be reflected on the phone. A principal preset changed on the phone MUST be observable from the car. The exact code path used to reach the headless-safe persistence seam from an Android Auto bind is deferred to `sdd-design`.
|
|
|
|
#### Scenario: Preset applied from the car persists for the phone
|
|
|
|
- GIVEN the user selects `eq_preset:<nombre>` from the car head unit
|
|
- WHEN the selection is processed
|
|
- THEN the preset is persisted as the principal preset
|
|
- AND opening the phone's EQ UI afterward shows that preset as the active/principal preset
|
|
|
|
#### Scenario: Preset applied from the phone is reflected for the car
|
|
|
|
- GIVEN the user changes the principal preset from the phone UI
|
|
- WHEN the car head unit subsequently observes EQ-related state through the browse/selection surface
|
|
- THEN the car-observable state reflects the phone's most recently applied principal preset
|
|
|
|
### Requirement: Active Preset Indication (Optional)
|
|
|
|
The system MAY indicate the active/principal preset within the `Ecualizador` folder's rows. Because the legacy `MediaBrowserService` browsable-item model has no native "selected item" affordance, any indication, if implemented, MUST be conveyed via a row `title` text convention (e.g. a marker prefix/suffix) rather than assuming a checkmark or selection icon exists. If a reliable title-text convention is too costly to maintain, this requirement MAY instead be satisfied by explicitly not implementing active-preset indication.
|
|
|
|
#### Scenario: Active preset is indicated via title convention (if implemented)
|
|
|
|
- GIVEN the currently applied principal preset is known when `Ecualizador`'s children are built
|
|
- WHEN the design's chosen title-text convention is applied
|
|
- THEN exactly one of the 6 preset rows' `title` carries the active-preset marker, uniquely identifying that preset among the 6
|
|
|
|
#### Scenario: Active preset indication is out of scope (if not implemented)
|
|
|
|
- GIVEN the design opts not to implement an active-preset marker
|
|
- WHEN the `Ecualizador` folder's children are built
|
|
- THEN all 6 preset rows are returned with their plain preset names, with no partial or inconsistent marking applied
|
|
|
|
### 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:<id>` items and audio files as playable `pista:<id>` 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:<id>` items and audio files as `pista:<id>` playable items
|
|
- AND each playable item's title is the file's raw filename
|
|
|
|
#### Scenario: Car browses a nested subfolder
|
|
|
|
- GIVEN a `carpeta_local:<id>` 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:<id>` 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:<id>` 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:<id>` 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:<id>` 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 and Paging
|
|
|
|
A local-music folder (root or nested) whose immediate children exceed `_maxItemsCarpetaLocal` (50) MUST NOT silently truncate the excess items. `getChildren` MUST return at most 50 items for the requested page, followed by a trailing non-playable, browsable "more" item whenever additional items remain beyond that page. Selecting the "more" item MUST invoke `getChildren` again and reveal the next page of up to 50 items, continuing until every item in the folder has been reached; no item may be permanently unreachable. A page that exhausts the folder's remaining items MUST NOT include a "more" item.
|
|
(Previously: folders exceeding 50 immediate children were capped to 50 with the remainder silently dropped; pagination was explicitly out of scope.)
|
|
|
|
#### 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 for the first page
|
|
- THEN exactly 50 items are returned for that page, followed by a trailing "more" item
|
|
- AND no error or crash occurs, and no item is permanently unreachable
|
|
|
|
#### Scenario: Folder has exactly 50 items (boundary)
|
|
|
|
- GIVEN a local-music folder contains exactly 50 immediate children
|
|
- WHEN `getChildren` is called with that folder id
|
|
- THEN all 50 items are returned
|
|
- AND no "more" item is appended, since nothing remains to reveal
|
|
|
|
#### Scenario: Folder with 51-100 items pages in two steps
|
|
|
|
- GIVEN a local-music folder contains between 51 and 100 immediate children
|
|
- WHEN `getChildren` is called with that folder id
|
|
- THEN the first page returns 50 items plus a trailing "more" item
|
|
- AND WHEN the user selects that "more" item, `getChildren` is invoked again
|
|
- THEN it returns the remaining items with no further "more" item, since that is the last page
|
|
|
|
#### Scenario: Folder with 100+ items chains across 3+ pages
|
|
|
|
- GIVEN a local-music folder contains more than 100 immediate children
|
|
- WHEN the user repeatedly selects each returned page's "more" item
|
|
- THEN each intermediate page returns 50 items plus a trailing "more" item
|
|
- AND the final page returns only the remaining items with no "more" item
|
|
- AND every item in the folder is reachable through this chain, none dropped
|
|
|
|
#### Scenario: Only the requested page's items are resolved into MediaItems
|
|
|
|
- GIVEN a local-music folder contains more than 50 immediate children
|
|
- WHEN `getChildren` is called for a given page
|
|
- THEN only that page's items (at most 50, plus the "more" item) are constructed into full `MediaItem`s (art, resolved title)
|
|
- AND items belonging to pages not yet requested are NOT constructed into `MediaItem`s as part of that call
|
|
|
|
### Requirement: Local Music Folder Friendly Name Display
|
|
|
|
The settings UI MUST display a human-readable label for the picked local-music root folder instead of the raw SAF `content://` tree URI. If a friendly name cannot be derived from the persisted URI, the settings UI MUST fall back to a clear, non-empty label rather than showing a broken, blank, or raw-URI value.
|
|
|
|
#### Scenario: Folder picked and friendly name derivable
|
|
|
|
- GIVEN a local-music root folder has been picked and its URI persisted
|
|
- WHEN the settings screen displays the current folder
|
|
- THEN it shows a human-readable name derived from the folder, not the raw content URI
|
|
|
|
#### Scenario: Friendly name cannot be derived
|
|
|
|
- GIVEN a persisted local-music root URI whose friendly name cannot be derived (e.g. a malformed or unexpected SAF documentId shape)
|
|
- WHEN the settings screen displays the current folder
|
|
- THEN it falls back to a non-empty, non-broken label instead of an empty or corrupted value
|
|
|
|
### Requirement: Playable Item Metadata
|
|
|
|
Every playable `MediaItem` (station) MUST include a non-empty `title` and a loadable `artUri`. Stations without a logo MUST fall back to on-brand artwork, and stations whose logo URL cannot actually be loaded MUST degrade to the same on-brand fallback instead of rendering broken or blank art. The fallback MUST be visually consistent with the phone UI's per-station rotation rather than a generic launcher-icon copy.
|
|
(Previously: fallback only covered a null/empty favicon and pointed at a bundled default-artwork asset that read as a copy of the launcher icon.)
|
|
|
|
#### Scenario: Station has a valid, reachable remote logo
|
|
|
|
- GIVEN a station has a remote logo URL that resolves to a loadable image
|
|
- WHEN it is mapped to a `MediaItem`
|
|
- THEN `title` is the station name and `artUri` is the station's logo URL
|
|
|
|
#### Scenario: Station has no logo (Case A)
|
|
|
|
- GIVEN a station has no logo (`favicon` is null or empty)
|
|
- WHEN it is mapped to a `MediaItem`
|
|
- THEN `artUri` is set to one of the on-brand `station_art_*` fallback assets instead of being empty or null
|
|
|
|
#### Scenario: Station's logo URL is present but unreachable (Case B)
|
|
|
|
- GIVEN a station has a non-empty `favicon` URL that cannot be loaded (dead link, unreachable host, or non-image response)
|
|
- WHEN it is mapped to (or resolved as) a `MediaItem` in the Android Auto browse tree
|
|
- THEN the system SHALL show the same on-brand fallback art used for Case A instead of broken or blank art
|
|
- AND the car head unit MUST NOT display an empty, broken-image, or indefinitely-loading art tile for that station
|
|
|
|
#### Scenario: Fallback art matches phone-UI per-station selection
|
|
|
|
- GIVEN a station falls back to on-brand art (Case A or Case B)
|
|
- WHEN the fallback asset is chosen for that station
|
|
- THEN the selected asset MUST be one of the same 4 rotating assets used by the phone UI (`station_art_aurora`, `station_art_cosmic`, `station_art_pulse`, `station_art_nova`)
|
|
- AND the same station MUST deterministically resolve to the same asset on both the phone UI and Android Auto (per-station selection parity), not a random or session-varying choice
|
|
|
|
#### Scenario: Fallback art is on-brand, not the launcher icon
|
|
|
|
- GIVEN a station requires fallback art (Case A or Case B)
|
|
- WHEN its `artUri` is resolved
|
|
- THEN it MUST NOT point at a byte-for-byte copy of the app launcher icon
|
|
- AND it MUST point at one of the 4 on-brand `station_art_*` assets
|
|
|
|
### Requirement: Media Item Resolution by ID
|
|
|
|
`getMediaItem` MUST resolve a single station id to its full `MediaItem`, returning `null` for unknown ids instead of throwing.
|
|
|
|
#### Scenario: Known station id
|
|
|
|
- GIVEN a valid station id that exists in current state
|
|
- WHEN `getMediaItem(id)` is called
|
|
- THEN it returns the corresponding `MediaItem` with `title` and `artUri` populated
|
|
|
|
#### Scenario: Unknown station id
|
|
|
|
- GIVEN an id that does not match any known station or folder
|
|
- WHEN `getMediaItem(id)` is called
|
|
- THEN it returns `null` without throwing
|
|
|
|
### Requirement: Play by Media ID Reuses Existing Playback Path
|
|
|
|
`playFromMediaId` MUST resolve the id to an `Emisora` and invoke the existing internal `playMediaItem` path; it MUST NOT duplicate playback or reconnection logic.
|
|
|
|
#### Scenario: User selects a station in the car
|
|
|
|
- GIVEN the user taps a playable station item on the car head unit
|
|
- WHEN `playFromMediaId(id)` is called
|
|
- THEN the id is resolved to an `Emisora` via the existing resolution logic (`_emisoraDesdeMediaItem`)
|
|
- AND playback starts through the existing `playMediaItem` internal path
|
|
- AND no separate or duplicated playback logic is executed
|
|
|
|
#### Scenario: Unknown or stale media id
|
|
|
|
- GIVEN `playFromMediaId` is called with an id that no longer resolves to a known station
|
|
- WHEN resolution fails
|
|
- THEN playback does not start and no unhandled exception propagates from the handler
|
|
|
|
### Requirement: Playback State Synchronization
|
|
|
|
Play/pause/stop state changes MUST remain synchronized between the car head unit and the phone UI, regardless of which side initiated the change.
|
|
|
|
#### Scenario: User pauses from the car
|
|
|
|
- GIVEN a station is playing and projected to the car
|
|
- WHEN the user pauses from the car head unit
|
|
- THEN the phone UI reflects the paused state via the shared `MediaSession`/`PlaybackState`
|
|
|
|
#### Scenario: User pauses from the phone
|
|
|
|
- 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
|