IMPLEMENTATION_PLAN.md 32 KB

CarDash implementation plan

Last updated: 2026-10-31

1. What we are building

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:

  • One home page. No top bar, no bottom dock, no pages.
  • Clock/date, battery/charging and Wi-Fi on the page.
  • A prominent now-playing card: song-progress ring around play/pause, scrolling title/artist, volume slider, −/+, mute.
  • Up to 16 home buttons: Navigation, Music, optional Jellyfin, Radio, All apps, plus apps pinned from All Apps. Settings lives next to Wi-Fi in the top-right status area, outside the grid.
  • Navigation/Music/Radio are app slots with show/hide switches. They can be assigned, changed and cleared. Jellyfin is a built-in optional music source (no app assignment): a dashboard, album page, grouped genres and an in-app Media3 player. All apps is always present and last.
  • Pinned apps are added by long-pressing an app in All Apps and removed by long-pressing the added button on Home. Only pinned apps show their own icon by default; the fixed buttons and predefined slots stay stylized.
  • The grid adapts from 2 to 6 columns, shrinks its tiles as the count grows, and is anchored to the bottom edge so a short row never floats mid-page.
  • Every external app opens full-screen through a normal Android Intent.
  • Settings: light/dark/automatic theme, six accent colors, battery visibility, fixed night window, home-button switches, slot assignment, HOME-role setup, four-digit PIN, plus separate OBD-II, Speed and Temperature cards.
  • The stylized Music tile shows an animated equalizer while another app is playing; the Jellyfin tile shows it only while CarDash's Jellyfin player plays.
  • Settings → Media grants notification access for the active MediaSession title, duration and position. CarDash does not read notification content.
  • The long-press hint is visible only during the first ten minutes after a fresh installation, then never returns. Upgraded installs are migrated to expired.
  • The Home-role banner is gone; a one-time first-run tip points to Settings.
  • Long-pressing a pinned app opens Rename, Change icon and Remove. Rename/icon changes persist; Remove needs no confirmation.
  • Optional OBD-II over a paired Bluetooth Classic ELM327: digital speed, throttle bar, rolling 5 km MAF economy and ambient temperature.
  • Speed card with a show/hide switch and, while OBD is selected, an OBD-II/GPS source switch. GPS is the fallback; stationary shows 0 km/h.
  • Temperature row with a show/hide switch and, while OBD is selected, an OBD-II/weather source switch. Open-Meteo weather + location is the fallback; red below 3°C.
  • Non-blocking Gogs update check with a Settings → Updates card and a release-page link; no automatic download or silent installation.
  • Dark surfaces are tinted by the selected accent instead of retaining a green base.
  • PIN is requested only on a cold start, never on resume or screen-off.
  • The screen stays awake while CarDash is the foreground window.
  • Landscape-only; tablet-first 1280×800, also verified at 1280×720; Android 10 minimum.
  • Settings keeps the display awake by default; the user can switch to the system timeout.
  • Long-press on empty Home enters a black night overlay with a very dim clock; tap wakes.
  • Package: com.wasp.cardash_launcher. Display name: CarDash.

2. Decisions and why

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 Absolute setStreamVolume, relative adjustStreamVolume fallback, fixed-policy detection Some car units ignore absolute writes; fixed-volume amplifiers cannot be controlled by apps.
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.

3. Architecture

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.
  • Package enumeration and icon rendering happen on a worker thread in Kotlin and are returned as PNG bytes.
  • Timers for volume/status are stopped when the app leaves the foreground.
  • The PIN check is awaited before runApp, so there is no dashboard flash on a locked cold start.
  • Jellyfin uses Quick Connect only. The pairing check runs after 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.
  • The Jellyfin dashboard asks for 6 albums per row and shows 6 columns when each card is at least 160 px wide, otherwise 5. Album labels share the cover's left edge; the album page cover is top-aligned; a Random selection re-rolls that row after playback starts.
  • Jellyfin playback is Media3 1.11.1 in a foreground 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.
  • Network access is limited to the optional Open-Meteo weather fallback, the user-configured Jellyfin server (library, artwork, audio), and the Gogs release check; no telemetry.

4. Milestones

# 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

5. Verification checklist

  • flutter analyze clean.
  • Unit/widget tests pass (77 tests, including OBD device filtering, bounded retries, resume debounce and the application-scoped Impeller opt-out).
  • Debug APK builds with JDK 21.
  • Installs on API 35 1280×800 tablet.
  • HOME role granted; Home opens CarDash.
  • Immersive full-screen; no Android bars.
  • Real app list and icons in All Apps.
  • App picker assign / long-press change / clear, with persistence across restart.
  • Launch Navigation/Music/Radio full-screen and return Home (verified with Maps and YouTube Music).
  • Volume slider, mute and hardware-key synchronization (keys are routed to STREAM_MUSIC while the dashboard is foreground).
  • Fixed-volume car units disable the slider and pass hardware keys through; absolute-write failures fall back to relative stream steps.
  • Volume diagnostics expose raw stream values and last-write results on screen and under the CarDashVolume logcat tag.
  • Light theme, dark theme and automatic switch.
  • PIN set/change/remove; cold start shows lock; wrong PIN; no re-lock after returning from an app.
  • Verify 1024×600 compact layout (adb shell wm size 1024x600).
  • Boot and verify on Android 10 (API 29) AVD, including PIN and release APK.
  • Release APK on both AVDs (54 MB universal APK).
  • Navigation/Music/Radio switches hide/show buttons, persist, and preserve assignments.
  • All Apps long-press pins apps; the 16-button cap rejects a 17th button.
  • Long-press on an added app removes it; it can be re-added from All Apps.
  • 16 buttons fit at 1280×800, 1280×720 and 1024×600; the grid uses 2–6 columns.
  • FLAG_KEEP_SCREEN_ON follows the keep-awake switch: present by default, cleared when off and restored when on.
  • Long-press on empty Home enters night mode; card/button long-presses are unaffected; tapping wakes.
  • Six accent choices change the Material theme immediately and persist.
  • Battery switch hides both the battery level and charging bolt.
  • Settings is next to Wi-Fi; All apps is the last grid button.
  • A user-installed app (F-Droid) shows its own icon; system apps stay generic.
  • Music equalizer widget animates; native media-active polling is wired.
  • Home hint is gone ten minutes after the first launch of a fresh install.
  • All Apps search icon is visibly smaller.
  • No Home-role banner; the first-run tip appears once, then never again.
  • Long-press pinned app shows Rename, Change icon and Remove.
  • Rename updates the Home label; Change icon persists a stylized icon; Remove is immediate.
  • Fixed buttons stay stylized; pinned apps show their own icons by default.
  • Change icon offers Default plus 29 monochrome icons and applies the chosen icon.
  • Accent-tinted dark background changes from green to blue when Blue is chosen.
  • OBD Settings device dropdown and Bluetooth permission action are present.
  • OBD economy/card tests pass; native ELM327 path compiles.
  • Location permission, reverse geocoding and Open-Meteo fallback verified live (Ukiah, 17°C).
  • Temperature row renders below the date and follows the selected OBD/weather source.
  • Circled ring shows song progress; 44 tests include media keys, metadata, progress, the marquee, the ring, keep-awake and night mode.
  • Scrolling title/artist verified with a live Chrome MediaSession; pause holds both the ring and scroll position, resume continues.
  • Settings → Media grants notification access and shows Granted.
  • Settings gear is next to Wi-Fi; the grid ends with All apps.
  • GPS speed shows 0 km/h when stationary with no OBD adapter; enabling speed asks for location permission.
  • 1.0.0 updater build installed on emulator/tablet; it reported up to date before 1.0.1.
  • 1.0.1 universal APK built and added to releases/; release published for update testing.
  • Emulator completed the full in-app 1.0.0 → 1.0.1 download/installer flow.
  • Yellow update dot appears on the Settings gear; 66 tests pass.
  • Bluetooth-off Settings card exposes Open Bluetooth settings and refreshes on return.
  • 1.0.2 universal APK built and published for in-app update.
  • Settings split into separate OBD-II, Speed and Temperature cards; OBD-II holds adapter settings only.
  • Speed and Temperature toggles hide/show their Home elements and persist.
  • OBD/GPS source switches appear while an adapter is selected and persist.
  • Onboarding hint is limited to ten minutes after a fresh install; upgraded installs never show it.
  • 66 tests pass, including the onboarding window and source preferences.
  • 1.0.3 universal APK built and published for in-app update.
  • Jellyfin Settings card: enable switch, URL entry, Quick Connect code, saved-token reuse, status, offline retry and token invalidation covered by unit/widget tests.
  • Jellyfin Home switch after Music, disabled/off until configured, counts toward the 16-button cap and clears on disconnect.
  • Jellyfin dashboard: Albums/Artists strip, Recently played / Recently added / Random, grouped genres, 6 cards when they fit and 5 otherwise, Random re-shuffle after selection.
  • Jellyfin album page: top-aligned cover, track list, current track, previous/next, play/pause and volume.
  • Media3 playback: background foreground service, media notification, hardware media keys, audio-focus pause/resume, end-of-album stop and Jellyfin playback reporting.
  • Exclusive Music/Jellyfin equalizers and the Jellyfin tile subtitle clipped to the button width.
  • 134 tests pass, including the Jellyfin API contract, genre catalog, controllers, album page, dashboard and genre picker.
  • 1.2.0 universal APK built and published for in-app update.
  • Tablet has the 1.0.0 installer build; unknown-app confirmation is user-controlled.
  • Screenshots in docs/screenshots/; README/AGENTS/plan/OBD doc updated.

6. Known risks and mitigations

  1. Media-volume polling is every 900 ms while foreground. It is cheap and handles hardware keys, but a future native ContentObserver would remove the polling. Play/pause uses standard Android media keys.
  2. QUERY_ALL_PACKAGES is required for a launcher app list. It is documented and acceptable for sideloading; a Play distribution needs policy work.
  3. PIN security scope: cold-start only by design; it does not replace the Android keyguard or protect against adb/data extraction. README explains it.
  4. Emulator stability: headless emulators can be killed by the host; scripts use managed boot and the host GPU. SwiftShader is only a fallback.
  5. Flutter 3.47 / AGP 9.1 / JDK 21: works on this machine; Java 27 is not the validated toolchain. The emulator uses the host GPU; SwiftShader segfaulted on the API 35 image, so the helper script defaults to -gpu host.
  6. 16-button density: small labels and mixed pinned-app icons are the trade-off for a single page. The Settings count and grid scaling make the limit explicit.
  7. Screen-on battery use: the flag applies only while the dashboard is foreground; it is not a background wake lock.
  8. Media metadata: the Music equalizer follows 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.
  9. Pinned customizations: Rename affects only the Home label. Default restores the app icon; a chosen stylized icon is a bundled asset and cannot become unavailable.
  10. OBD hardware is untested here. No ELM327 adapter is available on this machine; the native socket/protocol path compiles and the parser/economy UI is unit-tested, but a real-car soak test is still required.
  11. Weather depends on location, permission and network. Any failure hides the row; OBD ambient is always preferred when available.
  12. Uninstall/reinstall may drop the HOME role; Settings and the installer script can restore it.
  13. Jellyfin 12.2 API differences from older docs: keep the verified endpoints and auth header in JellyfinService; recently played is derived from played Audio tracks because album UserData is not updated by playback. Parse defensively.
  14. Media3/audio focus: the service pauses and keeps position on permanent focus loss and resumes only after a transient loss. The notification needs POST_NOTIFICATIONS on Android 13+; playback works when it is denied. Test on API 29/34/35 and a real tablet.
  15. Jellyfin layout density: 6 cards only when each is at least 160 px wide; otherwise 5. Labels are clipped to the card/button width so long album names cannot break the grid.
  16. Jellyfin genre grouping is heuristic: built-in families cover the known tags (including Cyrillic); unknown tags become standalone. The picker is capped at five families.
  17. High-resolution Jellyfin audio on a car head unit: direct play can sound slow or quiet on some DSP/audio HAL combinations. The staged streaming quality setting offers 320/256/192/128 kbps MP3 transcodes; verify which one fixes the user's unit. The choice applies to the next album queue.

7. Out of scope (for now)

  • Embedded third-party apps, overlay bars, split screen, kiosk/device-owner.
  • Global play/pause or now-playing metadata.
  • Wi-Fi/BLE/USB OBD adapters, DTC contents, RPM/coolant/voltage/MIL, fuel level/range.
  • Calls, climate, reverse camera.
  • An app drawer with folders/pages; a single searchable All Apps page is enough.
  • Play Store packaging/signing, remote git, CI.
  • Jellyfin Artists tab, artist/album browsing and search.
  • Jellyfin offline caching/downloads; queue persistence across process death.
  • Data-aware automatic streaming quality (Wi-Fi vs metered). A manual Direct/320/256/192/128 kbps choice is staged in the next build.
  • User-editable genre mapping, gapless playback and richer metadata.

8. Change log

  • 2026-10-31 (unreleased): adds a Jellyfin Streaming quality dropdown: Direct (default) or 320/256/192/128 kbps MP3 transcoding. The choice is persisted in SettingsRepository, read when the next album queue is built, and shown in the Jellyfin settings card. Staged for the next version; no APK or version bump has been made. 142 tests pass.
  • 2026-10-31: CarDash 1.2.1+21 fixes the brief Music-tile equalizer flash after pausing Jellyfin from the Home card. The Music equalizer now ignores CarDash's own stale system MediaSession while a Jellyfin pause propagates, and the Home card stays on the paused Jellyfin queue during that gap. 139 tests pass; release releases/cardash-android-1.2.1.apk.
  • 2026-10-31: CarDash 1.2.0+20 adds the full optional Jellyfin music source. The earlier Settings-only Quick Connect work now feeds a Home tile after Music, a dashboard (Recently played / Recently added / Random, six cards when they fit and five otherwise, grouped genre families, Random re-shuffle), and an album page with a top-aligned cover, track list, previous/next, play/pause and volume. Playback is Media3 1.11.1 in a foreground 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.
  • 2026-10-31: Added the optional Settings → Jellyfin integration. A switch and server URL drive the Quick Connect flow; the returned token is kept in secure storage and reused on later starts. The card shows connecting, waiting, connected, offline and error states. A rejected token disables the integration and clears the token; an unreachable server retries with 5–60 s backoff. 84 tests passed at this intermediate step.
  • 2026-10-29: Pivoted from an overlay/head-unit chrome design to a launcher-native single home page, no top/bottom bars, full-screen external apps. Implemented core UI, Kotlin bridges, settings, PIN and documentation. Verified initial home, app picker, All Apps, volume, themes and cold-start PIN on the API 35 AVD; then verified the release build and PIN flows on the API 29 AVD. Added hardware volume-key routing and PIN verification progress. Screenshots captured at 1280×800 and 1024×600.
  • 2026-10-29: Added home-button visibility switches, All Apps long-press pinning, a 16-button cap with an adaptive 2–6 column grid, pinned-app removal, and a foreground keep-screen-on flag. Verified 17 tests, 16-button layouts at 1280×800 and 1024×600, cap/removal/re-add, and 0.2.0 on the physical tablet.
  • 2026-10-29: Added six accent choices, battery/charging visibility, a two-minute fading hint anchored to process start, All apps last, user-app icons, a media-active equalizer, and a smaller All Apps search icon. Added the two-attempt ADB install rule. Verified 21 tests, accent/battery toggles, a user-app icon (F-Droid), hint expiry, and 0.3.0 on the emulator/tablet.
  • 2026-10-29: Removed the Home-role banner; added a once-only first-run tip. Replaced immediate removal with a pinned-app menu (Rename, Change icon, Remove), custom Home labels and up to 25 installed-app icon choices. Verified 23 tests and the full menu flow at 1280×800; 0.4.0 installed on the tablet.
  • 2026-10-29: Clarified icon policy: fixed/predefined buttons are always stylized; only pinned apps show their own icon by default. The Change icon grid now offers Default plus 29 bundled monochrome line icons. Verified 0.5.0 on the emulator and tablet.
  • 2026-10-29: Fixed the dark accent background. Added optional ELM327 OBD-II (speed, throttle, rolling MAF economy, ambient), location/Open-Meteo fallback, Car data Settings with adapter dropdown and weather toggle, and permission flows. Verified 30 tests, live weather on the emulator and 0.6.0 installation; OBD hardware remains a car test.
  • 2026-10-29: The circled volume ring is now play/pause (media-key resume, or opens the assigned Music app when nothing responds). Settings moved next to Wi-Fi in the status area and no longer consumes a grid slot. Verified 33 tests and 0.7.0 on the emulator and physical tablet.
  • 2026-10-29: Replaced the volume ring with song progress and added the scrolling now-playing title/artist. Added a MediaSession bridge and a Settings → Media notification-access grant; no notification content is read. Verified metadata, progress, scrolling/pause/resume with a live Chrome media session, 41 tests and 0.8.0 on emulator/tablet.
  • 2026-10-29: Added a default-on keep-awake switch, a black long-press night mode with a dim clock, and verified the 16-button layout at 1280×720. Verified the native window flag toggles off/on, night mode/card long-press isolation, 44 tests and 0.9.0 on emulator/tablet.
  • 2026-10-29: Added GPS speed fallback when no OBD adapter is connected and fine location is already allowed. No permission prompt; stationary values below 2 km/h render as 0. Verified 47 tests and 0.10.0 on emulator/tablet.
  • 2026-10-30: Added a non-blocking Gogs update checker and installed a 1.0.0 updater build on emulator/tablet. Published 1.0.1+13 as a version-only update test; 50 tests passed. The emulator later completed the in-app 1.0.0 → 1.0.1 download and package-installer flow.
  • 2026-10-30: Added the Bluetooth-off Open Bluetooth settings action and release/deployment runbook. Bumped to 1.0.2+14 and published it for in-app update.
  • 2026-10-30: Split Settings into OBD-II, Speed and Temperature cards, added independent show/hide switches plus OBD/GPS and OBD/weather source switches, and limited the long-press hint to ten minutes after a fresh install. Bumped to 1.0.3+15; 66 tests pass.
  • 2026-10-30: Added fixed-volume detection and a relative volume-step fallback for car units that ignore absolute setStreamVolume; fixed-volume sliders now explain that the vehicle amplifier owns the level. Bumped to 1.0.4+16.
  • 2026-10-31: Found the emulator's Settings crash was Impeller, not the app's layout. The 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.
  • 2026-10-31: Hardened the OBD-II/Bluetooth path after a freeze triggered by returning from Bluetooth settings. Bluetooth binder calls now run on a serialized native worker with timeouts; the picker filters to likely OBD-II adapters; connections stop after three failures with a Retry connection action; and resume reuses the cached bonded list instead of querying it every time. Bumped to 1.0.6+18.
  • 2026-10-30: Added a long-press volume diagnostics panel with all stream indices and the last target/actual write, plus CarDashVolume logcat output, to identify devices that ignore both absolute and relative volume writes. Bumped to 1.0.5+17; 66 tests pass.
  • 2026-10-30: Versioned the universal Android release as 1.0.0+12 and added releases/cardash-android-1.0.0.apk with checksum and release notes.
  • 2026-10-29: Centered the now-playing title across the whole media card instead of the right-hand control column. Verified 0.9.1 on emulator/tablet.
  • 2026-10-31: Enabling Show speed with GPS as the source (no OBD adapter selected or OBD speed off) now requests location permission and retries the GPS fallback; a denied request leaves the switch on and shows an explanation. The normal resume path still never prompts. 89 tests pass.