chore: auto-merge develop → main
Triggered by: abeb87c fix(kiosk): fix APK install failure — session lifecycle, error details, issue reporting
This commit is contained in:
@@ -64,7 +64,14 @@ object ErrorReporter {
|
|||||||
val pi = context.packageManager.getPackageInfo(context.packageName, 0)
|
val pi = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||||
appVersion = pi.versionName ?: "unknown"
|
appVersion = pi.versionName ?: "unknown"
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {}
|
||||||
deviceInfo = "${Build.MANUFACTURER} ${Build.MODEL} (Android ${Build.VERSION.RELEASE})"
|
deviceInfo = buildString {
|
||||||
|
val mfr = Build.MANUFACTURER.takeIf { it.isNotBlank() && it != "unknown" }
|
||||||
|
?: Build.PRODUCT.takeIf { it.isNotBlank() && it != "unknown" }
|
||||||
|
?: Build.BOARD
|
||||||
|
val model = Build.MODEL.takeIf { it.isNotBlank() && it != "unknown" }
|
||||||
|
?: Build.HARDWARE
|
||||||
|
append("$mfr $model (Android ${Build.VERSION.RELEASE}/${Build.VERSION.SDK_INT})")
|
||||||
|
}
|
||||||
|
|
||||||
// Send any crash that was saved to prefs during a previous session
|
// Send any crash that was saved to prefs during a previous session
|
||||||
sendPendingCrash()
|
sendPendingCrash()
|
||||||
@@ -110,15 +117,17 @@ object ErrorReporter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Report a non-exception message (e.g. WebView page error, network failure).
|
* Report a non-exception message (e.g. WebView page error, network failure).
|
||||||
|
* @param forceReport if true, bypasses the in-session dedup so retries are always sent.
|
||||||
*/
|
*/
|
||||||
fun reportMessage(
|
fun reportMessage(
|
||||||
type: String,
|
type: String,
|
||||||
message: String,
|
message: String,
|
||||||
extra: Map<String, Any?> = emptyMap()
|
extra: Map<String, Any?> = emptyMap(),
|
||||||
|
forceReport: Boolean = false
|
||||||
) {
|
) {
|
||||||
val ctx = mutableMapOf<String, Any?>("device" to deviceInfo)
|
val ctx = mutableMapOf<String, Any?>("device" to deviceInfo)
|
||||||
ctx.putAll(extra)
|
ctx.putAll(extra)
|
||||||
reportAsync(type = type, message = message, stack = "", context = ctx)
|
reportAsync(type = type, message = message, stack = "", context = ctx, force = forceReport)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Internal ─────────────────────────────────────────────────────────────
|
// ── Internal ─────────────────────────────────────────────────────────────
|
||||||
@@ -128,10 +137,14 @@ object ErrorReporter {
|
|||||||
return key.hashCode().toString(16)
|
return key.hashCode().toString(16)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reportAsync(type: String, message: String, stack: String, context: Map<String, Any?>) {
|
private fun reportAsync(type: String, message: String, stack: String, context: Map<String, Any?>, force: Boolean = false) {
|
||||||
val fp = fingerprint(type, message)
|
val fp = fingerprint(type, message)
|
||||||
synchronized(sentFingerprints) {
|
if (!force) {
|
||||||
if (!sentFingerprints.add(fp)) return // already reported this session
|
synchronized(sentFingerprints) {
|
||||||
|
if (!sentFingerprints.add(fp)) return // already reported this session
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
synchronized(sentFingerprints) { sentFingerprints.add(fp) }
|
||||||
}
|
}
|
||||||
executor.execute { doPost(type, message, stack, context) }
|
executor.execute { doPost(type, message, stack, context) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -644,50 +644,110 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
try {
|
try {
|
||||||
val pi = packageManager.packageInstaller
|
val pi = packageManager.packageInstaller
|
||||||
val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
|
val params = PackageInstaller.SessionParams(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.INSTALL_RESULT_$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(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) ?: PackageInstaller.STATUS_FAILURE
|
// Do NOT close() the session after commit — it is now owned by the system.
|
||||||
when (status) {
|
val action = "it.dadaloop.evershelf.kiosk.INSTALL_RESULT_$sessionId"
|
||||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
val resultReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||||
|
val status = intent?.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) ?: PackageInstaller.STATUS_FAILURE
|
||||||
|
when (status) {
|
||||||
|
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||||
|
// Do NOT unregister here — the final result arrives as a second broadcast
|
||||||
|
@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
|
||||||
|
setInstallUI("\u23F3", getString(R.string.install_installing), getString(R.string.install_confirm_detail), 0xFF94a3b8.toInt(), btnEnabled = false)
|
||||||
@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)
|
unregisterReceiver(this)
|
||||||
if (confirmIntent != null) {
|
setInstallUI("\u274C", getString(R.string.install_error_install), "No confirmation intent", 0xFFf87171.toInt(), btnEnabled = true, progress = -2)
|
||||||
pendingInstallFile = file
|
|
||||||
pendingInstallPkg = targetPkg
|
|
||||||
setInstallUI("\u23F3", getString(R.string.install_installing), getString(R.string.install_confirm_detail), 0xFF94a3b8.toInt(), btnEnabled = false)
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
startActivityForResult(confirmIntent, INSTALL_CONFIRM_REQUEST)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
PackageInstaller.STATUS_SUCCESS -> {
|
}
|
||||||
setInstallUI("\u2705", getString(R.string.install_success), getString(R.string.install_success_detail), 0xFF34d399.toInt(), btnEnabled = false, progress = -2)
|
PackageInstaller.STATUS_SUCCESS -> {
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
unregisterReceiver(this)
|
||||||
updateBanner.visibility = View.GONE
|
setInstallUI("\u2705", getString(R.string.install_success), getString(R.string.install_success_detail), 0xFF34d399.toInt(), btnEnabled = false, progress = -2)
|
||||||
bannerProgressBar.visibility = View.GONE
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
}, 3000)
|
updateBanner.visibility = View.GONE
|
||||||
|
bannerProgressBar.visibility = View.GONE
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE,
|
||||||
|
PackageInstaller.STATUS_FAILURE_CONFLICT -> {
|
||||||
|
unregisterReceiver(this)
|
||||||
|
runOnUiThread {
|
||||||
|
pendingInstallFile = file
|
||||||
|
pendingInstallPkg = targetPkg
|
||||||
|
androidx.appcompat.app.AlertDialog.Builder(this@KioskActivity)
|
||||||
|
.setTitle("⚠️ Conflitto firma APK")
|
||||||
|
.setMessage("L'app installata usa una firma diversa.\n\nDisinstalla la versione precedente: al termine l'installazione riparte automaticamente.")
|
||||||
|
.setPositiveButton("Disinstalla") { _, _ ->
|
||||||
|
disableKioskLock()
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
startActivityForResult(Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$targetPkg")), UNINSTALL_REQUEST)
|
||||||
|
}
|
||||||
|
.setNegativeButton("Annulla", null).show()
|
||||||
}
|
}
|
||||||
PackageInstaller.STATUS_FAILURE_INCOMPATIBLE,
|
}
|
||||||
PackageInstaller.STATUS_FAILURE_CONFLICT -> {
|
-1 /* STATUS_FAILURE_ABORTED */ -> {
|
||||||
|
unregisterReceiver(this)
|
||||||
|
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
unregisterReceiver(this)
|
||||||
|
val msg = intent?.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) ?: ""
|
||||||
|
val deviceLabel = buildDeviceLabel()
|
||||||
|
val diagInfo = buildString {
|
||||||
|
appendLine("Status: $status — ${installStatusHint(status)}")
|
||||||
|
if (msg.isNotEmpty()) appendLine("Msg: $msg")
|
||||||
|
appendLine("PKG: $targetPkg")
|
||||||
|
appendLine("APK: ${file.length() / 1024} KB")
|
||||||
|
appendLine("Android: ${Build.VERSION.SDK_INT} (${Build.VERSION.RELEASE})")
|
||||||
|
appendLine("Device: $deviceLabel")
|
||||||
|
}
|
||||||
|
setInstallUI("\u274C", getString(R.string.install_error_install),
|
||||||
|
diagInfo.trim(), 0xFFf87171.toInt(), btnEnabled = true, progress = -2)
|
||||||
|
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
||||||
|
ErrorReporter.reportMessage(
|
||||||
|
"install_failure",
|
||||||
|
"PackageInstaller status=$status pkg=$targetPkg android=${Build.VERSION.SDK_INT}",
|
||||||
|
mapOf(
|
||||||
|
"pkg" to targetPkg,
|
||||||
|
"status" to status,
|
||||||
|
"msg" to msg,
|
||||||
|
"apk_kb" to (file.length() / 1024),
|
||||||
|
"android" to Build.VERSION.SDK_INT,
|
||||||
|
"device" to deviceLabel
|
||||||
|
),
|
||||||
|
forceReport = true
|
||||||
|
)
|
||||||
|
val pkgInstalled = try { packageManager.getPackageInfo(targetPkg, 0); true } catch (_: Exception) { false }
|
||||||
|
if (pkgInstalled) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
pendingInstallFile = file
|
pendingInstallFile = file
|
||||||
pendingInstallPkg = targetPkg
|
pendingInstallPkg = targetPkg
|
||||||
androidx.appcompat.app.AlertDialog.Builder(this@KioskActivity)
|
androidx.appcompat.app.AlertDialog.Builder(this@KioskActivity)
|
||||||
.setTitle("⚠️ Conflitto firma APK")
|
.setTitle("⚠️ Installazione fallita (status=$status)")
|
||||||
.setMessage("L'app installata usa una firma diversa.\n\nDisinstalla la versione precedente: al termine l'installazione riparte automaticamente.")
|
.setMessage(diagInfo.trim())
|
||||||
.setPositiveButton("Disinstalla") { _, _ ->
|
.setPositiveButton("Disinstalla e riprova") { _, _ ->
|
||||||
disableKioskLock()
|
disableKioskLock()
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
startActivityForResult(Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$targetPkg")), UNINSTALL_REQUEST)
|
startActivityForResult(Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$targetPkg")), UNINSTALL_REQUEST)
|
||||||
@@ -695,48 +755,48 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
.setNegativeButton("Annulla", null).show()
|
.setNegativeButton("Annulla", null).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
val msg = intent?.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) ?: "status=$status"
|
|
||||||
setInstallUI("\u274C", getString(R.string.install_error_install), msg, 0xFFf87171.toInt(), btnEnabled = true, progress = -2)
|
|
||||||
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
|
||||||
ErrorReporter.reportMessage("install_failure", "PackageInstaller status=$status msg=$msg pkg=$targetPkg")
|
|
||||||
val pkgInstalled = try { packageManager.getPackageInfo(targetPkg, 0); true } catch (_: Exception) { false }
|
|
||||||
if (pkgInstalled) {
|
|
||||||
runOnUiThread {
|
|
||||||
pendingInstallFile = file
|
|
||||||
pendingInstallPkg = targetPkg
|
|
||||||
androidx.appcompat.app.AlertDialog.Builder(this@KioskActivity)
|
|
||||||
.setTitle("⚠️ Installazione fallita")
|
|
||||||
.setMessage("Installazione fallita (status=$status).\n\nDisinstalla la versione precedente e riprova?")
|
|
||||||
.setPositiveButton("Disinstalla e riprova") { _, _ ->
|
|
||||||
disableKioskLock()
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
startActivityForResult(Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$targetPkg")), UNINSTALL_REQUEST)
|
|
||||||
}
|
|
||||||
.setNegativeButton("Annulla", null).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)
|
||||||
setInstallUI("\u23F3", getString(R.string.install_installing), getString(R.string.install_installing), 0xFF94a3b8.toInt(), btnEnabled = false, progress = -1)
|
setInstallUI("\u23F3", getString(R.string.install_installing), getString(R.string.install_installing), 0xFF94a3b8.toInt(), btnEnabled = false, progress = -1)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
setInstallUI("\u274C", getString(R.string.install_error_download), e.message ?: "", 0xFFf87171.toInt(), btnEnabled = true, progress = -2)
|
setInstallUI("\u274C", getString(R.string.install_error_install), e.message ?: "", 0xFFf87171.toInt(), btnEnabled = true, progress = -2)
|
||||||
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
||||||
ErrorReporter.reportMessage("install_packager_exception", "installWithPackageInstaller exception for $targetPkg: ${e.message}")
|
ErrorReporter.reportMessage("install_packager_exception",
|
||||||
|
"installWithPackageInstaller exception for $targetPkg: ${e.message}",
|
||||||
|
forceReport = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildDeviceLabel(): String {
|
||||||
|
val mfr = Build.MANUFACTURER.takeIf { it.isNotBlank() && it != "unknown" }
|
||||||
|
?: Build.PRODUCT.takeIf { it.isNotBlank() && it != "unknown" }
|
||||||
|
?: Build.BOARD
|
||||||
|
val model = Build.MODEL.takeIf { it.isNotBlank() && it != "unknown" }
|
||||||
|
?: Build.HARDWARE
|
||||||
|
return "$mfr $model"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installStatusHint(status: Int): String = when (status) {
|
||||||
|
1 -> "Errore generico (APK incompatibile con questo dispositivo o versione Android)"
|
||||||
|
2 -> "Bloccato da policy o da un'altra app in corso"
|
||||||
|
3 -> "Annullato dall'utente"
|
||||||
|
4 -> "APK non valido o corrotto"
|
||||||
|
5 -> "Conflitto: versione precedente con firma diversa"
|
||||||
|
6 -> "Spazio insufficiente"
|
||||||
|
7 -> "Incompatibile con questa versione di Android"
|
||||||
|
else -> "Errore sconosciuto"
|
||||||
|
}
|
||||||
|
|
||||||
// ── Error Page ────────────────────────────────────────────────────────
|
// ── Error Page ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private fun errorPageHtml(): String {
|
private fun errorPageHtml(): String {
|
||||||
|
|||||||
@@ -913,28 +913,48 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
unregisterReceiver(this)
|
unregisterReceiver(this)
|
||||||
val msg = intent?.getStringExtra(
|
val msg = intent?.getStringExtra(
|
||||||
android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE
|
android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE
|
||||||
) ?: "status=$status"
|
) ?: ""
|
||||||
|
val deviceLabel = buildString {
|
||||||
|
val mfr = Build.MANUFACTURER.takeIf { it.isNotBlank() && it != "unknown" }
|
||||||
|
?: Build.PRODUCT.takeIf { it.isNotBlank() && it != "unknown" }
|
||||||
|
?: Build.BOARD
|
||||||
|
val model = Build.MODEL.takeIf { it.isNotBlank() && it != "unknown" }
|
||||||
|
?: Build.HARDWARE
|
||||||
|
append("$mfr $model")
|
||||||
|
}
|
||||||
|
val hint = when (status) {
|
||||||
|
1 -> "APK incompatibile con questo dispositivo o versione Android"
|
||||||
|
2 -> "Bloccato da policy o da un'altra installazione in corso"
|
||||||
|
3 -> "Annullato"
|
||||||
|
4 -> "APK non valido o corrotto"
|
||||||
|
5 -> "Conflitto: versione precedente con firma diversa"
|
||||||
|
6 -> "Spazio insufficiente"
|
||||||
|
7 -> "Incompatibile con questa versione di Android"
|
||||||
|
else -> "Errore sconosciuto"
|
||||||
|
}
|
||||||
val diagInfo = buildString {
|
val diagInfo = buildString {
|
||||||
appendLine("Status: $status")
|
appendLine("❌ Status $status: $hint")
|
||||||
appendLine("Msg: $msg")
|
if (msg.isNotEmpty()) appendLine("Dettaglio: $msg")
|
||||||
appendLine("PKG: $targetPkg")
|
appendLine("Pacchetto: $targetPkg")
|
||||||
appendLine("APK: ${file.length() / 1024} KB")
|
appendLine("APK: ${file.length() / 1024} KB")
|
||||||
appendLine("Android: ${Build.VERSION.SDK_INT} (${Build.VERSION.RELEASE})")
|
appendLine("Android: ${Build.VERSION.SDK_INT} (${Build.VERSION.RELEASE})")
|
||||||
appendLine("Device: ${Build.MANUFACTURER} ${Build.MODEL}")
|
appendLine("Dispositivo: $deviceLabel")
|
||||||
}
|
}
|
||||||
setGatewayUI("❌", getString(R.string.install_error_install),
|
setGatewayUI("❌", getString(R.string.install_error_install),
|
||||||
msg, 0xFFf87171.toInt())
|
diagInfo.trim(), 0xFFf87171.toInt())
|
||||||
ErrorReporter.reportMessage(
|
ErrorReporter.reportMessage(
|
||||||
"install_failure",
|
"install_failure",
|
||||||
"PackageInstaller failed: status=$status msg=$msg",
|
"PackageInstaller status=$status pkg=$targetPkg android=${Build.VERSION.SDK_INT}",
|
||||||
mapOf(
|
mapOf(
|
||||||
"pkg" to targetPkg,
|
"pkg" to targetPkg,
|
||||||
"status" to status,
|
"status" to status,
|
||||||
|
"hint" to hint,
|
||||||
"msg" to msg,
|
"msg" to msg,
|
||||||
"apk_kb" to (file.length() / 1024),
|
"apk_kb" to (file.length() / 1024),
|
||||||
"android" to Build.VERSION.SDK_INT,
|
"android" to Build.VERSION.SDK_INT,
|
||||||
"device" to "${Build.MANUFACTURER} ${Build.MODEL}"
|
"device" to deviceLabel
|
||||||
)
|
),
|
||||||
|
forceReport = true
|
||||||
)
|
)
|
||||||
val pkgInstalled = try {
|
val pkgInstalled = try {
|
||||||
packageManager.getPackageInfo(targetPkg, 0); true
|
packageManager.getPackageInfo(targetPkg, 0); true
|
||||||
@@ -943,7 +963,6 @@ class SetupActivity : AppCompatActivity() {
|
|||||||
if (pkgInstalled) {
|
if (pkgInstalled) {
|
||||||
offerUninstallAndRetry(file, targetPkg)
|
offerUninstallAndRetry(file, targetPkg)
|
||||||
} else {
|
} else {
|
||||||
// Show diagnostic dialog with Retry button
|
|
||||||
AlertDialog.Builder(this@SetupActivity)
|
AlertDialog.Builder(this@SetupActivity)
|
||||||
.setTitle("❌ Installazione fallita (status=$status)")
|
.setTitle("❌ Installazione fallita (status=$status)")
|
||||||
.setMessage(diagInfo.trim())
|
.setMessage(diagInfo.trim())
|
||||||
|
|||||||
Reference in New Issue
Block a user