feat: add OTA

This commit is contained in:
TCFDONMEZ
2026-09-29 17:04:59 +03:00
parent 511c166436
commit 48a7a0dd72
10 changed files with 292 additions and 38 deletions
+25 -17
View File
@@ -5,16 +5,17 @@ next — probably me, having forgotten the details.
## First upgrade from 2.2.3 or older
Install the platform-signed 2.2.6 APK manually once if the car still runs 2.2.3 or older.
Install the platform-signed 2.2.7 APK manually once if the car still runs 2.2.3 or older.
Those builds use the old `pm install` path; publishing a newer APK does not repair the
installer already running on the car. Builds from 2.2.4 use `PackageInstaller` sessions.
Keep the unstable package ID, system shared UID and signing certificate when upgrading.
The 2.2.5 build removes window automation. It also distinguishes failed release checks
from a successful check with no newer release. On 2026-09-29, GitHub still published only
2.2.3 at the last live check. The local 2.2.6 build includes these fixes and the redesigned
2.2.3 at the last live check. The local 2.2.7 build includes these fixes and the redesigned
UI; it becomes available to OTA only after publication as a pre-release asset named
`FatihsMG4-unstable-2.2.6.apk`.
`FatihsMG4-unstable-2.2.7.apk`, together with
`FatihsMG4-unstable-2.2.7.apk.sha256`.
The vehicle artifact uses `com.evsuite.abrp.unstable`, `android.uid.system`, and the
DriveHub_Kamera platform key. Its certificate SHA-256 must be
@@ -36,6 +37,7 @@ release is a candidate only if all of these hold:
| Version strictly higher than installed | `isNewer()` | Nothing to do, reports "Güncel" |
| Download host on the allowlist | `isAllowedUrl()` | Refused and logged |
| APK signed with the **same certificate** as the running app | `signatureMatchesRunningApp()` | Deleted, never installed |
| Matching `.sha256` asset or `SHA256SUMS` entry | `fetchExpectedSha256()` | Update ignored/refused |
The last one is the one that bites. The car runs a platform-signed build; an APK signed
with a debug key — or anyone else's key — is downloaded, rejected and deleted. Sign every
@@ -49,11 +51,11 @@ The version comes from the **asset filename**, not the git tag. The tag can stay
1. **Raise the version.** `app/build.gradle`, `defaultConfig`:
```gradle
versionCode 23 // must increase — Android orders updates by this, not by name
versionName "2.2.6"
versionCode 24 // must increase — Android orders updates by this, not by name
versionName "2.2.7"
```
The unstable flavour appends its own suffix, so `2.2.6` becomes `2.2.6.0-unstable`.
The unstable flavour appends its own suffix, so `2.2.7` becomes `2.2.7.0-unstable`.
Check what the installed build reports before picking a number: the app's About
dialog shows it, and `isNewer` compares numerically segment by segment.
@@ -76,35 +78,42 @@ The version comes from the **asset filename**, not the git tag. The tag can stay
"$BT/apksigner" sign \
--key "$KEYS/platform.pk8" \
--cert "$KEYS/platform.x509.pem" \
--out FatihsMG4-unstable-2.2.6.apk \
--out FatihsMG4-unstable-2.2.7.apk \
aligned.apk
rm aligned.apk
```
The output filename **is** the version the updater reads. Name it
`FatihsMG4-unstable-<version>.apk` and nothing else.
`FatihsMG4-unstable-<version>.apk`, then create its required integrity sidecar:
```bash
shasum -a 256 FatihsMG4-unstable-2.2.7.apk \
> FatihsMG4-unstable-2.2.7.apk.sha256
```
4. **Verify the signer** before uploading. It must say `CN=Android`:
```bash
"$BT/apksigner" verify --print-certs FatihsMG4-unstable-2.2.6.apk
"$BT/apksigner" verify --print-certs FatihsMG4-unstable-2.2.7.apk
```
5. **Publish.** On github.com/fatihdonmezdev/MG4ABRP → Releases → Draft a new release:
- tag: `unstable` (reuse it; the rolling tag is the design)
- **tick "Set as a pre-release"** ← the whole thing is inert without this
- attach the signed APK
- attach the signed APK and its `.sha256` sidecar
6. **Install it.** Open the app on the head unit and press the refresh button in the top
bar. Expected toast: `Güncellendi: 2.2.3 — yeniden başlatın`.
bar. The dialog shows download progress, verifies SHA-256, then reports that the
PackageInstaller session has started.
## When it says something else
| Toast | Meaning |
|---|---|
| `Güncel (2.2.2.0-unstable)` | No release beat the installed version. Check the pre-release tick and the asset filename. |
| `İndirme başarısız` | Release found, download did not complete. Usually no network. |
| `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. |
@@ -142,12 +151,11 @@ Two consequences for anyone reading a failure:
Still unverified on the car: whether this build installs silently or raises that
confirmation. Either way it installs, which the old path did not.
## Why the check is manual
## Why installation is manual
The refresh button is the only trigger. `UpdateHook.checkInBackground(this)` also runs on
app start, but the app is rarely launched — the upload service does the work, and the
driver does not open the UI on every trip. The button is there so an update can be pulled
deliberately rather than waiting for a launch that may not come.
`UpdateHook.checkInBackground(this)` performs an availability-only check at app start and
shows a notification toast when a complete APK + hash pair exists. It never downloads or
installs. The refresh button is the only trigger for download, verification and installation.
The upload service does **not** check for updates on its tick. Installing a new APK
restarts the process, and doing that mid-drive would interrupt the telemetry the service