diff --git a/OTA.md b/OTA.md index c6780a4..6db7e9f 100644 --- a/OTA.md +++ b/OTA.md @@ -71,7 +71,9 @@ version, checks it against the `.sha256`, checks the package, and installs it wi installer session as an over-the-air update. The car build only; the stable build has no button. On the Android 9 emulator (car build, system user) this installed without asking, from the -storage's top folder; on the car the stick's top folder is used the same way. +storage's top folder; on the car the stick's top folder is used the same way, and the install +ends on Android's installer screen, confirmed with **Install** (below). Every step, with the +platform's own words, is on the Logs tab under "Updates". ## When it says something else @@ -100,5 +102,11 @@ path: the app opens the archive itself and streams it in. The approach comes fro the same wall on the same hardware. Upstream later fell back to "download to Downloads and tap it"; this fork tries the session first and keeps that as the fallback. -**Not yet tried on this car:** whether the session installs silently as the system user, or -asks for a confirmation. +**On the car (SWI68, 2026-10-23) the session is refused:** the package manager answers +`Not allowed to install non-system apps on internal storage` — a firmware policy for apps that +are not system apps, even when they run as the system user. The app then opens Android's own +installer screen, where the driver confirms with **Install**, the same route the Files app takes. +That screen needs a `file://` URI on the car build: a read grant on our FileProvider is refused +when it comes from the system user (`not exported from UID 1000`), and the installer reports the +unreadable file as "There was a problem parsing the package". On the emulator the session still +installs silently. diff --git a/SPECS.md b/SPECS.md index 61ee21f..7393cc1 100644 --- a/SPECS.md +++ b/SPECS.md @@ -118,6 +118,14 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does * confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator` (Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked). +- 2026-10-23 — App 2.19.5: installing from USB (and over the air) works on the car again, with a + confirmation. The update log showed the car refusing the silent session: `Not allowed to install + non-system apps on internal storage`. The fallback, Android's installer screen, then said "There + was a problem parsing the package": as the system user, our FileProvider's read grant to the + installer is refused (`not exported from UID 1000`). The car build now hands the installer a + file URI to the verified copy in Downloads (StrictMode's file-URI check lifted for that one + call). On the emulator, with the session forced to fail as on the car, the installer screen + asked to update and installed it. Not tried on the car yet. OTA.md updated. - 2026-10-23 — App 2.19.4: "Export CarPlay data" on the Logs tab (Foreground apps card), for the question whether CarPlay's resolution can change. CarPlay with policy_control still leaves a grey strip, also after reconnecting: Allgo announces a smaller picture to the iPhone. 20 s after the diff --git a/app/build.gradle b/app/build.gradle index 05fcd0e..cf0cf47 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,8 +34,8 @@ android { applicationId "com.stinnesbeck.nilsmg4" minSdk 27 targetSdk 36 - versionCode 103 - versionName "2.19.4" + versionCode 104 + versionName "2.19.5" // 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. diff --git a/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java b/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java index 43cc9d4..5cee62e 100644 --- a/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java +++ b/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java @@ -566,6 +566,28 @@ final class OtaUpdater { } File publicApk = copyToPublicDownloads(apk); + if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID) { + // As the system user, our FileProvider's read grant is refused ("not exported from + // UID 1000") and the installer reports the unreadable file as a parse error. A file + // URI it reads with its own storage permission instead, as from the Files app. + Intent install = new Intent(Intent.ACTION_VIEW) + .setDataAndType(Uri.fromFile(publicApk), "application/vnd.android.package-archive") + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (install.resolveActivity(context.getPackageManager()) == null) { + throw new IllegalStateException("No package installer activity"); + } + // StrictMode forbids file URIs in intents (FileUriExposedException): lifted for this + // one call only. + android.os.StrictMode.VmPolicy policy = android.os.StrictMode.getVmPolicy(); + android.os.StrictMode.setVmPolicy(new android.os.StrictMode.VmPolicy.Builder().build()); + try { + context.startActivity(install); + } finally { + android.os.StrictMode.setVmPolicy(policy); + } + Log.i(TAG, "System install UI opened with a file URI for " + publicApk.getAbsolutePath()); + return; + } Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", publicApk); Intent install = new Intent(Intent.ACTION_VIEW)