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(); } }