Files
mg4/app/build.gradle
T
nilsandClaude Opus 5.5 69d284f81b App 2.18.0: install from a USB stick
Settings -> App -> "Install from USB stick" (car build only). It looks
in the top folder of every mounted volume (found from the app's own
folder on it, since the head unit has no file picker) for
NilsMG4-unstable-<version>.apk, takes the newest one newer than the
installed version, checks it against its .sha256 sidecar and the
package name, and installs it with the same PackageInstaller session
as the over-the-air update. The stable build gets a stub and no button.

Tested on the emulator: 2.18.0 installed a 2.18.1 from the storage's
top folder without a confirmation, as the system user, so the OTA
install step should not need one either.

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

149 lines
6.1 KiB
Groovy

plugins {
alias(libs.plugins.android.application)
}
android {
namespace 'com.evsuite.abrp'
compileSdk {
version = release(36) {
minorApiLevel = 1
}
}
// APK signing — nothing to do with the user's ABRP credentials. These values are ONLY for
// the build: they prove the APK comes from us and never enter the app. The ABRP API key and
// token are entered in the app and stored encrypted on the device (see SecurePrefs).
// The car build is signed by build-car (flake.nix) with the AOSP platform key instead.
//
// A keystore holds several keys: SIGNING_STORE_PASSWORD opens the container,
// SIGNING_KEY_ALIAS picks the key, SIGNING_KEY_PASSWORD opens that key.
// Read from the environment (CI) or a local gradle.properties — NEVER committed.
def keystorePath = System.getenv("SIGNING_KEYSTORE") ?: project.findProperty("signing.keystore")
signingConfigs {
if (keystorePath != null && file(keystorePath).exists()) {
platform {
storeFile file(keystorePath)
storePassword System.getenv("SIGNING_STORE_PASSWORD") ?: project.findProperty("signing.store.password")
keyAlias System.getenv("SIGNING_KEY_ALIAS") ?: project.findProperty("signing.key.alias") ?: "platform"
keyPassword System.getenv("SIGNING_KEY_PASSWORD") ?: project.findProperty("signing.key.password")
}
}
}
defaultConfig {
applicationId "com.stinnesbeck.nilsmg4"
minSdk 27
targetSdk 36
versionCode 95
versionName "2.18.0"
// Nothing about the server ships in the APK: its URL and ingest token are entered in
// the app's Server tab (ServerConfig), and the database is only reachable by the server.
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
// null when no keystore is configured: the build still works on a machine without
// secrets (unsigned APK) instead of failing.
def platformSigning = signingConfigs.findByName("platform")
// Two distribution channels:
// - stable: no self-update at all. The user installs it; nothing in the app fetches an
// APK (the updater is not even in this APK).
// - unstable: the car build (system UID, platform-signed), with the OTA updater, so the
// car can update itself from published pre-releases. Installs next to stable.
flavorDimensions += "channel"
productFlavors {
stable {
dimension "channel"
buildConfigField "boolean", "OTA_ENABLED", "false"
}
unstable {
dimension "channel"
applicationIdSuffix ".unstable"
// Three-part versions, the same as defaultConfig ("2.8.1-unstable"). Upstream added
// a fourth part for its CI build number; there is no CI here and the OTA updater is
// off. The version code keeps upstream's x100000 so it never drops below codes
// already installed on the car (2.8.0.0 was 6600000): a lower code is a downgrade,
// which the installer refuses.
versionCode defaultConfig.versionCode * 100000
versionName defaultConfig.versionName
versionNameSuffix "-unstable"
buildConfigField "boolean", "OTA_ENABLED", "true"
}
}
buildTypes {
debug {
if (platformSigning != null) signingConfig platformSigning
}
release {
// [T-918] R8 enabled. CarPropertyAdapter reaches the car purely by
// reflection, so proguard-rules.pro must keep those targets; a release build
// needs an on-vehicle check of the car reads before it ships.
minifyEnabled true
if (platformSigning != null) signingConfig platformSigning
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
// BuildConfig.DEBUG gates the debug-only logging (T-912).
buildFeatures {
buildConfig true
}
lint {
abortOnError true
checkReleaseBuilds true
warningsAsErrors false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
}
// The version build-car puts in the APK's file name ("NilsMG4-unstable-2.8.1.apk"), which
// is also the name the OTA updater reads a release's version from.
tasks.register('printUnstableVersion') {
doLast {
println android.defaultConfig.versionName
}
}
// Names the APKs by channel so the build outputs are readable.
androidComponents {
onVariants(selector().all()) { variant ->
variant.outputs.each { output ->
if (variant.buildType == "release") {
// A fixed name for the unstable release output. What is published for the car
// comes from build-car, named NilsMG4-unstable-<version>.apk: that file name is
// where the updater reads a release's version from.
output.outputFileName.set(variant.flavorName == "unstable"
? "NilsMG4-unstable.apk"
: "NilsMG4-${variant.flavorName}-${output.versionName.get()}.apk")
} else {
// Debug builds: NilsMG4-stable-debug.apk, NilsMG4-unstable-debug.apk.
output.outputFileName.set("NilsMG4-${variant.flavorName}-${variant.buildType}.apk")
}
}
}
}
dependencies {
implementation project(':evhardware')
implementation libs.appcompat
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
implementation libs.viewpager2
implementation libs.security.crypto
implementation 'com.google.zxing:core:3.5.3'
// android.jar's org.json is a stub in unit tests; the real implementation lets the
// telemetry payload tests parse what the app actually builds.
testImplementation libs.junit
testImplementation libs.json
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
}