# CarDash A calm, configurable Android car dashboard launcher, built with Flutter. CarDash replaces the tablet home screen with one landscape page: a large clock, battery and Wi-Fi status, the main media-volume control, and five shortcuts — **Navigation**, **Music**, **Radio**, **Settings** and **All apps**. The first three are configurable; a long press picks a different installed app. External apps open full-screen using the normal Android launcher mechanism. No overlays, no root, no kiosk setup.

Verified on Android 15 and Android 10 tablet emulators at 1280×800. Landscape only.

## Screenshots | Light | Dark | |---|---| | ![CarDash light home](docs/screenshots/home-light.png) | ![CarDash dark home](docs/screenshots/home-dark.png) | Also: [All apps](docs/screenshots/all-apps.png) · [app picker](docs/screenshots/app-picker.png) · [Settings](docs/screenshots/settings.png) · [PIN lock](docs/screenshots/pin-lock.png) · [1024×600 layout](docs/screenshots/home-compact-1024.png) ## Features - **One home screen** — no pages, no top bar, no bottom dock; information and controls live on the home page. - **Large clock and date** with the device's 12/24-hour preference. - **Battery, charging and Wi-Fi** status. - **Media volume** — ring, slider, step and mute controls. While CarDash is in front, the hardware volume keys are routed to the same media stream, so the dashboard and the keys always agree. This is the phone's media stream, not a private volume. - **Configurable shortcuts** for Navigation, Music and Radio. Generic icons on the dashboard; real app icons in the picker and All Apps. - **All apps** — searchable grid of every launchable app, launched full-screen. - **Light / Dark / Automatic** theme. Automatic uses a fixed local-time window (default 19:00–07:00), editable in Settings. - **Optional PIN** — four digits, stored as a salted PBKDF2 hash in Keystore-backed storage. It is requested **only when the launcher process cold-starts**, never when returning from an app. - **Standard Android home role** — no special overlays or permissions. - **Android 10+**, landscape-only, tablet-first (verified at 1280×800). - No network permission, no accounts, no telemetry, no T&C screen. ## Requirements - Flutter 3.47.8 / Dart 3.13.7 (or a compatible newer stable release) - Android SDK 36, build-tools 36 - **JDK 21** for Gradle (the machine default is JDK 27, which is not validated) - Android 10 (API 29) or newer device/emulator - A landscape tablet or an emulator; the app locks itself to landscape - The prepared tablet AVDs use the **host GPU**; a working GPU driver is recommended The Android application ID is `com.wasp.cardash_launcher`. Android package segments cannot contain hyphens, so the requested `com.wasp.cardash-launcher` spelling is normalized to an underscore. ```bash export JAVA_HOME=/usr/lib/jvm/java-21-openjdk export ANDROID_HOME=/opt/android-sdk export PATH="$ANDROID_HOME/platform-tools:$PATH" ``` ## Quick start ```bash flutter pub get ./tool/check.sh # Boot the prepared 10.1" 1280×800 Android 15 tablet (headless) ./tool/emulator.sh 35 # Build, install, and make CarDash the HOME app ./tool/install.sh debug ``` If the emulator does not yet exist on another machine, create it with: ```bash sdkmanager "system-images;android-35;google_apis;x86_64" avdmanager create avd -n cardash_tablet_api35 \ -k "system-images;android-35;google_apis;x86_64" \ -d "10.1in WXGA (Tablet)" ``` The repository also has an Android 10 AVD (`cardash_tablet_api29`) for minimum-version checks. ### Manual install ```bash flutter build apk --release adb install -r build/app/outputs/flutter-apk/app-release.apk adb shell cmd role add-role-holder android.app.role.HOME com.wasp.cardash_launcher adb shell am start -a android.intent.action.MAIN -c android.intent.category.HOME ``` On a real tablet, use **Settings → General → Home app → Set as default** and accept the system prompt. The in-app "Make CarDash your home app" card does the same thing. Android will not let an app force itself into the home role silently. ## Using CarDash | Action | Result | |---|---| | Tap Navigation / Music / Radio | Open the assigned app full-screen | | Long-press Navigation / Music / Radio | Pick a different installed app | | Tap an unassigned shortcut | Open the app picker | | Settings | Theme, home shortcuts, default-home role, PIN | | All apps | Search every launchable app; tap to open | | Volume ring / mute | Toggle media mute | | Slider or − / + | Set media volume | | Hardware volume keys | Adjust the dashboard's media volume; the ring follows | The app hides the Android status and navigation bars while CarDash is in the foreground. Swipe from an edge to reveal them temporarily; they hide again when the app resumes. ## Settings **General** - Light / Dark / Auto theme. - Auto night window (default dark from 19:00, light from 07:00). - The three home shortcuts. - Home role status and shortcut to the system prompt. **Security** - Set, change or remove a four-digit PIN. - The PIN is requested only on a cold start. It does not re-lock when switching apps, after screen-off, or after returning from Navigation. This is the agreed behavior, not an oversight. - The PIN protects the launcher UI. It is **not** the Android lock screen; set a device screen lock separately for device-level security. - There is no PIN recovery. Resetting app data removes it. ## Architecture ``` lib/ core/ design tokens, theme controller, icons, shared widgets data/ models, platform services, SettingsRepository (single source of truth) features/ home/ home page, shortcut grid, volume card, controllers apps/ All Apps, app-picker sheet settings/ General + Security lock/ PIN pad, cold-start lock screen android/... MainActivity, LauncherBridge, SystemBridge (Kotlin) tool/ emulator, install, check scripts docs/ implementation plan, design notes ``` - Flutter owns all UI. Kotlin only exposes package enumeration/launching, the HOME role, battery/Wi-Fi and media volume over two `MethodChannel`s. - `SettingsRepository` is the only persistence path. Controllers are `ChangeNotifier`s injected with `provider`. - External apps are separate activities; Android has no public API to embed them inside the launcher. This app deliberately accepts full-screen launching for maximum compatibility. ## Testing ```bash flutter analyze flutter test ./tool/check.sh ``` Current tests cover PIN hashing/persistence, settings persistence, theme resolution and the shortcut tile widget. Native package enumeration, the HOME role, volume, app launching and PIN flows were verified on both API 35 and API 29 tablet emulators, including the release APK. ## Troubleshooting the emulator The prepared AVDs use the host GPU (`-gpu host`), which is the verified setup on this machine. Software rendering with SwiftShader segfaulted on the API 35 image. If your machine has no usable GPU, you can try: ```bash CARDASH_GPU=swiftshader_indirect ./tool/emulator.sh 35 ``` The prepared AVD resolution is 1280×800. If the emulator is already running, `./tool/emulator.sh stop` shuts it down before starting a different one. The Android 15 AVD is left running headlessly after development; use `./tool/emulator.sh 35 window` for a visible window. ## Compatibility and limitations - **minSdk 29 (Android 10)**; compile/targetSdk 36. Nothing in the design requires a newer Android release. - **Landscape only** (`sensorLandscape`). The home page is designed for 1280×800 and remains usable at 1024×600. - **No embedded third-party app UI.** Navigation/Music/Radio are launched in their own full-screen windows. - **No global play/pause control.** Volume is on the home screen; playback buttons live in the media app itself. - **No weather, Bluetooth, calls or climate** in this version. - **`QUERY_ALL_PACKAGES`** is used to list all launchable apps. It is reasonable for a launcher but requires a Play policy declaration if the app is ever distributed; this is a local/sideload build for now. - **No internet permission.** The app does not fetch maps, weather or updates. - The launcher cannot replace Android's lock screen. The PIN does not protect a running session, by design. ## Design The visual language is inspired by the [TrailCurrent Godwit](https://trailcurrent.com/godwit.html) head unit (MIT): warm neutral surfaces, a single deep-green instrument accent, large numerals and quiet line icons. It is not a copy of that UI. - Typeface: **Manrope** (SIL Open Font License), bundled offline. - Icons: **Lucide** (ISC), bundled as SVGs. - Palette and tokens: `lib/core/theme/app_colors.dart`. ## Roadmap - Optional compact now-playing control beside the volume card. - Configurable home layout or additional shortcut slots. - Release signing configuration. - Real-device soak test on an Android 10 car tablet. ## License This repository is in local development and has not been pushed or published. A project license has not been chosen yet. Third-party asset licenses: Manrope (OFL) and Lucide (ISC). The design is inspired by TrailCurrent Godwit (MIT).