fix(kiosk): fallback Intent.ACTION_VIEW when PackageInstaller STATUS=1 + fix self-update URL

- SetupActivity: catch STATUS_FAILURE=1 separately and immediately retry with
  Intent.ACTION_VIEW (system installer dialog) instead of showing a dead error.
  STATUS=1 is a generic PackageInstaller failure that can happen on many
  Android 14 devices even with a valid APK, but the system installer handles it.
- SetupActivity: remove misleading 'incompatibile' hint for status=1 (was wrong;
  STATUS_FAILURE_INCOMPATIBLE = 7, not 1).
- SetupActivity: deduplicate buildDeviceLabel() to shared private method
- KioskActivity: fix KIOSK_DOWNLOAD_URL to point to kiosk-latest release
  (was pointing to 'latest' which only has the gateway APK, so self-update
  was silently broken).
- Bump version 1.4.0 -> 1.5.0 (versionCode 5 -> 6)
This commit is contained in:
dadaloop82
2026-05-05 05:50:22 +00:00
parent abeb87c536
commit 0f0ce684f1
3 changed files with 80 additions and 13 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "it.dadaloop.evershelf.kiosk" applicationId = "it.dadaloop.evershelf.kiosk"
minSdk = 24 minSdk = 24
targetSdk = 34 targetSdk = 34
versionCode = 5 versionCode = 6
versionName = "1.4.0" versionName = "1.5.0"
} }
signingConfigs { signingConfigs {
@@ -99,7 +99,7 @@ class KioskActivity : AppCompatActivity() {
private const val KEY_SCREENSAVER = "screensaver_enabled" private const val KEY_SCREENSAVER = "screensaver_enabled"
private const val GATEWAY_PACKAGE = "it.dadaloop.evershelf.scalegate" private const val GATEWAY_PACKAGE = "it.dadaloop.evershelf.scalegate"
private const val GATEWAY_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/latest/download/evershelf-scale-gateway.apk" private const val GATEWAY_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/latest/download/evershelf-scale-gateway.apk"
private const val KIOSK_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/latest/download/evershelf-kiosk.apk" private const val KIOSK_DOWNLOAD_URL = "https://github.com/dadaloop82/EverShelf/releases/download/kiosk-latest/evershelf-kiosk.apk"
private const val SPLASH_DURATION = 1500L private const val SPLASH_DURATION = 1500L
private const val GITHUB_RELEASES_API = "https://api.github.com/repos/dadaloop82/EverShelf/releases/latest" private const val GITHUB_RELEASES_API = "https://api.github.com/repos/dadaloop82/EverShelf/releases/latest"
} }
@@ -128,6 +128,7 @@ class SetupActivity : AppCompatActivity() {
private const val INSTALL_CONFIRM_REQUEST = 2002 private const val INSTALL_CONFIRM_REQUEST = 2002
private const val UNINSTALL_REQUEST = 2003 private const val UNINSTALL_REQUEST = 2003
private const val PERMISSION_REQUEST_CODE = 2004 private const val PERMISSION_REQUEST_CODE = 2004
private const val INSTALL_FALLBACK_REQUEST = 2005
fun applyLocale(base: Context, lang: String): Context { fun applyLocale(base: Context, lang: String): Context {
val locale = Locale(lang) val locale = Locale(lang)
@@ -909,28 +910,37 @@ class SetupActivity : AppCompatActivity() {
unregisterReceiver(this) unregisterReceiver(this)
runOnUiThread { checkGatewayStatus() } runOnUiThread { checkGatewayStatus() }
} }
android.content.pm.PackageInstaller.STATUS_FAILURE -> {
// Generic failure (status=1): PackageInstaller can't install on this
// device/config. Fall back to system Intent.ACTION_VIEW installer UI.
unregisterReceiver(this)
ErrorReporter.reportMessage(
"install_failure",
"PackageInstaller STATUS_FAILURE=1, trying ACTION_VIEW fallback",
mapOf(
"pkg" to targetPkg,
"apk_kb" to (file.length() / 1024),
"android" to Build.VERSION.SDK_INT,
"device" to buildDeviceLabel()
),
forceReport = true
)
runOnUiThread { tryFallbackInstall(file, targetPkg) }
}
else -> { else -> {
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
) ?: "" ) ?: ""
val deviceLabel = buildString { val deviceLabel = buildDeviceLabel()
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) { val hint = when (status) {
1 -> "APK incompatibile con questo dispositivo o versione Android"
2 -> "Bloccato da policy o da un'altra installazione in corso" 2 -> "Bloccato da policy o da un'altra installazione in corso"
3 -> "Annullato" 3 -> "Annullato"
4 -> "APK non valido o corrotto" 4 -> "APK non valido o corrotto"
5 -> "Conflitto: versione precedente con firma diversa" 5 -> "Conflitto: versione precedente con firma diversa"
6 -> "Spazio insufficiente" 6 -> "Spazio insufficiente"
7 -> "Incompatibile con questa versione di Android" 7 -> "Incompatibile con questa versione di Android"
else -> "Errore sconosciuto" else -> "Errore sconosciuto (status=$status)"
} }
val diagInfo = buildString { val diagInfo = buildString {
appendLine("❌ Status $status: $hint") appendLine("❌ Status $status: $hint")
@@ -1101,6 +1111,63 @@ class SetupActivity : AppCompatActivity() {
Handler(Looper.getMainLooper()).postDelayed({ installWithPackageInstaller(f, pkg) }, 600) Handler(Looper.getMainLooper()).postDelayed({ installWithPackageInstaller(f, pkg) }, 600)
} }
} }
INSTALL_FALLBACK_REQUEST -> {
// System package installer returned — check if the package is now installed
Handler(Looper.getMainLooper()).postDelayed({
val installed = try { packageManager.getPackageInfo(pendingInstallPkg, 0); true } catch (_: Exception) { false }
if (installed) {
setGatewayUI("", getString(R.string.install_success),
getString(R.string.install_success_detail), 0xFF34d399.toInt(), btnEnabled = false)
Handler(Looper.getMainLooper()).postDelayed({ checkGatewayStatus() }, 1500)
} else {
checkGatewayStatus()
}
}, 800)
}
} }
} }
private fun tryFallbackInstall(file: java.io.File, targetPkg: String) {
try {
val uri = androidx.core.content.FileProvider.getUriForFile(
this, "$packageName.provider", file
)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "application/vnd.android.package-archive")
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
}
pendingInstallFile = file
pendingInstallPkg = targetPkg
setGatewayUI("", getString(R.string.install_installing),
"Conferma l'installazione nella finestra di sistema...",
0xFF94a3b8.toInt(), btnEnabled = false, progress = -1)
@Suppress("DEPRECATION")
startActivityForResult(intent, INSTALL_FALLBACK_REQUEST)
} catch (e: Exception) {
val deviceLabel = buildDeviceLabel()
val diagInfo = buildString {
appendLine("❌ PackageInstaller status=1 e fallback non riuscito")
appendLine("Errore: ${e.message}")
appendLine("Android: ${Build.VERSION.SDK_INT} (${Build.VERSION.RELEASE})")
appendLine("Dispositivo: $deviceLabel")
}
setGatewayUI("", getString(R.string.install_error_install),
diagInfo.trim(), 0xFFf87171.toInt())
ErrorReporter.reportMessage(
"install_fallback_exception",
"tryFallbackInstall failed: ${e.message}",
mapOf("android" to Build.VERSION.SDK_INT, "device" to deviceLabel),
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"
}
} }