diff --git a/SPECS.md b/SPECS.md new file mode 100644 index 0000000..aa56f6f --- /dev/null +++ b/SPECS.md @@ -0,0 +1,55 @@ +# SPECS — Nils' MG4 + +Journal for this fork of [fatihdonmezdev/FatihsMG4](https://github.com/fatihdonmezdev/FatihsMG4): +what has been changed, and what is still to do. + +- Our repository: https://git.stinnesbeck.com/nils/mg4 +- Upstream: https://github.com/fatihdonmezdev/FatihsMG4 (itself a fork of malys / EVABRPUploader + and Leon Kernan / ABRP_Uploader). Credited in the app's About dialog. + +## To do + +- [ ] **About dialog link and QR code → https://git.stinnesbeck.com/nils/mg4.** + Both come from `REPOSITORY_URL` in `app/src/main/java/com/evsuite/abrp/MainActivity.java` + and `about_repository` in `app/src/main/res/values/strings.xml`. They still point to Fatih's + GitHub repository. +- [ ] **Re-enable OTA updates against our own releases.** `RELEASES_API` in + `app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java` points at `updates.invalid` on purpose, + so checks fail. The updater speaks the GitHub releases API and only allows GitHub hosts + (`ALLOWED_HOSTS`). For git.stinnesbeck.com it needs the host on the allowlist, and the + response parsing checked against that server's releases API. Release assets must be named + `NilsMG4-unstable-.apk` with a `.sha256` sidecar (`build-car` writes both). +- [ ] **Fix the unit tests.** They don't compile upstream: `AbrpApiTest`, `TelemetryPayloadTest` + and others still use `AbrpApi`/`TelemetryPayload`, which upstream commit `ea8032a` deleted. +- [ ] **Charging currency.** Prices and costs are shown in Turkish lira (`₺/kWh`, `₺`) in + `pane_charging.xml` and `MainActivity.java`. +- [ ] **Cloud consumption sync.** `CONSUMPTION_INSTALLATION_ID` in `app/build.gradle` is a fixed + UUID inherited from upstream. Set our own before configuring `CONSUMPTION_API_URL` / + `CONSUMPTION_API_TOKEN`, so our data cannot land under upstream's id. +- [ ] **Translate the rest.** The `web/` dashboard (Next.js) and docs (`CHANGELOG.md`, `OTA.md`'s + message table, comments in `mise.toml`/`app/build.gradle`) are still Turkish or French. +- [ ] **Docs still use the old package ID.** `README.md`, `OTA.md`, `CHANGELOG.md`, `mise.toml` and + `docs/MG4_PLATFORM_INSTALL.md` refer to `com.evsuite.abrp(.unstable)` and `FatihsMG4-*.apk`. +- [ ] **Move hardcoded UI strings into `strings.xml`.** Many are inline in Java and layouts. + +## Done + +- 2026-10-17 — Translated the app UI from Turkish to English. +- 2026-10-17 — Nix flake: `nix develop` provides JDK 21 and the Android SDK. `build-car` builds the + unstable APK and signs it with the AOSP test platform key, which the MG4 head unit uses + (cert SHA-256 `c8a2e9bc…192ab8`). +- 2026-10-17 — OTA update checks disabled by pointing at an unresolvable host (see To do). +- 2026-10-17 — Renamed to "Nils' MG4": package ID `com.stinnesbeck.nilsmg4` (`.unstable` for the car + build), APK names `NilsMG4-*.apk`. It installs next to the old FatihsMG4 app instead of replacing + it, and starts with empty settings and history. + +## Installing on the car + +``` +nix develop +build-car +adb push build/car/NilsMG4-unstable-.apk /data/local/tmp/update.apk +adb shell "pm install -r -f -d -t /data/local/tmp/update.apk" +``` + +To remove the old upstream app: `adb uninstall com.evsuite.abrp.unstable`. diff --git a/app/build.gradle b/app/build.gradle index 8a597c2..a82f37f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,7 +31,7 @@ android { } defaultConfig { - applicationId "com.evsuite.abrp" + applicationId "com.stinnesbeck.nilsmg4" minSdk 27 targetSdk 36 versionCode 56 @@ -113,7 +113,7 @@ android { } // Version publiée par la CI pour composer le nom de l'asset APK : ce nom DOIT être -// comparable par l'updater ("FatihsMG4-unstable-1.0.42.apk"). Le tag de la +// comparable par l'updater ("NilsMG4-unstable-1.0.42.apk"). Le tag de la // pré-release est la constante "unstable", écrasée à chaque build. tasks.register('printUnstableVersion') { doLast { @@ -129,8 +129,11 @@ androidComponents { // 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. output.outputFileName.set(variant.flavorName == "unstable" - ? "FatihsMG4-unstable.apk" - : "FatihsMG4-${variant.flavorName}-${output.versionName.get()}.apk") + ? "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") } } } diff --git a/app/src/main/java/com/evsuite/abrp/ConsumptionCloudClient.java b/app/src/main/java/com/evsuite/abrp/ConsumptionCloudClient.java index 4e855e2..aec491d 100644 --- a/app/src/main/java/com/evsuite/abrp/ConsumptionCloudClient.java +++ b/app/src/main/java/com/evsuite/abrp/ConsumptionCloudClient.java @@ -28,7 +28,7 @@ import java.util.List; * picks up right where the old one left off instead of showing zeroes. */ final class ConsumptionCloudClient { - private static final String TAG = "FatihsMG4.Mongo"; + private static final String TAG = "NilsMG4.Mongo"; private static final long CONSUMPTION_SYNC_INTERVAL_MS = 2 * 60_000L; private static final long CHARGE_RETRY_INTERVAL_MS = 5 * 60_000L; private static final long HISTORY_FETCH_INTERVAL_MS = 5 * 60_000L; diff --git a/app/src/main/java/com/evsuite/abrp/ConsumptionTracker.java b/app/src/main/java/com/evsuite/abrp/ConsumptionTracker.java index 2f9395a..c00ddcc 100644 --- a/app/src/main/java/com/evsuite/abrp/ConsumptionTracker.java +++ b/app/src/main/java/com/evsuite/abrp/ConsumptionTracker.java @@ -216,7 +216,7 @@ final class ConsumptionTracker { if (cloudLifetime.km > local.km) { lifetime.set(cloudLifetime.km, cloudLifetime.kwh, cloudLifetime.hours, cloudLifetime.soc); persist(); - Log.i("FatihsMG4.Consumption", "Lifetime baseline synced from cloud: " + Log.i("NilsMG4.Consumption", "Lifetime baseline synced from cloud: " + cloudLifetime.km + " km (was " + local.km + " km)"); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4f9a0ce..341259d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -128,7 +128,7 @@ About About Nils\' MG4 Version %1$s - Fork of malys / EVABRPUploader\nand Leon Kernan / ABRP_Uploader + Fork of fatihdonmezdev / FatihsMG4,\nmalys / EVABRPUploader\nand Leon Kernan / ABRP_Uploader github.com/fatihdonmezdev/FatihsMG4 MG4 EH32 · SWI69 Nils\' MG4 logo diff --git a/app/src/testUnstable/java/com/evsuite/abrp/OtaUpdaterTest.java b/app/src/testUnstable/java/com/evsuite/abrp/OtaUpdaterTest.java index 4211434..e318cfd 100644 --- a/app/src/testUnstable/java/com/evsuite/abrp/OtaUpdaterTest.java +++ b/app/src/testUnstable/java/com/evsuite/abrp/OtaUpdaterTest.java @@ -102,13 +102,13 @@ public class OtaUpdaterTest { @Test public void versionIsReadFromTheAssetName() { // The release tag is the constant "unstable", so the asset name carries the build. - assertEquals("1.0.42", OtaUpdater.versionFromAssetName("FatihsMG4-unstable-1.0.42.apk")); - assertEquals("1.0.100", OtaUpdater.versionFromAssetName("FatihsMG4-unstable-1.0.100.APK")); + assertEquals("1.0.42", OtaUpdater.versionFromAssetName("NilsMG4-unstable-1.0.42.apk")); + assertEquals("1.0.100", OtaUpdater.versionFromAssetName("NilsMG4-unstable-1.0.100.APK")); } @Test public void assetNameWithoutAVersionIsIgnored() { - assertNull(OtaUpdater.versionFromAssetName("FatihsMG4-unstable.apk")); + assertNull(OtaUpdater.versionFromAssetName("NilsMG4-unstable.apk")); assertNull(OtaUpdater.versionFromAssetName("unstable")); } @@ -125,14 +125,14 @@ public class OtaUpdaterTest { public void sha256SidecarFormatsAreParsed() { String hash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; assertEquals(hash, OtaUpdater.parseExpectedSha256(hash, - "FatihsMG4-unstable-2.2.7.apk")); + "NilsMG4-unstable-2.2.7.apk")); assertEquals(hash, OtaUpdater.parseExpectedSha256( - hash + " *FatihsMG4-unstable-2.2.7.apk\n", - "FatihsMG4-unstable-2.2.7.apk")); + hash + " *NilsMG4-unstable-2.2.7.apk\n", + "NilsMG4-unstable-2.2.7.apk")); assertNull(OtaUpdater.parseExpectedSha256(hash + " another.apk", - "FatihsMG4-unstable-2.2.7.apk")); + "NilsMG4-unstable-2.2.7.apk")); assertNull(OtaUpdater.parseExpectedSha256("not-a-hash", - "FatihsMG4-unstable-2.2.7.apk")); + "NilsMG4-unstable-2.2.7.apk")); } @Test @@ -150,14 +150,14 @@ public class OtaUpdaterTest { .put(release(true, asset("2.2.5"))); OtaUpdater.Update update = OtaUpdater.selectUpdate(releases, "2.2.2.0-unstable"); assertEquals("2.2.6", update.versionName); - assertTrue(update.apkUrl.endsWith("FatihsMG4-unstable-2.2.6.apk")); + assertTrue(update.apkUrl.endsWith("NilsMG4-unstable-2.2.6.apk")); } @Test public void stableDraftForeignAndUnversionedAssetsCannotWin() throws Exception { JSONObject foreign = asset("9.0.0").put("browser_download_url", "https://example.com/app.apk"); - JSONObject unversioned = asset("9.0.0").put("name", "FatihsMG4-unstable.apk"); - JSONObject stableName = asset("9.0.0").put("name", "FatihsMG4-stable-9.0.0.apk"); + JSONObject unversioned = asset("9.0.0").put("name", "NilsMG4-unstable.apk"); + JSONObject stableName = asset("9.0.0").put("name", "NilsMG4-stable-9.0.0.apk"); JSONArray releases = new JSONArray() .put(release(false, asset("9.0.0"))) .put(release(true, asset("9.0.0")).put("draft", true)) @@ -216,7 +216,7 @@ public class OtaUpdaterTest { } private static JSONObject asset(String version) throws Exception { - String name = "FatihsMG4-unstable-" + version + ".apk"; + String name = "NilsMG4-unstable-" + version + ".apk"; return new JSONObject().put("name", name).put("browser_download_url", "https://github.com/fatihdonmezdev/FatihsMG4/releases/download/unstable/" + name); } diff --git a/app/src/unstable/AndroidManifest.xml b/app/src/unstable/AndroidManifest.xml index 1e4f316..54d3c67 100644 --- a/app/src/unstable/AndroidManifest.xml +++ b/app/src/unstable/AndroidManifest.xml @@ -27,7 +27,7 @@ android:name=".OtaInstallResultReceiver" android:exported="false"> - + diff --git a/app/src/unstable/java/com/evsuite/abrp/OtaInstallResultReceiver.java b/app/src/unstable/java/com/evsuite/abrp/OtaInstallResultReceiver.java index 3a4611a..2aabac1 100644 --- a/app/src/unstable/java/com/evsuite/abrp/OtaInstallResultReceiver.java +++ b/app/src/unstable/java/com/evsuite/abrp/OtaInstallResultReceiver.java @@ -19,7 +19,7 @@ import android.widget.Toast; */ public final class OtaInstallResultReceiver extends BroadcastReceiver { - static final String ACTION_INSTALL_RESULT = "com.evsuite.abrp.OTA_INSTALL_RESULT"; + static final String ACTION_INSTALL_RESULT = "com.stinnesbeck.nilsmg4.OTA_INSTALL_RESULT"; private static final String TAG = "EVABRP.Update"; diff --git a/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java b/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java index 67838a4..276f60f 100644 --- a/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java +++ b/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java @@ -53,7 +53,7 @@ import java.security.MessageDigest; final class OtaUpdater { private static final String TAG = "OtaUpdater"; - private static final String CACHE_PREFIX = "FatihsMG4-ota-"; + private static final String CACHE_PREFIX = "NilsMG4-ota-"; /** * Pre-releases live here; the unstable channel tracks them. @@ -167,7 +167,7 @@ final class OtaUpdater { /** * Version carried by an unstable asset name: - * "FatihsMG4-unstable-1.0.42.apk" -> "1.0.42". + * "NilsMG4-unstable-1.0.42.apk" -> "1.0.42". * * The release tag is the fixed string "unstable" (one rolling pre-release), so the asset * name is what identifies a build. Returns null when the name carries no version. @@ -203,7 +203,7 @@ final class OtaUpdater { try { conn.setInstanceFollowRedirects(false); conn.setRequestProperty("Accept", "application/vnd.github.v3+json"); - conn.setRequestProperty("User-Agent", "FatihsMG4-Android"); + conn.setRequestProperty("User-Agent", "NilsMG4-Android"); conn.setConnectTimeout(TIMEOUT_MS); conn.setReadTimeout(TIMEOUT_MS); int status = conn.getResponseCode(); @@ -302,7 +302,7 @@ final class OtaUpdater { connection.setInstanceFollowRedirects(false); connection.setConnectTimeout(TIMEOUT_MS); connection.setReadTimeout(TIMEOUT_MS); - connection.setRequestProperty("User-Agent", "FatihsMG4-Android"); + connection.setRequestProperty("User-Agent", "NilsMG4-Android"); try { int status = connection.getResponseCode(); if (status >= 300 && status <= 399) { @@ -584,7 +584,7 @@ final class OtaUpdater { if (downloads == null || (!downloads.isDirectory() && !downloads.mkdirs())) { throw new IllegalStateException("Downloads directory unavailable"); } - File destination = new File(downloads, "FatihsMG4-OTA-update.apk"); + File destination = new File(downloads, "NilsMG4-OTA-update.apk"); try (FileInputStream input = new FileInputStream(source); FileOutputStream output = new FileOutputStream(destination, false)) { byte[] buffer = new byte[64 * 1024]; diff --git a/flake.nix b/flake.nix index 70fead2..b0b4a1f 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "FatihsMG4 — Android Automotive build environment"; + description = "NilsMG4 — Android Automotive build environment"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -57,7 +57,7 @@ root=$(git rev-parse --show-toplevel 2>/dev/null || pwd) cd "$root" if [ ! -f app/build.gradle ]; then - echo "build-car: run this from the FatihsMG4 checkout" >&2 + echo "build-car: run this from the NilsMG4 checkout" >&2 exit 1 fi @@ -81,10 +81,10 @@ ./gradlew assembleUnstableDebug "$@" version=$(./gradlew -q printUnstableVersion "$@" | tail -1) - in=app/build/outputs/apk/unstable/debug/app-unstable-debug.apk + in=app/build/outputs/apk/unstable/debug/NilsMG4-unstable-debug.apk mkdir -p build/car aligned=build/car/aligned.apk - out="build/car/FatihsMG4-unstable-$version.apk" + out="build/car/NilsMG4-unstable-$version.apk" "${buildTools}/zipalign" -f -p 4 "$in" "$aligned" # Options come before the input APK; apksigner rejects the other order. @@ -122,7 +122,7 @@ shellHook = '' echo "sdk.dir=$ANDROID_HOME" > local.properties - echo "FatihsMG4 dev shell — JDK $(java -version 2>&1 | head -1 | cut -d'"' -f2), SDK at $ANDROID_HOME" + echo "NilsMG4 dev shell — JDK $(java -version 2>&1 | head -1 | cut -d'"' -f2), SDK at $ANDROID_HOME" echo " ./gradlew assembleStableDebug # debug APK" echo " ./gradlew assembleUnstableDebug # build that can update itself (OTA)" echo " build-car # unstable build signed for the car" diff --git a/settings.gradle b/settings.gradle index 98c922f..c8d83b5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,7 +22,7 @@ dependencyResolutionManagement { } } -rootProject.name = "FatihsMG4" +rootProject.name = "NilsMG4" include ':app' include ':evhardware' project(':evhardware').projectDir = file('EVHardware/lib')