Translate the remaining French and Turkish
mise.toml, the comments in app/build.gradle (corrected where they no longer described the build: the car build is signed by build-car, and the updater reads the version from build-car's file name), OTA.md's message table (now the updater's current English messages), the Turkish messages quoted in CHANGELOG.md and OTA.md (glossed in English), two test assertion messages, an icon comment, and the ignition-state comment in EVHardware.kt. The vendored EVHardware library still has about 120 half-French comment lines; noted in SPECS.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
+2
-2
@@ -39,7 +39,7 @@
|
||||
|
||||
- Charging sessions are now metered on DC only (10 kW and above). An AC charge is an
|
||||
overnight affair the head unit sleeps through, so its curve, duration and energy were
|
||||
guesses; the charging screen labels one as `AC şarj (ölçülmüyor)` rather than showing
|
||||
guesses; the charging screen labels one as `AC şarj (ölçülmüyor)` ("AC charging (not metered)") rather than showing
|
||||
the previous DC session's figures next to it.
|
||||
|
||||
### Fixed
|
||||
@@ -246,7 +246,7 @@ All notable changes to this project are documented here. Format follows
|
||||
- OTA installs now go through the platform `PackageInstaller` instead of shelling out to
|
||||
`pm install -r`. The old path handed the package manager service a file inside the app's
|
||||
private cache, which it cannot read from its own process, and every such failure was
|
||||
reported as `Kurulum reddedildi (imza uyuşmuyor?)` — a signature verdict on a correctly
|
||||
reported as `Kurulum reddedildi (imza uyuşmuyor?)` ("installation refused (signature mismatch?)") — a signature verdict on a correctly
|
||||
signed build. A session streams the archive from this process, so no second process has
|
||||
to reach our cache. Approach taken from merthankaraman/DriveHub_Dort, which solved the
|
||||
same problem on the same head unit.
|
||||
|
||||
@@ -335,18 +335,18 @@ object EVHardware {
|
||||
}
|
||||
|
||||
/**
|
||||
* État d'ignition courant, en valeurs [IgnitionState] (AAOS).
|
||||
* Current ignition state, as [IgnitionState] (AAOS) values.
|
||||
*
|
||||
* CarPropertyManager d'abord. Mais SWI68 n'expose pas IGNITION_STATE via CPM : la
|
||||
* property revient vide, et l'état d'allumage passait pour illisible alors que Katman5
|
||||
* le rapporte correctement — le log le montre (`Katman5: state initial = 2 (RUN/READY)`)
|
||||
* pendant que le diagnostic classait IGNITION_STATE en NOT_READABLE.
|
||||
* CarPropertyManager first. But SWI68 does not expose IGNITION_STATE through CPM: the
|
||||
* property comes back empty, and the ignition state looked unreadable although Katman5
|
||||
* reports it correctly — the log shows it (`Katman5: state initial = 2 (RUN/READY)`) while
|
||||
* the diagnostics classed IGNITION_STATE as NOT_READABLE.
|
||||
*
|
||||
* D'où le repli sur le dernier état livré par IVehicleConditionService, converti :
|
||||
* Katman5 parle en [CarIgnitionItem], cette fonction rend du [IgnitionState], et les
|
||||
* deux échelles ne coïncident pas (RUN vaut 2 chez l'un, OFF vaut 2 chez l'autre).
|
||||
* Hence the fallback to the last state delivered by IVehicleConditionService, converted:
|
||||
* Katman5 speaks [CarIgnitionItem], this function returns [IgnitionState], and the two
|
||||
* scales do not line up (RUN is 2 in one, OFF is 2 in the other).
|
||||
*
|
||||
* Returns -1 si rien n'est lisible, 0 si la property existe mais ne dit rien.
|
||||
* Returns -1 if nothing is readable, 0 if the property exists but says nothing.
|
||||
*/
|
||||
fun getCurrentIgnitionState(): Int {
|
||||
val v0 = getIntPropertyCPM(PROP_IGNITION_STATE, 0)
|
||||
@@ -356,7 +356,7 @@ object EVHardware {
|
||||
return vcmIgnitionAsAaos() ?: global
|
||||
}
|
||||
|
||||
/** Le dernier état Katman5 traduit en [IgnitionState], ou null s'il n'y en a pas eu. */
|
||||
/** The last Katman5 state translated into [IgnitionState], or null if there was none. */
|
||||
private fun vcmIgnitionAsAaos(): Int? = when (sLastVcmIgnitionState) {
|
||||
CarIgnitionItem.OFF -> IgnitionState.OFF
|
||||
CarIgnitionItem.ACCESSORY -> IgnitionState.ACC
|
||||
|
||||
@@ -39,7 +39,7 @@ release is a candidate only if all of these hold:
|
||||
| Marked **pre-release** | `release.optBoolean("prerelease")` | Skipped entirely |
|
||||
| Asset name contains `unstable` | `name.contains("unstable")` | Asset ignored |
|
||||
| Asset name ends `-<version>.apk` | `ASSET_VERSION` regex | No version, asset ignored |
|
||||
| Version strictly higher than installed | `isNewer()` | Nothing to do, reports "Güncel" |
|
||||
| Version strictly higher than installed | `isNewer()` | Nothing to do, reports "Up to date" |
|
||||
| Download host on the allowlist | `isAllowedUrl()` | Refused and logged |
|
||||
| APK signed with the **same certificate** as the running app | Android `PackageInstaller` | Installation is rejected by the platform |
|
||||
| Matching `.sha256` asset or `SHA256SUMS` entry | `fetchExpectedSha256()` | Update ignored/refused |
|
||||
@@ -115,17 +115,13 @@ The version comes from the **asset filename**, not the git tag. The tag can stay
|
||||
|
||||
## When it says something else
|
||||
|
||||
| Toast | Meaning |
|
||||
| Message | Meaning |
|
||||
|---|---|
|
||||
| `Güncel (2.2.2.0-unstable)` | No release beat the installed version. Check the pre-release tick and the asset filename. |
|
||||
| `Güncelleme reddedildi: SHA-256 doğrulaması yok` | APK has no matching sidecar, or the sidecar is invalid/unreachable. |
|
||||
| `İndirme veya SHA-256 doğrulaması başarısız` | Download failed or its bytes do not match the published hash. |
|
||||
| `Kuruluyor: 2.2.3 — ekrandaki onayı bekleyin` | The session was committed. Not done yet: watch for the platform's own toast, or a confirmation dialog to accept. |
|
||||
| `Kurulum reddedildi: imza uyuşmuyor` | Genuinely the wrong key — rebuild and re-sign with `platform.pk8`. Unlike the old catch-all, this one means what it says. |
|
||||
| `Kurulum reddedildi: APK okunamadı` | The archive does not parse, or it is built for another package. |
|
||||
| `Kurulum başlatılamadı … Log sayfasına bakın` | The session could not be created or committed. `adb logcat -s EVABRP.Update`. |
|
||||
| `Kurulum başarısız: INSTALL_FAILED_…` | From `OtaInstallResultReceiver` — the package manager's own words for why it refused. |
|
||||
| `Güncelleme kontrolü başarısız` | The check threw. `adb logcat -s EVABRP.Update OtaUpdater` if a cable is available; otherwise the in-app Log page. |
|
||||
| `Up to date (2.14.2-unstable)` | No release beat the installed version. Check the pre-release tick and the asset filename. |
|
||||
| `Update refused: no SHA-256 verification` | The APK has no matching sidecar, or the sidecar is invalid or unreachable. |
|
||||
| `Download or SHA-256 verification failed (…)` | The download failed, or its bytes do not match the published hash. |
|
||||
| `Update check failed` | The check threw. `adb logcat -s UpdateHook OtaUpdater` if a cable is available; otherwise the in-app Logs tab. |
|
||||
| `Update check already in progress` | A check is still running; wait for its result. |
|
||||
|
||||
## How the install works, and why it is not `pm install`
|
||||
|
||||
@@ -137,7 +133,7 @@ The downloaded APK lives in `context.getCacheDir()`. `pm` does not install the f
|
||||
— it passes the path to the package manager service, which opens it from *its* process and
|
||||
SELinux context, where an app's private cache is not readable. The install failed, and
|
||||
because `install()` returned a bare boolean, the caller reported the only failure it knew
|
||||
how to name: `Kurulum reddedildi (imza uyuşmuyor?)`. A correctly signed build, refused with
|
||||
how to name: `Kurulum reddedildi (imza uyuşmuyor?)` ("installation refused (signature mismatch?)"). A correctly signed build, refused with
|
||||
a signature error it had not earned. The `pm` output that would have said so was captured
|
||||
and thrown away.
|
||||
|
||||
|
||||
@@ -84,8 +84,9 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does *
|
||||
(`GF_DATABASE_TYPE`, `GF_DATABASE_HOST`, …); the replicas then share that database instead
|
||||
of a volume. The car data itself is in InfluxDB and unaffected.
|
||||
- [ ] **Telemetry queue survives restarts.** Unsent live samples are kept in memory only.
|
||||
- [ ] **Translate the rest.** The docs (`CHANGELOG.md`, `OTA.md`'s
|
||||
message table, comments in `mise.toml`/`app/build.gradle`) are still Turkish or French.
|
||||
- [ ] **EVHardware's comments are partly French.** About 120 comment lines in the vendored
|
||||
`EVHardware/` library (mostly `EVHardware.kt`, some tests), half-translated upstream. The
|
||||
app, the docs and the build files are English.
|
||||
- [ ] **Move hardcoded UI strings into `strings.xml`.** Many are inline in Java and layouts.
|
||||
|
||||
## Done
|
||||
@@ -116,6 +117,10 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does *
|
||||
INGEST_TOKEN) into git and pushed it. Commit amended and force-pushed; both values regenerated in
|
||||
the now gitignored `k8s/secrets.yaml` (since moved to `k8s/secrets/secrets.yaml`, outside
|
||||
`kubectl apply -f k8s/`). Filled-in secrets live only there from now on.
|
||||
- 2026-10-18 — Translated the rest: `mise.toml`, the comments in `app/build.gradle`, `OTA.md`'s
|
||||
message table (now the updater's current English messages), the Turkish messages quoted in
|
||||
`CHANGELOG.md` and `OTA.md` (glossed), two test messages, the ignition-state comment in
|
||||
`EVHardware.kt`.
|
||||
- 2026-10-18 — About dialog: link and QR code point to https://git.stinnesbeck.com/nils/mg4
|
||||
(were Fatih's GitHub). Status section brought up to date.
|
||||
- 2026-10-18 — App 2.14.2: "Range, 5-min average", like the usage and power cards (was "Range at 5-min average").
|
||||
|
||||
+18
-17
@@ -10,14 +10,14 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
// Signature de l'APK — rien à voir avec la clé d'API ABRP de l'utilisateur.
|
||||
// Ces valeurs servent UNIQUEMENT au build : elles prouvent que l'APK vient bien de
|
||||
// nous et n'entrent jamais dans l'application. La clé d'API et le token ABRP, eux,
|
||||
// sont saisis dans l'app et stockés chiffrés sur l'appareil (voir SecurePrefs).
|
||||
// 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.
|
||||
//
|
||||
// Un keystore contient plusieurs clés : SIGNING_STORE_PASSWORD ouvre le conteneur,
|
||||
// SIGNING_KEY_ALIAS choisit la clé, SIGNING_KEY_PASSWORD ouvre cette clé-là.
|
||||
// Lues depuis l'environnement (CI) ou gradle.properties local — JAMAIS commitées.
|
||||
// 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()) {
|
||||
@@ -43,15 +43,15 @@ android {
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
// null quand aucun keystore n'est configuré : le build reste possible sur une
|
||||
// machine sans secrets (APK non signé), au lieu d'échouer.
|
||||
// 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")
|
||||
|
||||
// Deux canaux de distribution :
|
||||
// - stable : diffusé en release taguée, AUCUN auto-update. L'utilisateur installe
|
||||
// lui-même ; rien dans l'app ne va chercher un APK.
|
||||
// - unstable : pré-releases publiées à chaque push sur master, avec OTA pour que les testeurs
|
||||
// soient à jour sans manipulation. Installable à côté du stable.
|
||||
// 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 {
|
||||
@@ -111,13 +111,14 @@ tasks.register('printUnstableVersion') {
|
||||
}
|
||||
}
|
||||
|
||||
// Nomme les APK par canal pour que les artefacts de release soient lisibles.
|
||||
// Names the APKs by channel so the build outputs are readable.
|
||||
androidComponents {
|
||||
onVariants(selector().all()) { variant ->
|
||||
variant.outputs.each { output ->
|
||||
if (variant.buildType == "release") {
|
||||
// Le canal unstable garde un nom de fichier stable : l'updater identifie
|
||||
// la version par le tag de la pré-release, jamais par le nom d'APK.
|
||||
// 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")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Terminal : onglet Log. Teinté par @color/nav_tab_foreground. -->
|
||||
<!-- Terminal: the Logs tab. Tinted by @color/nav_tab_foreground. -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
|
||||
@@ -50,7 +50,7 @@ public class TelemetryPayloadTest {
|
||||
"batt_temp", "capacity", "odometer", "hvac_setpoint",
|
||||
"tire_pressure_fl", "tire_pressure_fr",
|
||||
"tire_pressure_rl", "tire_pressure_rr"}) {
|
||||
assertFalse("le champ " + field + " aurait dû être omis", json.contains(field));
|
||||
assertFalse("field " + field + " should have been omitted", json.contains(field));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ public class TelemetryPayloadTest {
|
||||
TelemetryPayload t = new TelemetryPayload(1L);
|
||||
t.speedKmh = 48.4f;
|
||||
JSONObject json = new JSONObject(t.build());
|
||||
assertFalse("soc illisible aurait dû être omis", json.has("soc"));
|
||||
assertFalse("an unreadable soc should have been omitted", json.has("soc"));
|
||||
assertEquals(48, json.getInt("speed"));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
# Environnement de développement — EVABRPUploader.
|
||||
# Development environment with mise (the Nix flake, `nix develop`, is the other way in).
|
||||
#
|
||||
# Commité volontairement : il décrit comment construire le projet, pas la machine de
|
||||
# quelqu'un. Les chemins passent par {{env.HOME}} / JAVA_HOME, jamais en dur.
|
||||
# Pour une surcharge locale (chemin de keystore, SDK ailleurs), créer un mise.local.toml
|
||||
# — celui-là reste ignoré par git.
|
||||
# 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 installe le JDK
|
||||
# mise run bootstrap installe le SDK Android si absent
|
||||
# mise run test tests unitaires
|
||||
# mise tasks liste tout
|
||||
# mise install installs the JDK
|
||||
# mise run bootstrap installs the Android SDK if missing
|
||||
# mise run test unit tests
|
||||
# mise tasks lists everything
|
||||
#
|
||||
# Pourquoi épingler le JDK : le JDK par défaut de la machine (26) n'est pas supporté
|
||||
# par AGP et fait échouer le build avec une erreur de version de classe peu parlante.
|
||||
# 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"
|
||||
@@ -24,42 +23,40 @@ _.path = [
|
||||
"{{env.HOME}}/Android/Sdk/cmdline-tools/latest/bin",
|
||||
]
|
||||
|
||||
# Version du SDK requise par app/build.gradle (compileSdk = 36).
|
||||
# SDK version app/build.gradle needs (compileSdk = 36).
|
||||
COMPILE_SDK = "36"
|
||||
BUILD_TOOLS = "36.0.0"
|
||||
|
||||
# Secrets de SIGNATURE D'APK — sans rapport avec la clé d'API ABRP, qui se saisit dans
|
||||
# l'application. À définir hors du dépôt : décommenter dans un mise.local.toml (ignoré
|
||||
# par git) et pointer vers un keystore local, JAMAIS vers un fichier du dépôt.
|
||||
# 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 doit tourner sur le JDK que mise vient d'installer. Sans -Dorg.gradle.java.home,
|
||||
# Gradle peut réutiliser un daemon lancé par un autre JDK — typiquement celui embarqué par
|
||||
# l'extension Java de VS Code, un JRE 21 sans jlink : le build échoue alors sur
|
||||
# JdkImageTransform, avec un message qui n'a rien à voir avec le code.
|
||||
# JAVA_HOME est posé par [tools] ci-dessus : $JAVA_HOME est résolu par le shell au moment
|
||||
# de l'exécution, donc ceci reste portable d'une machine à l'autre.
|
||||
# ── Émulateur ────────────────────────────────────────────────────────────────────
|
||||
# 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 ─────────────────────────────────────────────────────────────────────
|
||||
#
|
||||
# ATTENTION sur la fidélité : le véhicule tourne en AAOS 9 (API 28). Google ne publie
|
||||
# AUCUNE image système Automotive en dessous d'API 33 — vérifié avec sdkmanager --list.
|
||||
# Il n'existe donc pas d'émulateur qui reproduise à la fois l'OS et l'API voiture du MG4.
|
||||
# D'où deux profils, chacun fidèle sur un axe :
|
||||
# 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 — a bien un CarPropertyManager, mauvaise version
|
||||
# d'OS. Pour tester le cycle de vie du service et le chemin voiture.
|
||||
# Les propriétés VENDEUR SAIC (0x216xxxxx : SOC, autonomie) n'existent
|
||||
# pas dans l'émulateur : ces lectures échoueront, c'est normal.
|
||||
# emulator-screen API 28 tablette — bonne version d'OS et bon écran (1920x720 @ 160dpi,
|
||||
# 12,8" HD comme le MG4), mais AUCUN service voiture.
|
||||
# Pour l'UI, les layouts et la densité.
|
||||
# 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.)
|
||||
#
|
||||
# Rien de tout cela ne remplace un essai sur le véhicule.
|
||||
# Densité cible du projet : 160 dpi.
|
||||
# Écran 12,8" 1920x720. Firmware de référence : SWI68-29958-1300R69.
|
||||
# None of this replaces a test on the car.
|
||||
# Target density: 160 dpi. Screen 12.8" 1920x720. Reference firmware: SWI68-29958-1300R69.
|
||||
|
||||
EMU_CAR_AVD = "evabrp-car"
|
||||
EMU_CAR_IMAGE = "system-images;android-33;android-automotive;x86_64"
|
||||
@@ -70,16 +67,16 @@ EMU_HEIGHT = "720"
|
||||
EMU_DENSITY = "160"
|
||||
|
||||
[tasks.bootstrap]
|
||||
description = "Installe le SDK Android (platform + build-tools) s'il manque"
|
||||
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 déjà présent → rien à faire"
|
||||
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 "Téléchargement des commandline-tools…"
|
||||
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
|
||||
@@ -92,59 +89,59 @@ yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$ANDROID_H
|
||||
"$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 installé, local.properties écrit"
|
||||
echo "SDK installed, local.properties written"
|
||||
"""
|
||||
|
||||
[tasks.build]
|
||||
description = "Compile l'APK debug"
|
||||
description = "Builds the debug APK"
|
||||
run = './gradlew -Dorg.gradle.java.home=$JAVA_HOME assembleStableDebug'
|
||||
|
||||
[tasks.test]
|
||||
description = "Tests unitaires JVM"
|
||||
description = "JVM unit tests"
|
||||
run = './gradlew -Dorg.gradle.java.home=$JAVA_HOME testStableDebugUnitTest testUnstableDebugUnitTest'
|
||||
|
||||
[tasks.lint]
|
||||
description = "Analyse lint Android"
|
||||
description = "Android lint"
|
||||
run = './gradlew -Dorg.gradle.java.home=$JAVA_HOME lintStableDebug lintUnstableDebug'
|
||||
|
||||
[tasks.permissions]
|
||||
description = "Contrôle de dérive des permissions (le même que la CI)"
|
||||
description = "Permission drift check (the same as CI)"
|
||||
run = "bash .github/security/check-permissions.sh"
|
||||
|
||||
[tasks.check]
|
||||
description = "Ce que la CI exécute : permissions + lint + tests"
|
||||
description = "What CI runs: permissions + lint + tests"
|
||||
depends = ["permissions", "lint", "test"]
|
||||
|
||||
[tasks.install]
|
||||
description = "Installe l'APK debug sur le véhicule connecté en ADB"
|
||||
description = "Installs the debug APK on the car connected over ADB"
|
||||
run = './gradlew -Dorg.gradle.java.home=$JAVA_HOME installStableDebug'
|
||||
|
||||
[tasks.logs]
|
||||
description = "Suit les logs de l'app sur le véhicule"
|
||||
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 = "APK release des deux canaux (stable sans OTA, unstable avec OTA)"
|
||||
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 = "Installe l'émulateur + les images système et crée les deux AVD"
|
||||
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 "ERREUR : /dev/kvm absent — pas d'accélération matérielle, l'émulateur sera inutilisable." >&2
|
||||
echo "Sous WSL2 : activer la virtualisation imbriquée côté Windows." >&2
|
||||
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 "ATTENTION : utilisateur absent du groupe kvm. Corriger avec :" >&2
|
||||
echo " sudo usermod -aG kvm $USER # puis se reconnecter" >&2
|
||||
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
|
||||
@@ -153,13 +150,13 @@ yes | "$SDK" --sdk_root="$ANDROID_HOME" --licenses > /dev/null
|
||||
create_avd() {
|
||||
name="$1"; image="$2"; device="$3"
|
||||
if "$AVDM" list avd 2>/dev/null | grep -q "Name: $name"; then
|
||||
echo "AVD $name déjà présent"
|
||||
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"
|
||||
# Écran du MG4. Sans ça l'AVD garde la densité du profil générique et les layouts
|
||||
# ne ressemblent pas à ce que voit le conducteur.
|
||||
# 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"
|
||||
@@ -167,55 +164,55 @@ create_avd() {
|
||||
echo "hw.lcd.density=$EMU_DENSITY"
|
||||
echo "disk.dataPartition.size=4G"
|
||||
} >> "$cfg"
|
||||
echo "AVD $name configuré en ${EMU_WIDTH}x${EMU_HEIGHT} @ ${EMU_DENSITY}dpi"
|
||||
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, pas « host » : WSL2 n'expose aucun GPU réel (uniquement Mesa
|
||||
# llvmpipe). Avec -gpu host l'émulateur meurt au démarrage sur « Your GPU cannot be used
|
||||
# for hardware rendering », avant qu'aucune fenêtre n'apparaisse — d'où l'icône de tray
|
||||
# sans émulateur visible. Le rendu logiciel est plus lent, mais il démarre.
|
||||
# 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 = "Lance l'émulateur Automotive (API 33 — a un CarPropertyManager)"
|
||||
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 "Émulateur voiture prêt."
|
||||
echo "Car emulator ready."
|
||||
'''
|
||||
|
||||
[tasks.emulator-screen]
|
||||
description = "Lance l'émulateur écran MG4 (API 28, 1920x720 @ 160dpi — pas de service voiture)"
|
||||
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 "Émulateur écran prêt."
|
||||
echo "Screen emulator ready."
|
||||
'''
|
||||
|
||||
[tasks.emulator-stop]
|
||||
description = "Arrête l'émulateur en cours"
|
||||
description = "Stops the running emulator"
|
||||
run = '"$ANDROID_HOME/platform-tools/adb" emu kill || true'
|
||||
|
||||
[tasks.run]
|
||||
description = "Compile, installe et lance l'APK sur l'émulateur ou le véhicule connecté"
|
||||
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 "Aucun appareil connecté. Lancer d'abord 'mise run emulator-car' ou 'mise run emulator-screen'," >&2
|
||||
echo "ou brancher le véhicule en ADB." >&2
|
||||
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 "Installation de $APK"
|
||||
echo "Installing $APK"
|
||||
$ADB install -r -g "$APK"
|
||||
$ADB shell am start -n "com.stinnesbeck.nilsmg4/com.evsuite.abrp.MainActivity"
|
||||
echo "Lancé. Logs : mise run logs"
|
||||
echo "Started. Logs: mise run logs"
|
||||
'''
|
||||
|
||||
Reference in New Issue
Block a user