Last updated: 2026-11-01
CarDash is a Flutter/Dart Android home-screen launcher for a landscape car tablet. It deliberately trades the reference head-unit chrome for maximum compatibility:
Intent.com.wasp.cardash_launcher. Display name: CarDash.| Decision | Choice | Rationale |
|---|---|---|
| Central app content | Launcher-native; apps open full-screen | Android cannot embed another app; overlay chrome was rejected as fragile and app-obstructing. |
| UI chrome | None (no bars) | Removes the mismatch between launcher chrome and full-screen apps; cleaner, more compatible. |
| Home layout | Single page, info + volume + up to 16 buttons | Matches the refined brief; tiles scale from 2 to 6 columns. |
| App slots | Navigation, Music, Radio with visibility switches | Kept from the original brief; All apps is fixed. |
| Pinned apps | All Apps long-press adds, Home long-press removes | Makes any installed app reachable from Home without editing a separate layout screen. |
| Home cap | 16 buttons total, including All apps; Settings is outside the grid | Keeps the single page legible at tablet resolutions; enforced in LauncherController. |
| Settings placement | Gear next to Wi-Fi in StatusCluster |
Keeps Settings reachable without spending a grid slot. |
| Playback control | Circled volume ring is play/pause; Mute pill remains | One prominent control for play/resume/pause without adding another card. |
| Volume control | FYT/Syu MCU sound module, then absolute setStreamVolume, UI-panel write, relative adjustStreamVolume, fixed-policy detection |
Car amplifiers ignore the Android stream on many Chinese units; the MCU module is the only app-reachable master volume there. |
| Icons | Only pinned apps show their own icon by default; fixed slots stay stylized | Keeps the dashboard consistent while making added apps recognizable. |
| Accent | Six curated choices, persisted in Settings | Changes the whole theme without a color picker or arbitrary contrast failures. |
| Battery visibility | One switch hides level and charging bolt | One control for both indicators, as requested. |
| Media feedback | Stylized Music tile + animated bars while media is active | Compact visual feedback without adding a media control surface. |
| Now-playing metadata | MediaSessionManager via a notification-listener component | The only public path to title, duration and position; read-only and scoped to media sessions. |
| Song progress | Ring fraction from position/duration; scrolling title | Replaces the volume ring as requested; paused state holds position and scroll offset. |
| Hint lifetime | Ten minutes from the first launch after a fresh install, then never | Avoids permanent onboarding chrome; upgraded installs migrate to expired. |
| Home role | Settings row only; one-time first-run tip | Removes permanent dashboard chrome while keeping discovery. |
| Pinned editing | Long-press menu: Rename / Change icon / Remove | Keeps the home page clean; removal remains immediate. |
| Custom icon source | Default app icon plus 29 bundled monochrome line icons | Matches the stylized dashboard and follows light/dark theme colors. |
| OBD transport | Bluetooth Classic SPP, bonded ELM327 only, read-only | No scanning, BLE, USB or Wi-Fi; one native worker serializes requests. |
| OBD values | Speed, throttle (49/11), MAF economy, ambient (46) |
The requested dashboard subset; unsupported PIDs stay hidden. |
| Economy | 5 km MAF rolling window, petrol constants, fuel-cut guard | Matches the design note; show — until 0.5 km. |
| Weather fallback | Open-Meteo + one-shot device location via the Temperature card | No API key/account; hide the row on any failure. |
| GPS speed fallback | Poll GPS when speed source is GPS, OBD is unavailable, or the Speed card is off | Asks for location when speed is enabled with GPS as the source; 0 below 2 km/h to suppress stationary drift. |
| Speed source | OBD-II preferred while connected; GPS fallback, switch in Settings | The user can force GPS even with an adapter connected. |
| Update check | Public Gogs releases API, non-blocking startup check, Settings card | Update downloads the APK and opens Android’s installer; installation stays user-confirmed. Yellow dot on Settings gear when available. |
| Temperature priority | OBD ambient while its source switch is on, else location weather | OBD wins when fresh; weather is the fallback. |
| Settings cards | Separate OBD-II, Speed and Temperature cards | Adapter settings stay isolated from display/source choices. |
| Accent background | Dark canvas/surfaces/borders blend toward the accent | Fixes the green base showing through when Blue is selected. |
| Screen | FLAG_KEEP_SCREEN_ON controlled by the Settings keep-awake switch |
Default on; no wake-lock permission, and off restores the system display timeout. |
| Night mode | Long-press empty background; black overlay + dim clock; tap wakes | Quiet night display without changing panel brightness or adding a wake lock. |
| Theme | Light / Dark / Automatic with fixed 19:00–07:00 window | No location permission or network; time pickers make the window adjustable. |
| PIN | Four digits, salted PBKDF2 hash, cold start only | Matches the explicit instruction; no re-lock while running. |
| Min Android | API 29 (Android 10) | Requested minimum; no in-scope API requires newer. |
| Target/compile | 36 | Current Flutter toolchain defaults; runs on API 29. |
| Orientation | sensorLandscape + Dart orientation lock |
Car/tablet form factor; portrait is intentionally unsupported. |
| AVD | 10.1" WXGA tablet, 1280×800, 160 dpi, API 35 and API 29 | Matches a dashboard tablet and the reference resolution class. |
| HOME role | Standard RoleManager request + Settings row |
A launcher must be chosen as the system home app; never silently forced. |
| Overlay/kiosk/root | Out of scope | Rejected for compatibility. |
Flutter
HomeScreen
ClockBlock / StatusCluster / VolumeCard / ShortcutGrid
LauncherController -> apps, slots, pinned apps, 16-button cap, default-home state
SystemController -> battery/Wi-Fi/volume/media state + media keys
DisplayController -> persisted keep-awake window flag
GpsSpeedController -> GPS speed fallback (permission requested from Speed settings)
TelemetrySettingsController -> speed/temperature visibility + sources
UpdateController -> public Gogs release check + release-page link
JellyfinController -> Quick Connect pairing, token reuse, retry/offline state,
genre selections
JellyfinDashboardScreen -> JellyfinDashboardController -> JellyfinLibraryService
JellyfinAlbumScreen -> track list + JellyfinPlaybackController
JellyfinPlaybackController -> JellyfinPlaybackPlatform -> NativeJellyfinPlayback
HomeScreen -> NightModeOverlay on empty-background long press
AllAppsScreen / AppPickerSheet
SettingsScreen -> General / Media / Jellyfin / OBD-II / Speed / Temperature /
Security / Updates
PinSetupScreen / LockScreen -> LockController -> PinService
Android (Kotlin)
MainActivity -> keep-screen-on, volume keys, permission forwarding
-> LauncherBridge (packages, icons, system flag, role)
-> SystemBridge (battery, Wi-Fi, volume, media activity, keys)
-> MediaBridge (MediaSession title/progress) + NotificationListener
-> TelemetryBridge (Bluetooth devices, OBD, location)
-> ObdService (Classic SPP ELM327 worker)
-> JellyfinPlaybackBridge -> MediaController -> JellyfinPlaybackService
(Media3 ExoPlayer, MediaSession, notification, JellyfinPlaybackReporter)
SettingsRepository is the only persistence path for theme, night window,
slot visibility/assignments, pinned apps, onboarding time, speed/temperature
visibility and source choices, the Jellyfin switch/URL and the selected genre
families. The Jellyfin access token and the salted PIN hash live in
flutter_secure_storage.runApp, so there is no dashboard flash on a
locked cold start.runApp, and a
missing network retries with backoff instead of blocking the dashboard.
configured (URL + saved token) gates the Home switch; connected gates
network actions.mediaPlayback service.
The Dart controller owns the queue and polls state; the service owns audio
focus, the notification and playback reporting. It pauses on permanent focus
loss, resumes after transient loss, and stops at album end.| # | Milestone | State |
|---|---|---|
| M0 | Local git repo, Flutter scaffold, launcher config, package ID, icons, docs | Done |
| M1 | x86_64 API 35/API 29 images, 1280×800 landscape tablet AVDs, emulator/install scripts | Done |
| M2 | Theme system, single home layout, clock/status, volume card, shortcut grid | Done (initial visual pass) |
| M3 | Kotlin bridges: app list/icons/launch, HOME role, volume, battery/Wi-Fi | Done |
| M4 | App picker, All Apps, slot persistence, long-press flows, launch errors | Done |
| M5 | Settings, theme auto window, PIN setup/change/remove, cold-start lock | Done |
| M6 | Interaction testing, light/dark screenshots, layout polish, API 29 verification | Done |
| M7 | Release build, final docs, screenshots, last commits | Done |
| M8 | Home-button visibility switches, All Apps pinning, 16-button adaptive grid, keep-awake | Done |
| M9 | Accent colors, battery toggle, fading hint, user icons, Media equalizer, install rule | Done |
| M10 | Remove Home-role banner, one-time first-run tip, pinned Rename/icon/remove menu | Done |
| M11 | Restore stylized fixed icons; pinned apps show own icons by default; 29-icon selector | Done |
| M12 | Accent-tinted dark theme, OBD-II adapter/telemetry, location weather fallback, Car data Settings | Done |
| M13 | Play/pause volume ring, Settings gear in status area, 16-button cap without Settings | Done |
| M14 | Song-progress ring, scrolling now-playing title, notification-access Settings card | Done |
| M15 | Keep-awake Settings switch (default on), night mode, 1280×720 layout | Done |
| M16 | GPS speed fallback when no OBD adapter and location is already allowed | Done |
| M17 | Non-blocking Gogs update check; 1.0.1 version-only update-test release | Done |
| M18 | Bluetooth-off settings action; 1.0.2 release | Done |
| M19 | OBD-II/Speed/Temperature Settings cards; source switches; 10-minute first-install hint; 1.0.3 | Done |
| M20 | Fixed-volume car detection; relative volume fallback; 1.0.4 | Done |
| M21 | On-screen volume diagnostics and stream snapshots; 1.0.5 | Done |
| M22 | Bluetooth/OBD freeze hardening: UI-thread offload, device filtering, bounded retries, resume debounce; 1.0.6 | Done |
| M23 | Move the Impeller opt-out into <application> so Skia is actually used; 1.0.7 |
Done |
| M24 | Jellyfin 1.2.0: Home slot, dashboard/genres, album page, Media3 playback, background notification, audio focus, tests and release | Done |
flutter analyze clean.STREAM_MUSIC while the dashboard is foreground).CarDashVolume logcat tag.adb shell wm size 1024x600).FLAG_KEEP_SCREEN_ON follows the keep-awake switch: present by default, cleared when off and restored when on.releases/; release published for update testing.docs/screenshots/; README/AGENTS/plan/OBD doc updated.ContentObserver would remove
the polling. Play/pause uses standard Android media keys.QUERY_ALL_PACKAGES is required for a launcher app list. It is documented
and acceptable for sideloading; a Play distribution needs policy work.-gpu host.AudioManager.isMusicActive
and is suppressed while Jellyfin plays and while the active system session is
CarDash's own package. That package guard prevents a flash while a Jellyfin
pause propagates through the 900 ms system poll. Jellyfin title/progress come
from its own controller. Other apps' title/progress query the active
MediaSession through the notification-listener component, which is
user-granted and scoped to media metadata.JellyfinService; recently played is derived
from played Audio tracks because album UserData is not updated by
playback. Parse defensively.JellyfinPlaybackController.seek and keeps a local
drag value until the next poll. Includes the 1.2.3 recents/normal-app work and
the Jellyfin progress-duration fallback. 150 tests pass; release
releases/cardash-android-1.2.4.apk.excludeFromRecents, so it
appears in the recents/overview list and can be switched back to.
resumeWhilePausing and alwaysRetainTaskState were added for a cleaner Home
transition and task retention. Android can still kill a background app; no
keep-alive service, persistent flag or battery-exemption request was added.
On vendor firmware the user must allow autostart/no battery restrictions.
Includes all 1.2.2 Syu volume, Jellyfin favorites and streaming-quality work.
Release releases/cardash-android-1.2.3.apk.No favorites empty state. The album page gains a heart that sets
or unsets the server favorite, and the Favorites tab refreshes when you return.
The Home Jellyfin tile opens the currently active album with the dashboard
underneath, so Back returns to Albums instead of Home. Also adds a Streaming
quality dropdown: Direct (default) or 320/256/192/128 kbps MP3 transcoding.
The quality is persisted in SettingsRepository, read when the next album
queue is built, and shown in the Jellyfin settings card. Included in 1.2.2.releases/cardash-android-1.2.1.apk.mediaPlayback service with a media notification, background playback,
hardware media keys, audio-focus pause/resume, end-of-album stop and Jellyfin
playback reporting. The Music/Jellyfin equalizers are exclusive and the home
tile subtitle is clipped to the button width. 134 tests pass; the universal
release is releases/cardash-android-1.2.0.apk.setStreamVolume; fixed-volume sliders now
explain that the vehicle amplifier owns the level. Bumped to 1.0.4+16.EnableImpeller=false opt-out had been placed under <activity>,
where Flutter silently ignored it; moved it to <application>, so 1.0.7
actually uses Skia. Bumped to 1.0.7+19.CarDashVolume logcat output,
to identify devices that ignore both absolute and relative volume writes.
Bumped to 1.0.5+17; 66 tests pass.releases/cardash-android-1.0.0.apk with checksum and release notes.com.syu.ms toolkit service and drives the sound module's absolute
master volume (command 0, module 4, 0–36), so the on-screen slider changes the
amplifier instead of the unused Android media stream. The car's own knob and
steering-wheel controls stay independent; the Android key path is unchanged.
On other units the existing absolute/relative/UI-panel fallbacks remain.
Reads that never follow a write no longer snap the slider back: the requested
level is kept and shown with a ?, and the diagnostics panel gained the
minimum, control source and vendor state. No new permission, no root and no
manifest change.releases/cardash-android-1.2.2.apk.