App 2.19.5: the installer screen fallback reads the APK

The car refuses the silent session for a non-system app; the fallback
installer screen could not read our FileProvider URI because grants
from the system user are refused. The car build now hands it a file
URI to the verified copy in Downloads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-10-23 21:48:46 +02:00
co-authored by Claude Opus 5.5
parent 66b403acb8
commit b6f41079cc
4 changed files with 43 additions and 5 deletions
+11 -3
View File
@@ -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. 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 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 ## 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 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. 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 **On the car (SWI68, 2026-10-23) the session is refused:** the package manager answers
asks for a confirmation. `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.
+8
View File
@@ -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 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` 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). (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 - 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 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 strip, also after reconnecting: Allgo announces a smaller picture to the iPhone. 20 s after the
+2 -2
View File
@@ -34,8 +34,8 @@ android {
applicationId "com.stinnesbeck.nilsmg4" applicationId "com.stinnesbeck.nilsmg4"
minSdk 27 minSdk 27
targetSdk 36 targetSdk 36
versionCode 103 versionCode 104
versionName "2.19.4" versionName "2.19.5"
// Nothing about the server ships in the APK: its URL and ingest token are entered in // 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. // the app's Server tab (ServerConfig), and the database is only reachable by the server.
@@ -566,6 +566,28 @@ final class OtaUpdater {
} }
File publicApk = copyToPublicDownloads(apk); 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, Uri contentUri = FileProvider.getUriForFile(context,
context.getPackageName() + ".fileprovider", publicApk); context.getPackageName() + ".fileprovider", publicApk);
Intent install = new Intent(Intent.ACTION_VIEW) Intent install = new Intent(Intent.ACTION_VIEW)