# CarDash — Agent Guide ## 1. Project Overview CarDash is a Flutter/Dart Android home-screen launcher for a car tablet. It has one configurable home page (clock, battery/Wi-Fi, media volume, up to 16 home buttons), launches other apps full-screen, keeps the screen awake by default (configurable), offers a long-press night screen, light/dark/automatic themes, and an optional four-digit PIN requested only on cold start. Stack: Flutter 3.47.8 / Dart 3.13.7, Android minSdk 29 (Android 10), compile/targetSdk 36, Kotlin bridges, AGP 9.1.0, Gradle 9.3.1, JDK 21. Package `com.wasp.cardash_launcher`. Landscape-only, tablet-first (1280×800). You are the implementation agent: keep the app buildable, tested, documented, committed, and pushed. Always call this project **CarDash**. ## 2. Commands Run from the repository root. ```bash flutter pub get flutter analyze flutter test flutter test test/pin_service_test.dart export JAVA_HOME=/usr/lib/jvm/java-21-openjdk flutter build apk --debug flutter build apk --release ./tool/check.sh # analyze + tests ./tool/emulator.sh 35 # boot the Android 15 tablet (headless) ./tool/emulator.sh 29 # boot the Android 10 tablet ./tool/install.sh debug # build with JDK 21, install, set HOME role ./tool/install.sh release ``` ADB lives at `/opt/android-sdk/platform-tools/adb`; the SDK is at `/opt/android-sdk`. AVDs: `cardash_tablet_api35`, `cardash_tablet_api29` (1280×800, landscape). ## 3. Architecture - `lib/core/` — theme tokens (`AppColors`, `AccentChoice`), `AppTheme`, `ThemeController`, `DisplayController` (keep-awake), `AppClock`, reusable widgets. - `lib/data/models/` — `AppEntry`, `SlotType`, `SlotAssignment`, `SystemStatus`, `VolumeInfo`, `MediaPlayback`, OBD/weather models. - `lib/data/services/` — `NativeBridge`, `TelemetryBridge`, `WeatherService`, `UpdateService`, `PinService`, secure-store adapter. - `lib/data/repositories/` — `SettingsRepository`, the SSOT for theme, night window, slot assignments, visibility flags and pinned apps. - `lib/features/home/` — home screen, `LauncherController`, `SystemController`, `ObdController`, `WeatherController`, `GpsSpeedController`, equalizer, night overlay. - `lib/features/apps/` — All Apps screen and app-picker sheet. - `lib/features/settings/` — General, Media, Car data, Security and Updates pages. - `lib/features/lock/` — PIN pad, lock screen, cold-start `LockController`. - `android/app/src/main/kotlin/com/wasp/cardash_launcher/` — `MainActivity`, `LauncherBridge`, `SystemBridge`, `TelemetryBridge`, `ObdService` (Classic SPP ELM327), `MediaBridge`, `CardashNotificationListener`; `SystemBridge` also dispatches media keys and applies the keep-awake flag. - `tool/` — emulator/install/check scripts. `docs/` — implementation plan and design notes. Rules: - Persisted configuration goes through `SettingsRepository`; never add ad-hoc SharedPreferences keys in widgets. - Controllers are `ChangeNotifier`s consumed with `provider`; no business logic in widgets. - Android behavior lives behind `LauncherPlatform` so controllers stay testable. - External apps are launched with an Intent; they are **not** embedded in the launcher. - The PIN is app-local and checked only when the process starts. Persistence pattern: ```dart _slots[slot] = app.toAssignment(); await _settings.setSlot(slot, app.toAssignment()); notifyListeners(); ``` ## 4. Conventions - Use `context.colors` / `AppTheme` tokens, not hard-coded colors in widgets. - All UI copy is sentence case (except the CarDash name). Keep copy short and functional. - New home buttons: extend `SlotType` for fixed slots or use `LauncherController` pinned-app APIs; fixed slots stay stylized, pinned apps default to their own icon, and icon edits use `style_icon_choices.dart`. - New native methods: add them to `LauncherPlatform`, `NativeBridge`, the relevant Kotlin bridge, and the test fake if used. - Tests: fake `LauncherPlatform`/`SecureStore`; `SharedPreferences.setMockInitialValues({})`; widget tests wrap in `MaterialApp(theme: AppTheme.light())`. Test files live in `test/` and end with `_test.dart`. - Run `dart format lib test` before committing. Conventional Commits (`feat:`, `fix:`, `docs:`, `chore:`). After every commit, push to `origin` (`git push origin main`); never leave commits unpushed and never force-push. - Update this file, `README.md`, and `docs/IMPLEMENTATION_PLAN.md` in the same commit as any behavior change. - Keep the UI fast: no large blur layers, no continuously running decorative animation, prefer implicit animations under ~250 ms. ## 5. Boundaries - NEVER embed a third-party app's UI, add an overlay/`SYSTEM_ALERT_WINDOW`, kiosk, root, or split-screen feature. The agreed product launches apps full-screen. - NEVER add a terms-and-conditions screen, blocking first-run modal, or mandatory onboarding. - NEVER change the application ID, HOME intent filter, landscape lock, or minSdk without human confirmation. - NEVER re-lock on resume, screen-off, or route changes; no "Lock now" action. PIN is requested only on cold start. - NEVER store, log, or display a plaintext PIN. - INTERNET, location and Bluetooth permissions are approved only for OBD, Open-Meteo weather and the GPS speed fallback; notification access only for MediaSession title/progress; no notification content, scanning or background location. - NEVER commit `android/local.properties`, keystores, build outputs, APKs, or secrets. - REQUIRE human confirmation before changing signing configuration, package visibility permissions, or the repository remote. ## 6. Gotchas - The machine default Java is 27; Gradle/AGP for this project is validated with **JDK 21**. Always export `JAVA_HOME=/usr/lib/jvm/java-21-openjdk` for builds. - Only x86_64 system images run here; the pre-installed arm64 image cannot boot. The download script/images are already in `/opt/android-sdk`. - `QUERY_ALL_PACKAGES` is required to list every app. It is acceptable for a launcher but is not a Play-Store-distributable choice as-is; document it. - The Flutter template uses AGP 9.1 built-in Kotlin support. Do not add the Kotlin Gradle plugin unless the build explicitly requires it. - Immersive mode is reapplied on every `resumed` lifecycle event; Android may restore system bars after dialogs or role prompts. - The HOME role must be granted for the dashboard to replace the tablet home screen. On the emulator use `cmd role add-role-holder`; the Settings row uses `RoleManager`. - There is no Home-role banner. The one-time first-run tip uses `LauncherController.homeRoleHintShown`; do not show it again. - Screen-space is deliberately finite: test the 16-button grid at 1280×800, 1280×720 and 1024×600. The grid includes All apps; Settings sits in `StatusCluster` and does not consume a slot. Avoid unbounded `ListView`s inside `Column`s. - `tool/emulator.sh` starts a headless AVD with the **host GPU**; SwiftShader segfaulted on this machine's API 35 image. Use `CARDASH_GPU=swiftshader_indirect` only as a fallback. Use `adb exec-out screencap -p` for screenshots; there may be multiple `adb` binaries on PATH, so use the absolute one. - While CarDash is foreground, `MainActivity` consumes hardware volume keys and adjusts `STREAM_MUSIC` so they match the dashboard. Do not remove that override without providing another sync path; otherwise Android controls the ring stream when no media is playing. - `FLAG_KEEP_SCREEN_ON` is controlled by `DisplayController` from `display.keepScreenOn` (default on) through `SystemBridge`; do not hard-code it in `MainActivity` or move it to a wake lock. - The Music equalizer follows `AudioManager.isMusicActive`. Now-playing title/progress use the notification-listener component and active MediaSession; never read or store notification content. - Night mode is a full-screen black overlay with a dim clock. Long-press only on empty background; tap anywhere to dismiss. It does not change brightness or add a wake lock. - GPS speed is a fallback only when no OBD adapter is connected. It starts only with already-granted fine location and GPS; never prompt from that path, and clamp stationary noise below 2 km/h to 0. - Updates use a non-blocking public Gogs release check. The Update action downloads the APK to cache and opens Android’s package installer; never install silently. REQUEST_INSTALL_PACKAGES + one-time unknown-apps permission is required. - ADB installs: always try at most twice per connected device. If both attempts fail, stop silently rather than looping or switching install methods. - OBD is Bluetooth Classic SPP, bonded-only and read-only, and still needs a real-car test; weather uses Open-Meteo and one-shot location and stores no coordinates. - The circular ring is song progress; its centre is play/pause and the Mute pill is separate. Playback uses Android media keys; title/progress need the user-granted notification access, otherwise the card falls back to Playing / Nothing playing. - Reinstalling the app can clear its HOME role; re-run `tool/install.sh` or use Settings > Home app.