Files
mg4/mise.toml
T
nilsandClaude Opus 5.5 d4bb502026 App 2.17.2: Wi-Fi auto-connect removed
Upstream kept the head unit on the driver's phone hotspot by re-joining
a saved network every few minutes. This car's head unit has its own
connection and the Wi-Fi is not used, so the feature goes entirely:
WifiAutoConnect, its call on the service tick, the preference key, and
the ACCESS_WIFI_STATE / CHANGE_WIFI_STATE permissions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
2026-10-18 20:34:05 +02:00

220 lines
9.0 KiB
TOML

# 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"
[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"
'''