# CarDash implementation plan _Last updated: 2026-10-29_ ## 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, 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. 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 and shrinks its tiles as the count grows. - 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. - The stylized Music tile shows an animated equalizer while media audio is active. - Settings → Media grants notification access for the active MediaSession title, duration and position. CarDash does not read notification content. - The long-press hint fades away two minutes after app start. - 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. - Optional location weather fallback when OBD has no ambient value; red below 3°C; no row when unavailable. - 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. | | 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 | Two minutes from process start, then fade out | Avoids permanent onboarding chrome; survives widget rebuilds via `AppClock`. | | 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, Settings toggle | No API key/account; hide the row on any failure. | | Temperature priority | OBD ambient, else location weather | OBD wins whenever a fresh value exists. | | 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 HomeScreen -> NightModeOverlay on empty-background long press AllAppsScreen / AppPickerSheet SettingsScreen -> General / Car data / Security 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) ``` - `SettingsRepository` is the only persistence path for theme, night window, slot visibility/assignments and pinned apps. `flutter_secure_storage` holds the salted PIN hash. - 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. - Network access is limited to the optional Open-Meteo weather fallback; 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 | ## 5. Verification checklist - [x] `flutter analyze` clean. - [x] Unit/widget tests pass (23 tests: PIN, repository, theme/accent, battery, first-run state, slot visibility, pinned apps, custom labels/icons, cap, dense grid, equalizer). - [x] Debug APK builds with JDK 21. - [x] Installs on API 35 1280×800 tablet. - [x] HOME role granted; Home opens CarDash. - [x] Immersive full-screen; no Android bars. - [x] Real app list and icons in All Apps. - [x] App picker assign / long-press change / clear, with persistence across restart. - [x] Launch Navigation/Music/Radio full-screen and return Home (verified with Maps and YouTube Music). - [x] Volume slider, mute and hardware-key synchronization (keys are routed to `STREAM_MUSIC` while the dashboard is foreground). - [x] Light theme, dark theme and automatic switch. - [x] PIN set/change/remove; cold start shows lock; wrong PIN; no re-lock after returning from an app. - [x] Verify 1024×600 compact layout (`adb shell wm size 1024x600`). - [x] Boot and verify on Android 10 (API 29) AVD, including PIN and release APK. - [x] Release APK on both AVDs (54 MB universal APK). - [x] Navigation/Music/Radio switches hide/show buttons, persist, and preserve assignments. - [x] All Apps long-press pins apps; the 16-button cap rejects a 17th button. - [x] Long-press on an added app removes it; it can be re-added from All Apps. - [x] 16 buttons fit at 1280×800, 1280×720 and 1024×600; the grid uses 2–6 columns. - [x] `FLAG_KEEP_SCREEN_ON` follows the keep-awake switch: present by default, cleared when off and restored when on. - [x] Long-press on empty Home enters night mode; card/button long-presses are unaffected; tapping wakes. - [x] Six accent choices change the Material theme immediately and persist. - [x] Battery switch hides both the battery level and charging bolt. - [x] Settings is next to Wi-Fi; All apps is the last grid button. - [x] A user-installed app (F-Droid) shows its own icon; system apps stay generic. - [x] Music equalizer widget animates; native media-active polling is wired. - [x] Home hint is gone after two minutes from process start. - [x] All Apps search icon is visibly smaller. - [x] No Home-role banner; the first-run tip appears once, then never again. - [x] Long-press pinned app shows Rename, Change icon and Remove. - [x] Rename updates the Home label; Change icon persists a stylized icon; Remove is immediate. - [x] Fixed buttons stay stylized; pinned apps show their own icons by default. - [x] Change icon offers Default plus 29 monochrome icons and applies the chosen icon. - [x] Accent-tinted dark background changes from green to blue when Blue is chosen. - [x] OBD Settings device dropdown and Bluetooth permission action are present. - [x] OBD economy/card tests pass; native ELM327 path compiles. - [x] Location permission, reverse geocoding and Open-Meteo fallback verified live (Ukiah, 17°C). - [x] Temperature row renders below the date and follows OBD/weather priority. - [x] Circled ring shows song progress; 44 tests include media keys, metadata, progress, the marquee, the ring, keep-awake and night mode. - [x] Scrolling title/artist verified with a live Chrome MediaSession; pause holds both the ring and scroll position, resume continues. - [x] Settings → Media grants notification access and shows Granted. - [x] Settings gear is next to Wi-Fi; the grid ends with All apps. - [x] Release 0.9.1 installed on the emulator and physical Android 17 tablet. - [x] 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 equalizer follows `AudioManager.isMusicActive`; title/progress query the active MediaSession through the notification-listener component. Notification access 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. ## 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. ## 8. Change log - 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: Centered the now-playing title across the whole media card instead of the right-hand control column. Verified 0.9.1 on emulator/tablet.