fix(kiosk): 4 bug fix — uninstall loop, PHP check, APK validation, ErrorReporter init
Bug 1 — Uninstall loop (kiosk lock task blocks system uninstall UI):
startActivityForResult(ACTION_DELETE) was called while lock task was
active. The system uninstall activity is not in the lock task whitelist
so it either silently fails or creates an unresolvable loop.
Fix: call disableKioskLock() immediately before every ACTION_DELETE
intent (3 call sites). Call enableKioskLock() at the start of
onActivityResult(UNINSTALL_REQUEST) before retrying install.
Added 600 ms delay after uninstall so PackageManager finishes cleanup.
Bug 2 — Step 2 only checks HTTP connectivity, not PHP API:
testConnection() was checking the root URL only. A generic web server
could pass while the EverShelf PHP API was absent.
Fix: after HTTP 200-399 on the root URL, do a second GET to
/api/?action=check_update and check the response body contains
'latest_tag'|'webapp_version'|'ok'. Shows:
✅ Server EverShelf trovato e API attiva!
⚠ Server raggiungibile ma API PHP non trovata (codice N)
Bug 3 — STATUS_FAILURE=1 even after uninstall (invalid APK file):
GitHub DownloadManager follows redirects; if the release asset does
not exist yet, GitHub returns a 404 HTML page but DownloadManager
still reports STATUS_SUCCESSFUL. PackageInstaller then tries to parse
HTML as an APK and returns STATUS_FAILURE=1.
Fix: validate APK magic bytes (0x504B = 'PK') before calling
installWithPackageInstaller. If invalid: show error, delete corrupt
file, send ErrorReporter event, re-enable retry button.
Also renamed install error string to install_error_install (separate
from install_error_download) for clarity.
Bug 4 — ErrorReporter.serverBaseUrl empty during wizard install:
ErrorReporter.init() is called in onCreate() with the saved URL.
On first setup the URL is typed in step 2 and saved to prefs, but
ErrorReporter still has serverBaseUrl='' for the rest of that session.
Any install error in step 3 silently failed to POST.
Fix: call ErrorReporter.init(this, url) in btnStep2Next immediately
after prefs.edit().putString(KEY_URL, url) so step 3 has a live URL.
This commit is contained in:
@@ -229,6 +229,8 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
prefs.edit().putString(KEY_URL, url).apply()
|
prefs.edit().putString(KEY_URL, url).apply()
|
||||||
|
// Re-init ErrorReporter immediately so install errors in step 3 reach GitHub Issues.
|
||||||
|
ErrorReporter.init(this, url)
|
||||||
goToStep(3)
|
goToStep(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -728,11 +730,32 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
conn.requestMethod = "GET"
|
conn.requestMethod = "GET"
|
||||||
val code = conn.responseCode
|
val code = conn.responseCode
|
||||||
conn.disconnect()
|
conn.disconnect()
|
||||||
|
if (code !in 200..399) {
|
||||||
|
runOnUiThread { showUrlStatus("⚠ Server responded with code $code", false) }
|
||||||
|
return@Thread
|
||||||
|
}
|
||||||
|
// Second check: verify the EverShelf PHP API is actually present.
|
||||||
|
var apiOk = false
|
||||||
|
var apiCode = -1
|
||||||
|
try {
|
||||||
|
val base = url.trimEnd('/')
|
||||||
|
val apiConn = java.net.URL("$base/api/?action=check_update")
|
||||||
|
.openConnection() as java.net.HttpURLConnection
|
||||||
|
apiConn.requestMethod = "GET"
|
||||||
|
apiConn.connectTimeout = 5000
|
||||||
|
apiConn.readTimeout = 5000
|
||||||
|
apiCode = apiConn.responseCode
|
||||||
|
val body = apiConn.inputStream.bufferedReader().readText()
|
||||||
|
apiConn.disconnect()
|
||||||
|
apiOk = apiCode in 200..399 &&
|
||||||
|
(body.contains("latest_tag") || body.contains("webapp_version") || body.contains("ok"))
|
||||||
|
} catch (_: Exception) {}
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
if (code in 200..399) {
|
if (apiOk) {
|
||||||
showUrlStatus("✓ Connected successfully!", true)
|
showUrlStatus("✅ Server EverShelf trovato e API attiva!", true)
|
||||||
} else {
|
} else {
|
||||||
showUrlStatus("⚠ Server responded with code $code", false)
|
showUrlStatus("⚠ Server raggiungibile (HTTP $code) ma API PHP non trovata (codice $apiCode). " +
|
||||||
|
"Verifica che il server EverShelf sia installato correttamente.", false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1176,6 +1199,29 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Validate APK magic bytes (ZIP local file header = 0x504B0304).
|
||||||
|
// If GitHub returned a 404 HTML page, DownloadManager still reports SUCCESS
|
||||||
|
// but the file starts with '<' not 'PK' — this catches that case early.
|
||||||
|
val magic = try { file.inputStream().use { it.read(4) ; true }; // read to check open
|
||||||
|
file.inputStream().use { s -> val b = ByteArray(4); s.read(b); b }
|
||||||
|
} catch (_: Exception) { null }
|
||||||
|
val isApk = magic != null && magic[0] == 0x50.toByte() && magic[1] == 0x4B.toByte()
|
||||||
|
if (!isApk) {
|
||||||
|
setInstallUI(
|
||||||
|
"\u274C",
|
||||||
|
getString(R.string.install_error_download),
|
||||||
|
"Il file scaricato non è un APK valido (possibile 404 sulla release). " +
|
||||||
|
"Verifica che la release GitHub sia pubblicata.",
|
||||||
|
0xFFf87171.toInt(),
|
||||||
|
btnEnabled = true,
|
||||||
|
progress = -2
|
||||||
|
)
|
||||||
|
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
||||||
|
ErrorReporter.reportMessage("install_invalid_apk",
|
||||||
|
"Downloaded file is not a valid APK (bad magic bytes). URL=$pendingApkDownloadUrl size=${file.length()}")
|
||||||
|
file.delete() // remove corrupt file so next attempt re-downloads
|
||||||
|
return
|
||||||
|
}
|
||||||
// Derive the target package from the download URL (not the filename, which is always
|
// Derive the target package from the download URL (not the filename, which is always
|
||||||
// 'evershelf-update.apk'). The URL contains 'gateway' or 'scale' when installing the
|
// 'evershelf-update.apk'). The URL contains 'gateway' or 'scale' when installing the
|
||||||
// scale gateway; anything else is a kiosk self-update.
|
// scale gateway; anything else is a kiosk self-update.
|
||||||
@@ -1260,6 +1306,7 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
.setTitle("⚠️ Conflitto firma APK")
|
.setTitle("⚠️ Conflitto firma APK")
|
||||||
.setMessage("L'app installata usa una firma diversa.\n\nDisinstalla la versione precedente: al termine l'installazione riparte automaticamente.")
|
.setMessage("L'app installata usa una firma diversa.\n\nDisinstalla la versione precedente: al termine l'installazione riparte automaticamente.")
|
||||||
.setPositiveButton("Disinstalla") { _, _ ->
|
.setPositiveButton("Disinstalla") { _, _ ->
|
||||||
|
disableKioskLock() // release screen pin so uninstall UI can open
|
||||||
startActivityForResult(
|
startActivityForResult(
|
||||||
Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$targetPkg")),
|
Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$targetPkg")),
|
||||||
UNINSTALL_REQUEST
|
UNINSTALL_REQUEST
|
||||||
@@ -1275,7 +1322,7 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
) ?: "status=$status"
|
) ?: "status=$status"
|
||||||
setInstallUI(
|
setInstallUI(
|
||||||
"\u274C",
|
"\u274C",
|
||||||
getString(R.string.install_error_download),
|
getString(R.string.install_error_install),
|
||||||
msg,
|
msg,
|
||||||
0xFFf87171.toInt(),
|
0xFFf87171.toInt(),
|
||||||
btnEnabled = true,
|
btnEnabled = true,
|
||||||
@@ -1284,9 +1331,7 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) }
|
||||||
ErrorReporter.reportMessage("install_failure",
|
ErrorReporter.reportMessage("install_failure",
|
||||||
"PackageInstaller status=$status msg=$msg pkg=$targetPkg")
|
"PackageInstaller status=$status msg=$msg pkg=$targetPkg")
|
||||||
// Generic failure on an already-installed package often means
|
// Generic failure on an already-installed package: offer uninstall as last resort.
|
||||||
// a signature conflict with the old version. Offer uninstall as
|
|
||||||
// last resort (only after the system installer already failed).
|
|
||||||
val pkgInstalled = try {
|
val pkgInstalled = try {
|
||||||
packageManager.getPackageInfo(targetPkg, 0); true
|
packageManager.getPackageInfo(targetPkg, 0); true
|
||||||
} catch (_: Exception) { false }
|
} catch (_: Exception) { false }
|
||||||
@@ -1301,6 +1346,7 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
"bisogna prima disinstallarla.\n\n" +
|
"bisogna prima disinstallarla.\n\n" +
|
||||||
"Disinstalla ora e riprova automaticamente?")
|
"Disinstalla ora e riprova automaticamente?")
|
||||||
.setPositiveButton("Disinstalla e riprova") { _, _ ->
|
.setPositiveButton("Disinstalla e riprova") { _, _ ->
|
||||||
|
disableKioskLock() // release screen pin so uninstall UI can open
|
||||||
startActivityForResult(
|
startActivityForResult(
|
||||||
Intent(Intent.ACTION_DELETE,
|
Intent(Intent.ACTION_DELETE,
|
||||||
android.net.Uri.parse("package:$targetPkg")),
|
android.net.Uri.parse("package:$targetPkg")),
|
||||||
@@ -1464,6 +1510,7 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
.setTitle("⚠️ Installazione non riuscita")
|
.setTitle("⚠️ Installazione non riuscita")
|
||||||
.setMessage("Se hai visto un errore di conflitto firma, devi disinstallare la versione precedente.\n\nDisinstalla ora? L'installazione ripartirà automaticamente.")
|
.setMessage("Se hai visto un errore di conflitto firma, devi disinstallare la versione precedente.\n\nDisinstalla ora? L'installazione ripartirà automaticamente.")
|
||||||
.setPositiveButton("Disinstalla") { _, _ ->
|
.setPositiveButton("Disinstalla") { _, _ ->
|
||||||
|
disableKioskLock() // release screen pin so uninstall UI can open
|
||||||
startActivityForResult(
|
startActivityForResult(
|
||||||
Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$pkg")),
|
Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$pkg")),
|
||||||
UNINSTALL_REQUEST
|
UNINSTALL_REQUEST
|
||||||
@@ -1474,12 +1521,16 @@ class KioskActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Returned from uninstall screen — auto-retry the install with the saved APK file.
|
// Returned from uninstall screen — re-enable kiosk lock, then auto-retry install.
|
||||||
if (requestCode == UNINSTALL_REQUEST) {
|
if (requestCode == UNINSTALL_REQUEST) {
|
||||||
|
enableKioskLock()
|
||||||
val f = pendingInstallFile
|
val f = pendingInstallFile
|
||||||
val pkg = pendingInstallPkg
|
val pkg = pendingInstallPkg
|
||||||
if (f != null && f.exists() && pkg.isNotEmpty()) {
|
if (f != null && f.exists() && pkg.isNotEmpty()) {
|
||||||
installWithPackageInstaller(f, pkg)
|
// Small delay: give PackageManager time to finish processing the removal.
|
||||||
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
|
installWithPackageInstaller(f, pkg)
|
||||||
|
}, 600)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
<string name="install_success_detail">Die App wurde aktualisiert.</string>
|
<string name="install_success_detail">Die App wurde aktualisiert.</string>
|
||||||
<string name="install_error_download">Download fehlgeschlagen</string>
|
<string name="install_error_download">Download fehlgeschlagen</string>
|
||||||
<string name="install_error_download_detail">Verbindung prüfen und erneut versuchen.</string>
|
<string name="install_error_download_detail">Verbindung prüfen und erneut versuchen.</string>
|
||||||
|
<string name="install_error_install">Installation fehlgeschlagen</string>
|
||||||
<string name="install_perm_detail">Aktiviere \'Unbekannte Apps installieren\' in den Einstellungen, dann komm zurück.</string>
|
<string name="install_perm_detail">Aktiviere \'Unbekannte Apps installieren\' in den Einstellungen, dann komm zurück.</string>
|
||||||
<string name="install_btn_retry">↩ Nochmal versuchen</string>
|
<string name="install_btn_retry">↩ Nochmal versuchen</string>
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
<string name="install_success_detail">L\'app è stata aggiornata.</string>
|
<string name="install_success_detail">L\'app è stata aggiornata.</string>
|
||||||
<string name="install_error_download">Download fallito</string>
|
<string name="install_error_download">Download fallito</string>
|
||||||
<string name="install_error_download_detail">Controlla la connessione e riprova.</string>
|
<string name="install_error_download_detail">Controlla la connessione e riprova.</string>
|
||||||
|
<string name="install_error_install">Installazione fallita</string>
|
||||||
<string name="install_perm_detail">Abilita \'Installa app sconosciute\' nelle impostazioni, poi torna qui.</string>
|
<string name="install_perm_detail">Abilita \'Installa app sconosciute\' nelle impostazioni, poi torna qui.</string>
|
||||||
<string name="install_btn_retry">↩ Riprova</string>
|
<string name="install_btn_retry">↩ Riprova</string>
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
<string name="install_success_detail">L\'app è stata aggiornata.</string>
|
<string name="install_success_detail">L\'app è stata aggiornata.</string>
|
||||||
<string name="install_error_download">Download fallito</string>
|
<string name="install_error_download">Download fallito</string>
|
||||||
<string name="install_error_download_detail">Controlla la connessione e riprova.</string>
|
<string name="install_error_download_detail">Controlla la connessione e riprova.</string>
|
||||||
|
<string name="install_error_install">Installazione fallita</string>
|
||||||
<string name="install_perm_detail">Abilita \'Installa app sconosciute\' nelle impostazioni, poi torna qui.</string>
|
<string name="install_perm_detail">Abilita \'Installa app sconosciute\' nelle impostazioni, poi torna qui.</string>
|
||||||
<string name="install_btn_retry">↩ Riprova</string>
|
<string name="install_btn_retry">↩ Riprova</string>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user