feat: add OTA
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user