chore: auto-merge develop → main
Triggered by: 6d13b89 fix(kiosk): gateway install STATUS_FAILURE root cause
This commit is contained in:
@@ -826,70 +826,112 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
val params = android.content.pm.PackageInstaller.SessionParams(
|
val params = android.content.pm.PackageInstaller.SessionParams(
|
||||||
android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL
|
||||||
)
|
)
|
||||||
params.setAppPackageName(targetPkg)
|
// Note: setAppPackageName() is intentionally omitted — it causes STATUS_FAILURE (1)
|
||||||
|
// on some OEM/Android versions even when the package name is correct.
|
||||||
val sessionId = pi.createSession(params)
|
val sessionId = pi.createSession(params)
|
||||||
pi.openSession(sessionId).use { session ->
|
val session = pi.openSession(sessionId)
|
||||||
|
try {
|
||||||
file.inputStream().use { input ->
|
file.inputStream().use { input ->
|
||||||
session.openWrite("package", 0, file.length()).use { out ->
|
session.openWrite("package", 0, file.length()).use { out ->
|
||||||
input.copyTo(out)
|
input.copyTo(out)
|
||||||
session.fsync(out)
|
session.fsync(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val action = "it.dadaloop.evershelf.kiosk.SETUP_INSTALL_$sessionId"
|
} catch (e: Exception) {
|
||||||
val resultReceiver = object : BroadcastReceiver() {
|
try { session.abandon() } catch (_: Exception) {}
|
||||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
throw e
|
||||||
unregisterReceiver(this)
|
}
|
||||||
val status = intent?.getIntExtra(
|
// Do NOT close() the session after commit — it is now owned by the system.
|
||||||
android.content.pm.PackageInstaller.EXTRA_STATUS,
|
|
||||||
android.content.pm.PackageInstaller.STATUS_FAILURE
|
val action = "it.dadaloop.evershelf.kiosk.SETUP_INSTALL_$sessionId"
|
||||||
) ?: android.content.pm.PackageInstaller.STATUS_FAILURE
|
val resultReceiver = object : BroadcastReceiver() {
|
||||||
when (status) {
|
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||||
android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
val status = intent?.getIntExtra(
|
||||||
|
android.content.pm.PackageInstaller.EXTRA_STATUS,
|
||||||
|
android.content.pm.PackageInstaller.STATUS_FAILURE
|
||||||
|
) ?: android.content.pm.PackageInstaller.STATUS_FAILURE
|
||||||
|
|
||||||
|
when (status) {
|
||||||
|
android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||||
|
// Do NOT unregister here — on Android 11+ the final result
|
||||||
|
// (STATUS_SUCCESS or STATUS_FAILURE) arrives as a second broadcast
|
||||||
|
// to this same receiver AFTER the user confirms the dialog.
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val confirmIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||||
|
intent?.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
|
||||||
|
else intent?.getParcelableExtra(Intent.EXTRA_INTENT)
|
||||||
|
if (confirmIntent != null) {
|
||||||
|
pendingInstallFile = file
|
||||||
|
pendingInstallPkg = targetPkg
|
||||||
|
setGatewayUI("⏳", getString(R.string.install_installing),
|
||||||
|
getString(R.string.install_confirm_detail), 0xFF94a3b8.toInt(),
|
||||||
|
btnEnabled = false, progress = -1)
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
val confirmIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
startActivityForResult(confirmIntent, INSTALL_CONFIRM_REQUEST)
|
||||||
intent?.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
|
} else {
|
||||||
else intent?.getParcelableExtra(Intent.EXTRA_INTENT)
|
// No confirmation intent — give up gracefully
|
||||||
if (confirmIntent != null) {
|
unregisterReceiver(this)
|
||||||
pendingInstallFile = file
|
setGatewayUI("❌", getString(R.string.install_error_install),
|
||||||
pendingInstallPkg = targetPkg
|
"No confirmation intent", 0xFFf87171.toInt())
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
startActivityForResult(confirmIntent, INSTALL_CONFIRM_REQUEST)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
android.content.pm.PackageInstaller.STATUS_SUCCESS -> {
|
}
|
||||||
setGatewayUI("✅", getString(R.string.install_success), getString(R.string.install_success_detail), 0xFF34d399.toInt(), btnEnabled = false)
|
android.content.pm.PackageInstaller.STATUS_SUCCESS -> {
|
||||||
Handler(Looper.getMainLooper()).postDelayed({ checkGatewayStatus() }, 1500)
|
unregisterReceiver(this)
|
||||||
}
|
setGatewayUI("✅", getString(R.string.install_success),
|
||||||
android.content.pm.PackageInstaller.STATUS_FAILURE_INCOMPATIBLE,
|
getString(R.string.install_success_detail), 0xFF34d399.toInt(),
|
||||||
android.content.pm.PackageInstaller.STATUS_FAILURE_CONFLICT -> {
|
btnEnabled = false)
|
||||||
runOnUiThread { offerUninstallAndRetry(file, targetPkg) }
|
Handler(Looper.getMainLooper()).postDelayed({ checkGatewayStatus() }, 1500)
|
||||||
}
|
}
|
||||||
else -> {
|
android.content.pm.PackageInstaller.STATUS_FAILURE_INCOMPATIBLE,
|
||||||
val msg = intent?.getStringExtra(android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE) ?: "status=$status"
|
android.content.pm.PackageInstaller.STATUS_FAILURE_CONFLICT -> {
|
||||||
setGatewayUI("❌", getString(R.string.install_error_install), msg, 0xFFf87171.toInt())
|
unregisterReceiver(this)
|
||||||
ErrorReporter.reportMessage(
|
runOnUiThread { offerUninstallAndRetry(file, targetPkg) }
|
||||||
"install_failure",
|
}
|
||||||
"PackageInstaller failed: status=$status msg=$msg",
|
-1 /* STATUS_FAILURE_ABORTED */ -> {
|
||||||
mapOf("pkg" to targetPkg, "status" to status, "msg" to msg)
|
// User cancelled the install confirmation dialog — just reset UI
|
||||||
|
unregisterReceiver(this)
|
||||||
|
runOnUiThread { checkGatewayStatus() }
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
unregisterReceiver(this)
|
||||||
|
val msg = intent?.getStringExtra(
|
||||||
|
android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE
|
||||||
|
) ?: "status=$status"
|
||||||
|
setGatewayUI("❌", getString(R.string.install_error_install),
|
||||||
|
msg, 0xFFf87171.toInt())
|
||||||
|
ErrorReporter.reportMessage(
|
||||||
|
"install_failure",
|
||||||
|
"PackageInstaller failed: status=$status msg=$msg",
|
||||||
|
mapOf(
|
||||||
|
"pkg" to targetPkg,
|
||||||
|
"status" to status,
|
||||||
|
"msg" to msg,
|
||||||
|
"apk_kb" to (file.length() / 1024),
|
||||||
|
"android" to Build.VERSION.SDK_INT
|
||||||
)
|
)
|
||||||
val pkgInstalled = try { packageManager.getPackageInfo(targetPkg, 0); true } catch (_: Exception) { false }
|
)
|
||||||
if (pkgInstalled) runOnUiThread { offerUninstallAndRetry(file, targetPkg) }
|
val pkgInstalled = try {
|
||||||
}
|
packageManager.getPackageInfo(targetPkg, 0); true
|
||||||
|
} catch (_: Exception) { false }
|
||||||
|
if (pkgInstalled) runOnUiThread { offerUninstallAndRetry(file, targetPkg) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) RECEIVER_NOT_EXPORTED else 0
|
|
||||||
registerReceiver(resultReceiver, IntentFilter(action), flags)
|
|
||||||
val pi2 = PendingIntent.getBroadcast(
|
|
||||||
this, sessionId,
|
|
||||||
Intent(action).setPackage(packageName),
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
session.commit(pi2.intentSender)
|
|
||||||
}
|
}
|
||||||
|
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) RECEIVER_NOT_EXPORTED else 0
|
||||||
|
registerReceiver(resultReceiver, IntentFilter(action), flags)
|
||||||
|
val pi2 = PendingIntent.getBroadcast(
|
||||||
|
this, sessionId,
|
||||||
|
Intent(action).setPackage(packageName),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
session.commit(pi2.intentSender)
|
||||||
setGatewayUI("⏳", getString(R.string.install_installing), "", 0xFF94a3b8.toInt(), btnEnabled = false, progress = -1)
|
setGatewayUI("⏳", getString(R.string.install_installing), "", 0xFF94a3b8.toInt(), btnEnabled = false, progress = -1)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
setGatewayUI("❌", getString(R.string.install_error_install), e.message ?: "", 0xFFf87171.toInt())
|
setGatewayUI("❌", getString(R.string.install_error_install), e.message ?: "", 0xFFf87171.toInt())
|
||||||
|
ErrorReporter.reportMessage("install_packager_exception",
|
||||||
|
"installWithPackageInstaller exception for $targetPkg: ${e.message}",
|
||||||
|
mapOf("android" to Build.VERSION.SDK_INT, "apk_kb" to (file.length() / 1024)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -973,23 +1015,14 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
if (pendingApkDownloadUrl.isNotEmpty()) triggerApkDownload(pendingApkDownloadUrl)
|
if (pendingApkDownloadUrl.isNotEmpty()) triggerApkDownload(pendingApkDownloadUrl)
|
||||||
}
|
}
|
||||||
INSTALL_CONFIRM_REQUEST -> {
|
INSTALL_CONFIRM_REQUEST -> {
|
||||||
if (resultCode == RESULT_OK) {
|
// On Android 11+ the final install result (STATUS_SUCCESS / STATUS_FAILURE)
|
||||||
setGatewayUI("✅", getString(R.string.install_success), getString(R.string.install_success_detail), 0xFF34d399.toInt(), btnEnabled = false)
|
// arrives via the BroadcastReceiver, not via onActivityResult.
|
||||||
Handler(Looper.getMainLooper()).postDelayed({ checkGatewayStatus() }, 1500)
|
// RESULT_OK = user tapped "Install" in the system dialog (not "install succeeded")
|
||||||
} else {
|
// RESULT_CANCELED = user pressed Back without confirming
|
||||||
val f = pendingInstallFile
|
if (resultCode != RESULT_OK) {
|
||||||
val pkg = pendingInstallPkg
|
// User backed out of the confirmation — BroadcastReceiver will receive
|
||||||
if (f != null && f.exists() && pkg.isNotEmpty()) {
|
// STATUS_FAILURE_ABORTED (-1) and reset the UI automatically.
|
||||||
runOnUiThread {
|
// No action needed here.
|
||||||
AlertDialog.Builder(this)
|
|
||||||
.setTitle("⚠️ Installazione non riuscita")
|
|
||||||
.setMessage("Se c'è un conflitto di firma, devi disinstallare la versione precedente.\n\nDisinstalla ora?")
|
|
||||||
.setPositiveButton("Disinstalla") { _, _ ->
|
|
||||||
startActivityForResult(Intent(Intent.ACTION_DELETE, Uri.parse("package:$pkg")), UNINSTALL_REQUEST)
|
|
||||||
}
|
|
||||||
.setNegativeButton("Annulla", null).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UNINSTALL_REQUEST -> {
|
UNINSTALL_REQUEST -> {
|
||||||
|
|||||||
Reference in New Issue
Block a user