# 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 media-volume card (ring, slider, −/+, mute). - Up to **16 home buttons**: Navigation, Music, Radio, Settings, All apps, plus apps pinned from All Apps. - Navigation/Music/Radio are **app slots** with show/hide switches. They can be assigned, changed and cleared. Settings and All apps are always present. - Pinned apps are added by long-pressing an app in All Apps and removed by long-pressing the added button on Home. Settings and All apps are always last. User-installed apps show their own icon; system/unassigned slots keep generic. - 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 Music tile shows the selected app icon with an animated equalizer while media audio is active. - The long-press hint fades away two minutes after app start. - 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; Android 10 minimum. - 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; Settings and All apps are 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 Settings and All apps | Keeps the single page legible at tablet resolutions; enforced in `LauncherController`. | | Icons | User-installed apps use their own icon; system/unassigned slots stay generic | Identification without losing the calm fixed-button language. | | 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 | Music tile shows the selected app icon + animated bars while media is active | Compact visual feedback without adding a media control surface. | | Hint lifetime | Two minutes from process start, then fade out | Avoids permanent onboarding chrome; survives widget rebuilds via `AppClock`. | | Screen | `FLAG_KEEP_SCREEN_ON` while foreground | The dashboard is the car display; no wake-lock permission needed. | | 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 polling AllAppsScreen / AppPickerSheet SettingsScreen -> General / Security PinSetupScreen / LockScreen -> LockController -> PinService Android (Kotlin) MainActivity -> keep-screen-on, volume keys -> LauncherBridge (packages, icons, system flag, role) -> SystemBridge (battery, Wi-Fi, volume, media activity) ``` - `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. - No INTERNET permission, no weather, 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 | ## 5. Verification checklist - [x] `flutter analyze` clean. - [x] Unit/widget tests pass (21 tests: PIN, repository, theme/accent, battery visibility, slot visibility, pinned apps, 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 and 1024×600; the grid uses 2–6 columns. - [x] `FLAG_KEEP_SCREEN_ON` is present on the dashboard window. - [x] Six accent choices change the Material theme immediately and persist. - [x] Battery switch hides both the battery level and charging bolt. - [x] Settings/All apps are the last two Home buttons. - [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] Release 0.3.0 installed on the emulator and physical Android 17 tablet. - [x] Screenshots in `docs/screenshots/`; README/AGENTS/plan 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. No global play/pause is in scope. 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 generic system-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 attribution**: the equalizer follows `AudioManager.isMusicActive`, not a specific package. Exact attribution would require notification access. 9. **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. - Weather, Bluetooth, 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, Settings/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.