App 2.18.0: install from a USB stick
Settings -> App -> "Install from USB stick" (car build only). It looks in the top folder of every mounted volume (found from the app's own folder on it, since the head unit has no file picker) for NilsMG4-unstable-<version>.apk, takes the newest one newer than the installed version, checks it against its .sha256 sidecar and the package name, and installs it with the same PackageInstaller session as the over-the-air update. The stable build gets a stub and no button. Tested on the emulator: 2.18.0 installed a 2.18.1 from the storage's top folder without a confirmation, as the system user, so the OTA install step should not need one either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Installing a car build from a USB stick — UNSTABLE BUILDS ONLY. For development: copy
|
||||
* {@code NilsMG4-unstable-<version>.apk} and its {@code .sha256} (both from build-car) to the top
|
||||
* folder of a stick, plug it in, press the button on the Settings tab.
|
||||
*
|
||||
* The same checks as the over-the-air update apply, minus the server: the newest such file that
|
||||
* is newer than the installed version, a matching SHA-256 sidecar, this app's package, and the
|
||||
* installer's own signature check. The pure parts are here, unit-tested; UpdateHook does the IO.
|
||||
*/
|
||||
final class UsbUpdate {
|
||||
|
||||
private UsbUpdate() { }
|
||||
|
||||
/**
|
||||
* The top folder of the volume an app-specific folder lives on:
|
||||
* /storage/1234-ABCD/Android/data/<package>/files → /storage/1234-ABCD. Null when the path
|
||||
* does not have that shape. This is how a stick is found without a file picker, which the
|
||||
* head unit does not have.
|
||||
*/
|
||||
static File volumeRoot(File appFilesDir) {
|
||||
if (appFilesDir == null) return null;
|
||||
File data = appFilesDir.getParentFile() == null ? null : appFilesDir.getParentFile().getParentFile();
|
||||
File android = data == null ? null : data.getParentFile();
|
||||
if (!"files".equals(appFilesDir.getName()) || data == null || !"data".equals(data.getName())
|
||||
|| android == null || !"Android".equals(android.getName())) return null;
|
||||
return android.getParentFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* The car-build APK to install among [names]: the highest version that is newer than
|
||||
* [currentVersion], or null. Only names build-car writes count.
|
||||
*/
|
||||
static String pickNewest(String[] names, String currentVersion) {
|
||||
String best = null, bestVersion = null;
|
||||
if (names == null) return null;
|
||||
for (String name : names) {
|
||||
if (name == null || !name.toLowerCase(Locale.US).startsWith("nilsmg4-unstable-")) continue;
|
||||
String version = OtaUpdater.versionFromAssetName(name);
|
||||
if (version == null || !OtaUpdater.isNewer(version, currentVersion)) continue;
|
||||
if (bestVersion == null || OtaUpdater.isNewer(version, bestVersion)) {
|
||||
best = name;
|
||||
bestVersion = version;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user