# 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, a now-playing media card, and up to 16 buttons — **Navigation**, **Music**, **Radio**, **All apps**, plus any apps you pin from All Apps. The first three can be shown, hidden and assigned to installed apps; Settings opens from the gear next to Wi-Fi. 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, 1280×720 and 1024×600. Runs on a physical Android 17 HyperOS tablet. OBD-II UI/parser is implemented; the ELM327 socket path still needs a real-car test. Landscape only.
## Download - [Gogs release page](https://git.krasi.net/kkazakov/car-interface-android/releases) — published releases - [cardash-android-1.0.7.apk](releases/cardash-android-1.0.7.apk) — universal Android release - SHA-256 and build details: [releases/README.md](releases/README.md) Gogs limits release attachments to 32 MB, so the 54 MB universal APK is linked directly from the repository. ## Screenshots | Light | Dark | |---|---| |  |  | Also: [Home with top-right Settings](docs/screenshots/home-16.png) · [Home at 1024×600](docs/screenshots/home-16-1024.png) · [All apps](docs/screenshots/all-apps.png) · [app picker](docs/screenshots/app-picker.png) · [Settings · accent & buttons](docs/screenshots/settings-buttons.png) · [Settings · Media access](docs/screenshots/settings-media.png) · [Now playing](docs/screenshots/home-now-playing.png) · [1280×720](docs/screenshots/home-1280x720.png) · [GPS speed](docs/screenshots/home-gps-speed.png) · [Update dot](docs/screenshots/home-update-dot.png) · [Update dot close-up](docs/screenshots/home-update-dot-closeup.png) · [Mocked OBD · 1280×720](docs/screenshots/home-obd-mock-1280x720.png) · [Mocked OBD · 1280×800](docs/screenshots/home-obd-mock-1280x800.png) · [Night mode](docs/screenshots/home-night-mode.png) · [Blue accent](docs/screenshots/settings-blue-accent.png) · [user-app icon on Home](docs/screenshots/home-user-icon.png) · [pinned-app menu](docs/screenshots/home-app-menu.png) · [change-icon grid](docs/screenshots/home-app-icons.png) · [PIN lock](docs/screenshots/pin-lock.png) · [1024×600 layout](docs/screenshots/home-compact-1024.png) The two **Mocked OBD** images are documentation renders with a simulated adapter state (82 km/h, 34% throttle, 7.4 L/100 km, Outside 17°C). They are not live vehicle data. ## 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, with one switch to hide the battery level and charging bolt. - **OBD-II car data** — optional Bluetooth Classic ELM327 adapter. A dedicated **OBD-II** Settings card holds the adapter status, paired-device picker and Bluetooth actions. The live card shows speed, a throttle bar and rolling 5 km MAF economy, plus ambient temperature when the adapter provides it. - **Speed card with source choice** — Settings → **Speed** shows or hides the speed card and, while an adapter is selected, chooses OBD-II or GPS. GPS is the fallback when OBD speed is off or unavailable; it reads 0 km/h when stationary, and enabling speed with GPS as the source asks for location permission. - **Temperature row with source choice** — Settings → **Temperature** shows or hides the outside temperature and, while an adapter is selected, chooses OBD-II or GPS + weather. Enabling temperature asks for location permission for the Open-Meteo fallback; if denied, OBD temperature still works. The row reads `Outside, 7°C` for OBD or `Sofia, 7°C` for weather, and turns red below 3°C. - **Now-playing media card** — the circular ring shows song progress around a play/pause button. The song title and artist scroll along the bottom; paused audio stops the scroll and holds its position. When nothing is playing it reads **Nothing playing**. The volume slider, −/+, and Mute pill remain. - **Jellyfin (Settings only)** — optional music-server integration in Settings → Jellyfin. Turn it on, enter the server URL and pair it with a Quick Connect code; the saved token is reused on later starts. The card shows connected, offline and error states. A rejected token is cleared and asks for pairing again; a missing network pauses the connection and retries automatically. Nothing on Home uses it yet. - **Update check** — on startup, CarDash queries the public Gogs release list without blocking the dashboard. Settings → Updates shows the current version, a Check button, and an Update action that downloads the APK and opens Android’s installer. Android still requires confirmation and a one-time “install unknown apps” allowance; silent installation is not attempted. A small yellow dot appears on the Settings gear whenever an update is available. - **Now-playing access** — Settings → Media grants Android notification access, used only to read the active media session’s title, duration and position. CarDash never reads or stores notification content. - **Configurable home buttons** — switches in Settings show or hide Navigation, Music and Radio. **All apps** is always the last button; **Settings** now sits next to Wi-Fi in the top-right status area and is no longer a grid tile. - **Pin any app from All Apps** — long-press an app to add it to Home. The home page holds up to **16 buttons including All apps**; the grid shrinks as it fills and stays anchored to the bottom edge. - **Edit a pinned app** — long-press it on Home for Rename, Change icon and Remove. Pinned apps show their own icon by default. Rename changes only the Home label; Change icon offers **Default** plus 29 monochrome line icons (white on dark, black on light); Remove takes effect without confirmation. Re-add the app from All Apps. - **One-time launcher tip** — the first run shows a short notification that the default-launcher setting lives in Settings. It is not shown again. - **All apps** — searchable grid of every launchable app, launched full-screen. - **Media-aware Music tile** — while media audio is active and the Music shortcut is assigned, the stylized music icon shows a small animated equalizer. Android exposes media-active state, not the playing package, to a normal launcher; see the limitation below. - **Six accent colors** — Green, Blue, Amber, Rose, Violet or Teal, applied across the whole interface. - **Prevent CarDash from sleeping** — a Settings switch, on by default. Turn it off to use the system display timeout. - **Night mode** — long-press an empty part of the Home background to go black with a very dim centered clock; tap anywhere to return. Long-presses on buttons and cards are unaffected. - **A short onboarding hint** — the long-press hint is visible only during the first ten minutes after a fresh installation, then stays gone. Upgraded installs do not see it again. - **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 overlay, kiosk or root. - **Android 10+**, landscape-only, tablet-first (verified at 1280×800). - No accounts, no telemetry, no T&C screen. Network access is used only for the optional Open-Meteo temperature fallback, the Jellyfin server you configure, and the public release check. ## 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 - Optional: a Bluetooth Classic ELM327 adapter already paired in Android - Optional: location access for the Open-Meteo temperature fallback 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. Android will not let an app force itself into the home role silently. CarDash no longer keeps a home-role banner on the dashboard; the first run shows a one-time tip pointing to this setting. ## 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 → Accent | Pick Green, Blue, Amber, Rose, Violet or Teal | | Settings → Battery & charging | Show or hide battery level and charging bolt | | Settings → Prevent CarDash from sleeping | Keep the display on, or use the system timeout | | Long-press an empty part of Home | Enter black night mode with a dim clock | | Tap the night screen | Return to the dashboard | | Settings → Home buttons switches | Show or hide Navigation, Music, Radio | | Long-press an app in All Apps | Add it to Home (up to 16 buttons) | | Long-press an added app on Home | Rename, Change icon or Remove | | Settings → OBD-II | Pick a paired ELM327 or None; open Bluetooth settings | | Settings → Speed | Show or hide the speed card; choose OBD-II or GPS source | | Settings → Temperature | Show or hide the temperature row; choose OBD-II or GPS + weather | | Speed card | OBD-II when connected, otherwise GPS; 0 km/h when stopped | | Temperature row | OBD ambient, or location weather when OBD is off/unavailable | | Settings | Theme, accent, media access, battery, home buttons, OBD/speed/temperature, default-home role, PIN | | All apps | Search every launchable app; tap to open, hold to pin | | Music tile while media is active | Shows the selected app icon + equalizer | | Circular ring | Song progress; tap the centre to play/pause | | Song title at the card bottom | Scrolls only while playing; holds when paused | | Screen | Stays awake while CarDash is in front | | Mute pill | Toggle media mute | | Slider or − / + | Set media volume | | Hardware volume keys | Adjust the dashboard's media volume; the slider follows | | Long-press **Media volume** | Show/hide raw volume diagnostics while troubleshooting a car unit | 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. - Accent: Green, Blue, Amber, Rose, Violet or Teal. - Battery & charging: one switch hides both the level and charging bolt. - Prevent CarDash from sleeping: keep the display on; on by default. - Auto night window (default dark from 19:00, light from 07:00). - Home buttons: show/hide Navigation, Music, Radio; live count out of 16. - Assign the app for each slot; All apps is fixed and last. - Settings is always available from the gear next to Wi-Fi. - Home role status and shortcut to the system prompt. **Media** - Notification access for the now-playing title, duration and progress. - Read-only: CarDash does not read notification content. - Without access the ring stays empty and the card shows a generic Playing / Nothing playing state. **Jellyfin** - One switch enables the optional integration. While it is off, CarDash never contacts a Jellyfin server. - When enabled and not connected, enter the server URL (`https://host`, `host:port` or a path such as `https://host/jellyfin`) and press **Connect**. - Without a saved pairing, the card shows a **Quick Connect** code. Open Jellyfin, choose Quick Connect, enter the code and approve it there. - A saved pairing is reused on later starts; the card shows the server name and the signed-in user. **Remove pairing** clears it and turns the switch off. - A rejected token turns the switch off, clears the token and offers **Pair again**. - Without a network the card shows **No internet — retrying** and retries with backoff until the server is reachable again. - Nothing on Home uses the connection yet; the active state is exposed for a later feature. **OBD-II** - Adapter dropdown: None plus paired Bluetooth Classic devices that look like OBD-II adapters. **Show all paired devices** reveals phones, headsets and other bonded devices for unusual adapters. - Connection status and a Find/Allow action for Bluetooth permission. - If Bluetooth is off: **Open Bluetooth settings** to turn it on, then return. CarDash checks the adapter state and reuses the cached device list instead of querying the Bluetooth stack on every resume. - Connection attempts are bounded. After three failures the card shows **Retry connection** instead of retrying forever. - Read-only note: CarDash never writes to the ECU. Adapter settings only; speed and temperature choices live in their own cards. **Speed** - One switch shows or hides the speed card on Home. - While an OBD-II adapter is selected, **Use OBD-II for speed** selects the source. Off means GPS is used. - Without OBD (or with OBD speed off), the GPS fallback is used. Enabling speed or switching the source to GPS asks for location permission; if denied, the switch stays on and a message explains why. It reads 0 km/h when stationary. **Temperature** - One switch shows or hides the outside-temperature row below the clock. - While an OBD-II adapter is selected, **Use OBD-II for temperature** selects the source. Off means GPS + the Open-Meteo weather provider is used. - Enabling temperature or choosing the weather source asks for location permission and refreshes every 15 minutes. If permission is denied, OBD temperature still works when available. - The row turns red below 3°C and hides when no fresh value exists. **Updates** - Non-blocking check against the public Gogs releases. - Shows current/latest version; Update downloads the APK and opens Android’s installer. - Android confirms the install and, on first use, asks to allow CarDash as an unknown-app source. No silent installation. **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, OBD/weather controllers apps/ All Apps, app-picker sheet settings/ General, OBD-II, Speed, Temperature + Security lock/ PIN pad, cold-start lock screen android/... MainActivity, LauncherBridge, SystemBridge, TelemetryBridge, ObdService tool/ emulator, install, check scripts docs/ implementation plan, design notes ``` - Flutter owns all UI. Kotlin exposes package enumeration/launching, the HOME role, battery/Wi-Fi, media volume, active media metadata, Bluetooth OBD and one-shot location over method channels. - The OBD layer is read-only, Bluetooth Classic SPP, bonded devices only. A worker thread serializes ELM327 requests; Flutter polls the latest snapshot. - `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. - Full OBD/weather design and hardware notes: [docs/OBD_TEMPERATURE.md](docs/OBD_TEMPERATURE.md). ## Testing ```bash flutter analyze flutter test ./tool/check.sh ``` The 89 tests cover PIN hashing/persistence, settings persistence, theme/accent, dark accent tinting, battery visibility, first-run state, the ten-minute onboarding hint, slot visibility, pinned apps, custom labels/icons, the 16-button cap at 1280×800, 1280×720 and 1024×600, the dense shortcut grid, the equalizer animation, OBD economy, the OBD card, media-key playback, media metadata/progress, the marquee, the play/pause ring, fixed-volume handling, volume-write diagnostics, the keep-awake preference, the night overlay, GPS speed fallback and its permission prompt, speed/temperature source persistence, weather fallback, the update version/availability logic and the Settings update dot. Native package enumeration, the HOME role, volume, app launching and PIN flows were verified on API 35 and API 29 emulators. Location permission, reverse geocoding and the Open-Meteo fallback were verified live on the API 35 emulator. Now-playing title, artist, progress and pause/resume were verified against a live Chrome MediaSession. The OBD hardware path compiles and is covered by the parser/economy design, but no ELM327 adapter is available on this machine; socket behavior still needs a car test. The 1.0.7 universal APK is built from this tree and published; devices on 1.0.6 can pick it up through Settings → Updates. The emulator has verified the in-app update path. 1.0.7 moves the Impeller opt-out into the manifest's `