App 2.40.1: an update always reaches the installer screen
The car refuses the installer session, and only a refusal thrown at once opened the installer screen; one that came back through the session's result ended in a toast, so the update downloaded and then nothing seemed to happen. Now any result but success or the driver's cancel opens the installer screen with the verified APK, as does a confirmation that cannot be shown, and a session silent for 20 s is abandoned for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -464,23 +464,59 @@ final class OtaUpdater {
|
||||
static InstallResult install(Context context, File apk) {
|
||||
if (!archiveIsThisPackage(context, apk)) return InstallResult.UNREADABLE_ARCHIVE;
|
||||
try {
|
||||
commitSession(context, apk);
|
||||
int sessionId = commitSession(context, apk);
|
||||
watchForAnswer(context, sessionId, apk);
|
||||
return InstallResult.SESSION_STARTED;
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "OTA session install failed; trying system install UI", t);
|
||||
UpdateLog.record(context, "Installer session failed: " + t);
|
||||
try {
|
||||
launchInstallerActivity(context, apk);
|
||||
UpdateLog.record(context, "Opened the system installer instead");
|
||||
return InstallResult.INSTALL_UI_STARTED;
|
||||
} catch (Throwable fallback) {
|
||||
Log.w(TAG, "OTA install UI failed", fallback);
|
||||
UpdateLog.record(context, "System installer failed too: " + fallback);
|
||||
return InstallResult.SESSION_FAILED;
|
||||
}
|
||||
return openInstallerScreen(context, apk) ? InstallResult.INSTALL_UI_STARTED : InstallResult.SESSION_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
/** The installer screen for [apk], as the fallback; false if even that could not open. */
|
||||
static boolean openInstallerScreen(Context context, File apk) {
|
||||
try {
|
||||
launchInstallerActivity(context, apk);
|
||||
UpdateLog.record(context, "Opened the system installer instead");
|
||||
return true;
|
||||
} catch (Throwable fallback) {
|
||||
Log.w(TAG, "OTA install UI failed", fallback);
|
||||
UpdateLog.record(context, "System installer failed too: " + fallback);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** How long a committed session may stay silent before the installer screen is opened. */
|
||||
private static final long ANSWER_WAIT_MS = 20_000L;
|
||||
/** Sessions the platform has answered (any status), so the watch knows to stand down. */
|
||||
private static final java.util.Set<Integer> answered =
|
||||
java.util.Collections.synchronizedSet(new java.util.HashSet<>());
|
||||
|
||||
/** The platform said something about [sessionId]; called by {@link OtaInstallResultReceiver}. */
|
||||
static void noteAnswer(int sessionId) {
|
||||
answered.add(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* A committed session normally answers within a second or two: installed (the app
|
||||
* restarts), refused, or "confirm on screen". If it says nothing at all, the update would
|
||||
* end silently after its download, so the installer screen is opened instead.
|
||||
*/
|
||||
private static void watchForAnswer(Context context, int sessionId, File apk) {
|
||||
Context app = context.getApplicationContext();
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(() -> {
|
||||
if (answered.contains(sessionId)) return;
|
||||
UpdateLog.record(app, "No answer from the installer after " + ANSWER_WAIT_MS / 1000 + " s");
|
||||
try {
|
||||
app.getPackageManager().getPackageInstaller().abandonSession(sessionId);
|
||||
} catch (Throwable ignored) {
|
||||
// already gone
|
||||
}
|
||||
new Thread(() -> openInstallerScreen(app, apk), "ota-fallback").start();
|
||||
}, ANSWER_WAIT_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the archive parses and declares our own package name.
|
||||
*
|
||||
@@ -510,7 +546,8 @@ final class OtaUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
private static void commitSession(Context context, File apk) throws Exception {
|
||||
/** Commits [apk] to a new installer session and returns its id. */
|
||||
private static int commitSession(Context context, File apk) throws Exception {
|
||||
PackageInstaller installer = context.getPackageManager().getPackageInstaller();
|
||||
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
|
||||
@@ -533,7 +570,10 @@ final class OtaUpdater {
|
||||
}
|
||||
Intent callback = new Intent(context, OtaInstallResultReceiver.class)
|
||||
.setAction(OtaInstallResultReceiver.ACTION_INSTALL_RESULT)
|
||||
.setPackage(context.getPackageName());
|
||||
.setPackage(context.getPackageName())
|
||||
// Where the verified APK is, for the installer screen if the session fails.
|
||||
.putExtra(OtaInstallResultReceiver.EXTRA_APK, apk.getAbsolutePath())
|
||||
.putExtra(OtaInstallResultReceiver.EXTRA_SESSION, sessionId);
|
||||
// MUTABLE: the platform fills in EXTRA_STATUS and, when it wants the driver to
|
||||
// confirm, EXTRA_INTENT. An immutable PendingIntent would arrive empty.
|
||||
PendingIntent pending = PendingIntent.getBroadcast(context, sessionId, callback,
|
||||
@@ -543,6 +583,7 @@ final class OtaUpdater {
|
||||
session.commit(pending.getIntentSender());
|
||||
committed = true;
|
||||
Log.i(TAG, "OTA session " + sessionId + " committed (" + apk.length() + " bytes)");
|
||||
return sessionId;
|
||||
} finally {
|
||||
// Abandon failed staging, but always close our local handle after a commit too.
|
||||
if (!committed) {
|
||||
|
||||
Reference in New Issue
Block a user