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
108 lines
5.2 KiB
Java
108 lines
5.2 KiB
Java
package com.evsuite.abrp;
|
|
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageInstaller;
|
|
import android.util.Log;
|
|
import android.widget.Toast;
|
|
|
|
/**
|
|
* Where a {@link PackageInstaller} session reports what it did — UNSTABLE BUILDS ONLY.
|
|
*
|
|
* The session commit is asynchronous, so unlike the {@code pm install -r} it replaced, the
|
|
* install outcome cannot be the return value of the call that started it. It arrives here.
|
|
*
|
|
* STATUS_PENDING_USER_ACTION is the case worth understanding: the platform is saying it will
|
|
* install, but wants the driver to confirm. That happens when the app is not granted a silent
|
|
* install, and the only correct response is to launch the confirmation activity it hands us.
|
|
*
|
|
* Any other failure — on the car the session is refused, "Not allowed to install non-system
|
|
* apps on internal storage", and that answer can come here rather than as an exception — opens
|
|
* Android's installer screen with the verified APK, as {@link OtaUpdater#install} does for an
|
|
* immediate refusal. Only a cancel by the driver ends there.
|
|
*/
|
|
public final class OtaInstallResultReceiver extends BroadcastReceiver {
|
|
|
|
static final String ACTION_INSTALL_RESULT = "com.stinnesbeck.nilsmg4.OTA_INSTALL_RESULT";
|
|
static final String EXTRA_APK = "com.stinnesbeck.nilsmg4.extra.APK";
|
|
static final String EXTRA_SESSION = "com.stinnesbeck.nilsmg4.extra.SESSION";
|
|
|
|
private static final String TAG = "EVABRP.Update";
|
|
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
if (intent == null) return;
|
|
int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
|
|
String message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
|
Log.i(TAG, "OTA install result: status=" + status + " msg=" + message);
|
|
UpdateLog.record(context, "Install result: status " + status
|
|
+ (message != null ? ", " + message : ""));
|
|
int session = intent.getIntExtra(EXTRA_SESSION, -1);
|
|
if (session >= 0) OtaUpdater.noteAnswer(session);
|
|
String apkPath = intent.getStringExtra(EXTRA_APK);
|
|
|
|
switch (status) {
|
|
case PackageInstaller.STATUS_PENDING_USER_ACTION:
|
|
Intent confirm = intent.getParcelableExtra(Intent.EXTRA_INTENT);
|
|
if (confirm == null) {
|
|
UpdateLog.record(context, "No confirmation screen offered");
|
|
openInstallerScreen(context, apkPath, "no confirm intent");
|
|
return;
|
|
}
|
|
confirm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
try {
|
|
context.startActivity(confirm);
|
|
} catch (Throwable t) {
|
|
Log.e(TAG, "OTA confirm UI failed: " + t.getMessage());
|
|
UpdateLog.record(context, "Confirmation screen failed: " + t.getClass().getSimpleName());
|
|
openInstallerScreen(context, apkPath, t.getClass().getSimpleName());
|
|
}
|
|
break;
|
|
case PackageInstaller.STATUS_SUCCESS:
|
|
toast(context, context.getString(R.string.ota_install_success));
|
|
break;
|
|
case PackageInstaller.STATUS_FAILURE_ABORTED:
|
|
// The driver cancelled: nothing opens again.
|
|
toast(context, context.getString(R.string.ota_install_failed,
|
|
message != null ? message : "cancelled"));
|
|
break;
|
|
default:
|
|
// The status message is the package manager's own words — INSTALL_FAILED_*
|
|
// and friends — kept verbatim in the update log, because guessing at it is what
|
|
// made an earlier failure unreadable. Then the installer screen, which the car
|
|
// allows where it refuses the session.
|
|
openInstallerScreen(context, apkPath, message != null ? message : "status " + status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Opens Android's installer screen for the APK at [apkPath] off the main thread (it is
|
|
* copied to Downloads first); a toast with [why] when there is none or it cannot open.
|
|
*/
|
|
private void openInstallerScreen(Context context, String apkPath, String why) {
|
|
java.io.File apk = apkPath == null ? null : new java.io.File(apkPath);
|
|
if (apk == null || !apk.isFile()) {
|
|
toast(context, context.getString(R.string.ota_install_failed, why));
|
|
return;
|
|
}
|
|
PendingResult pending = goAsync();
|
|
Context app = context.getApplicationContext();
|
|
new Thread(() -> {
|
|
try {
|
|
if (!OtaUpdater.openInstallerScreen(app, apk)) {
|
|
new android.os.Handler(android.os.Looper.getMainLooper()).post(() ->
|
|
toast(app, app.getString(R.string.ota_install_failed, why)));
|
|
}
|
|
} finally {
|
|
pending.finish();
|
|
}
|
|
}, "ota-fallback").start();
|
|
}
|
|
|
|
private static void toast(Context context, String text) {
|
|
Toast.makeText(context.getApplicationContext(), text, Toast.LENGTH_LONG).show();
|
|
}
|
|
}
|