# Development environment with mise (the Nix flake, `nix develop`, is the other way in). # # Committed on purpose: it describes how to build the project, not someone's machine. Paths # go through {{env.HOME}} / JAVA_HOME, never hard-coded. For a local override (keystore path, # SDK elsewhere), create a mise.local.toml — that one stays ignored by git. # # mise install installs the JDK # mise run bootstrap installs the Android SDK if missing # mise run test unit tests # mise tasks lists everything # # Why the JDK is pinned: the machine's default JDK (26) is not supported by AGP and fails the # build with an unhelpful class-version error. [tools] java = "17" [env] ANDROID_HOME = "{{env.HOME}}/Android/Sdk" ANDROID_SDK_ROOT = "{{env.HOME}}/Android/Sdk" _.path = [ "{{env.HOME}}/Android/Sdk/platform-tools", "{{env.HOME}}/Android/Sdk/cmdline-tools/latest/bin", ] # SDK version app/build.gradle needs (compileSdk = 36). COMPILE_SDK = "36" BUILD_TOOLS = "36.0.0" # APK SIGNING secrets — nothing to do with the ABRP API key, which is entered in the app. # Set them outside the repository: uncomment in a mise.local.toml (ignored by git) and point # at a local keystore, NEVER at a file in the repository. The car build is signed by # `build-car` (flake.nix) instead. # SIGNING_KEYSTORE = "{{env.HOME}}/.keystores/signing.keystore" # SIGNING_STORE_PASSWORD = "…" # SIGNING_KEY_ALIAS = "platform" # SIGNING_KEY_PASSWORD = "…" # Gradle must run on the JDK mise just installed. Without -Dorg.gradle.java.home, Gradle may # reuse a daemon started by another JDK — typically the one bundled with VS Code's Java # extension, a JRE 21 without jlink: the build then fails in JdkImageTransform, with a message # that has nothing to do with the code. JAVA_HOME is set by [tools] above; $JAVA_HOME is # resolved by the shell at run time, so this stays portable between machines. # ── Emulator ───────────────────────────────────────────────────────────────────── # # A WORD on fidelity: the car runs AAOS 9 (API 28). Google publishes NO Automotive system # image below API 33 — checked with sdkmanager --list. So no emulator reproduces both the OS # and the car API of the MG4. Hence two profiles, each faithful on one axis: # # emulator-car API 33 Automotive — has a CarPropertyManager, wrong OS version. For the # service lifecycle and the car path. SAIC VENDOR properties (0x216xxxxx: # SOC, range) do not exist in the emulator: those reads fail, as expected. # emulator-screen API 28 tablet — right OS version and right screen (1920x720 @ 160 dpi, # 12.8" HD like the MG4), but NO car services. For the UI, layouts and # density. (The AOSP API 28 image also shares the car's platform key, so the # car build installs there as the system user.) # # None of this replaces a test on the car. # Screen 1920x720 px at 160 dpi, as on the car (app window 1778x720). Reference firmware: # SWI68-29958-1300R69. EMU_CAR_AVD = "evabrp-car" EMU_CAR_IMAGE = "system-images;android-33;android-automotive;x86_64" EMU_SCREEN_AVD = "evabrp-screen" EMU_SCREEN_IMAGE = "system-images;android-28;google_apis;x86_64" EMU_WIDTH = "1920" EMU_HEIGHT = "720" EMU_DENSITY = "160" [tasks.bootstrap] description = "Installs the Android SDK (platform + build-tools) if missing" run = """ set -e if [ -d "$ANDROID_HOME/platforms/android-$COMPILE_SDK" ]; then echo "SDK $COMPILE_SDK already present, nothing to do" exit 0 fi mkdir -p "$ANDROID_HOME" if [ ! -x "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" ]; then echo "Downloading the command-line tools…" tmp=$(mktemp -d) curl -sSL -o "$tmp/tools.zip" \ https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip unzip -q "$tmp/tools.zip" -d "$tmp" mkdir -p "$ANDROID_HOME/cmdline-tools" mv "$tmp/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest" rm -rf "$tmp" fi yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$ANDROID_HOME" --licenses > /dev/null "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$ANDROID_HOME" \ "platform-tools" "platforms;android-$COMPILE_SDK" "build-tools;$BUILD_TOOLS" echo "sdk.dir=$ANDROID_HOME" > local.properties echo "SDK installed, local.properties written" """ [tasks.build] description = "Builds the debug APK" run = './gradlew -Dorg.gradle.java.home=$JAVA_HOME assembleStableDebug' [tasks.test] description = "JVM unit tests" run = './gradlew -Dorg.gradle.java.home=$JAVA_HOME testStableDebugUnitTest testUnstableDebugUnitTest' [tasks.lint] description = "Android lint" run = './gradlew -Dorg.gradle.java.home=$JAVA_HOME lintStableDebug lintUnstableDebug' [tasks.permissions] description = "Permission drift check (the same as CI)" run = "bash .github/security/check-permissions.sh" [tasks.check] description = "What CI runs: permissions + lint + tests" depends = ["permissions", "lint", "test"] [tasks.install] description = "Installs the debug APK on the car connected over ADB" run = './gradlew -Dorg.gradle.java.home=$JAVA_HOME installStableDebug' [tasks.logs] description = "Follows the app's logs on the car" run = "adb logcat -s AbrpUploadService:V CarPropertyAdapter:V VhalProbe:V SecurePrefs:V OtaUpdater:V UpdateHook:V NilsMG4.Telemetry:V NilsMG4.Mongo:V NilsMG4.SteeringKey:V EVABRP.Boot:V EVABRP.Wifi:V" [tasks.release] description = "Release APKs of both channels (stable without OTA, unstable with OTA)" run = './gradlew -Dorg.gradle.java.home=$JAVA_HOME assembleStableRelease assembleUnstableRelease' [tasks.clean] run = './gradlew -Dorg.gradle.java.home=$JAVA_HOME clean' [tasks.emulator-setup] description = "Installs the emulator and system images and creates both AVDs" run = ''' set -e SDK="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" AVDM="$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager" if [ ! -e /dev/kvm ]; then echo "ERROR: /dev/kvm missing — no hardware acceleration, the emulator would be unusable." >&2 echo "On WSL2: enable nested virtualisation on the Windows side." >&2 exit 1 fi if ! id -nG | tr ' ' '\n' | grep -qx kvm; then echo "WARNING: user is not in the kvm group. Fix with:" >&2 echo " sudo usermod -aG kvm $USER # then log in again" >&2 fi yes | "$SDK" --sdk_root="$ANDROID_HOME" --licenses > /dev/null "$SDK" --sdk_root="$ANDROID_HOME" "emulator" "platform-tools" "$EMU_CAR_IMAGE" "$EMU_SCREEN_IMAGE" create_avd() { name="$1"; image="$2"; device="$3" if "$AVDM" list avd 2>/dev/null | grep -q "Name: $name"; then echo "AVD $name already exists" else echo no | "$AVDM" create avd -n "$name" -k "$image" -d "$device" --force fi cfg="$HOME/.android/avd/$name.avd/config.ini" # The MG4's screen. Without this the AVD keeps the generic profile's density and the # layouts do not look like what the driver sees. sed -i '/^hw.lcd/d;/^disk.dataPartition.size/d' "$cfg" { echo "hw.lcd.width=$EMU_WIDTH" echo "hw.lcd.height=$EMU_HEIGHT" echo "hw.lcd.density=$EMU_DENSITY" echo "disk.dataPartition.size=4G" } >> "$cfg" echo "AVD $name set to ${EMU_WIDTH}x${EMU_HEIGHT} @ ${EMU_DENSITY}dpi" } create_avd "$EMU_CAR_AVD" "$EMU_CAR_IMAGE" "automotive_1024p_landscape" create_avd "$EMU_SCREEN_AVD" "$EMU_SCREEN_IMAGE" "pixel_c" ''' # swiftshader_indirect, not "host": WSL2 exposes no real GPU (only Mesa llvmpipe). With # -gpu host the emulator dies at start-up on "Your GPU cannot be used for hardware # rendering", before any window appears — hence a tray icon and no visible emulator. # Software rendering is slower, but it starts. [tasks.emulator-car] description = "Starts the Automotive emulator (API 33 — has a CarPropertyManager)" run = ''' set -e "$ANDROID_HOME/emulator/emulator" -avd "$EMU_CAR_AVD" \ -gpu swiftshader_indirect -no-snapshot-save -no-boot-anim & "$ANDROID_HOME/platform-tools/adb" wait-for-device echo "Car emulator ready." ''' [tasks.emulator-screen] description = "Starts the MG4 screen emulator (API 28, 1920x720 @ 160dpi — no car services)" run = ''' set -e "$ANDROID_HOME/emulator/emulator" -avd "$EMU_SCREEN_AVD" \ -gpu swiftshader_indirect -no-snapshot-save -no-boot-anim & "$ANDROID_HOME/platform-tools/adb" wait-for-device echo "Screen emulator ready." ''' [tasks.emulator-stop] description = "Stops the running emulator" run = '"$ANDROID_HOME/platform-tools/adb" emu kill || true' [tasks.run] description = "Builds, installs and starts the APK on the emulator or the connected car" depends = ["build"] run = ''' set -e ADB="$ANDROID_HOME/platform-tools/adb" if [ -z "$($ADB devices | sed '1d' | grep -w device || true)" ]; then echo "No device connected. Start 'mise run emulator-car' or 'mise run emulator-screen' first," >&2 echo "or connect the car over ADB." >&2 exit 1 fi APK=$(ls -t app/build/outputs/apk/stable/debug/*.apk | head -1) echo "Installing $APK" $ADB install -r -g "$APK" $ADB shell am start -n "com.stinnesbeck.nilsmg4/com.evsuite.abrp.MainActivity" echo "Started. Logs: mise run logs" '''