AGENTS.md 8.6 KB

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.

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, 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 and Security 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 ChangeNotifiers 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:

_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 ListViews inside Columns.
  • 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.
  • 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.