From 22e506bd66db657bbe0bc370087a35b65f85fabc Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Sun, 3 May 2026 20:10:40 +0000 Subject: [PATCH] =?UTF-8?q?fix(kiosk):=204=20bug=20fix=20=E2=80=94=20unins?= =?UTF-8?q?tall=20loop,=20PHP=20check,=20APK=20validation,=20ErrorReporter?= =?UTF-8?q?=20init?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../dadaloop/evershelf/kiosk/KioskActivity.kt | 69 ++++++++++++++++--- .../app/src/main/res/values-de/strings.xml | 1 + .../app/src/main/res/values-it/strings.xml | 1 + .../app/src/main/res/values/strings.xml | 1 + 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/evershelf-kiosk/app/src/main/kotlin/it/dadaloop/evershelf/kiosk/KioskActivity.kt b/evershelf-kiosk/app/src/main/kotlin/it/dadaloop/evershelf/kiosk/KioskActivity.kt index e41e8ff..875d5ee 100644 --- a/evershelf-kiosk/app/src/main/kotlin/it/dadaloop/evershelf/kiosk/KioskActivity.kt +++ b/evershelf-kiosk/app/src/main/kotlin/it/dadaloop/evershelf/kiosk/KioskActivity.kt @@ -229,6 +229,8 @@ class KioskActivity : AppCompatActivity() { return@setOnClickListener } 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) } @@ -728,11 +730,32 @@ class KioskActivity : AppCompatActivity() { conn.requestMethod = "GET" val code = conn.responseCode 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 { - if (code in 200..399) { - showUrlStatus("✓ Connected successfully!", true) + if (apiOk) { + showUrlStatus("✅ Server EverShelf trovato e API attiva!", true) } 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) } 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 // 'evershelf-update.apk'). The URL contains 'gateway' or 'scale' when installing the // scale gateway; anything else is a kiosk self-update. @@ -1260,6 +1306,7 @@ class KioskActivity : AppCompatActivity() { .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() // release screen pin so uninstall UI can open startActivityForResult( Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$targetPkg")), UNINSTALL_REQUEST @@ -1275,7 +1322,7 @@ class KioskActivity : AppCompatActivity() { ) ?: "status=$status" setInstallUI( "\u274C", - getString(R.string.install_error_download), + getString(R.string.install_error_install), msg, 0xFFf87171.toInt(), btnEnabled = true, @@ -1284,9 +1331,7 @@ class KioskActivity : AppCompatActivity() { runOnUiThread { activeInstallBtn?.text = getString(R.string.install_btn_retry) } ErrorReporter.reportMessage("install_failure", "PackageInstaller status=$status msg=$msg pkg=$targetPkg") - // Generic failure on an already-installed package often means - // a signature conflict with the old version. Offer uninstall as - // last resort (only after the system installer already failed). + // Generic failure on an already-installed package: offer uninstall as last resort. val pkgInstalled = try { packageManager.getPackageInfo(targetPkg, 0); true } catch (_: Exception) { false } @@ -1301,6 +1346,7 @@ class KioskActivity : AppCompatActivity() { "bisogna prima disinstallarla.\n\n" + "Disinstalla ora e riprova automaticamente?") .setPositiveButton("Disinstalla e riprova") { _, _ -> + disableKioskLock() // release screen pin so uninstall UI can open startActivityForResult( Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$targetPkg")), @@ -1464,6 +1510,7 @@ class KioskActivity : AppCompatActivity() { .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.") .setPositiveButton("Disinstalla") { _, _ -> + disableKioskLock() // release screen pin so uninstall UI can open startActivityForResult( Intent(Intent.ACTION_DELETE, android.net.Uri.parse("package:$pkg")), 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) { + enableKioskLock() val f = pendingInstallFile val pkg = pendingInstallPkg 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) } } } diff --git a/evershelf-kiosk/app/src/main/res/values-de/strings.xml b/evershelf-kiosk/app/src/main/res/values-de/strings.xml index 1d1fda2..075e91e 100644 --- a/evershelf-kiosk/app/src/main/res/values-de/strings.xml +++ b/evershelf-kiosk/app/src/main/res/values-de/strings.xml @@ -28,6 +28,7 @@ Die App wurde aktualisiert. Download fehlgeschlagen Verbindung prüfen und erneut versuchen. + Installation fehlgeschlagen Aktiviere \'Unbekannte Apps installieren\' in den Einstellungen, dann komm zurück. ↩ Nochmal versuchen diff --git a/evershelf-kiosk/app/src/main/res/values-it/strings.xml b/evershelf-kiosk/app/src/main/res/values-it/strings.xml index 722122b..8ed3ada 100644 --- a/evershelf-kiosk/app/src/main/res/values-it/strings.xml +++ b/evershelf-kiosk/app/src/main/res/values-it/strings.xml @@ -28,6 +28,7 @@ L\'app è stata aggiornata. Download fallito Controlla la connessione e riprova. + Installazione fallita Abilita \'Installa app sconosciute\' nelle impostazioni, poi torna qui. ↩ Riprova diff --git a/evershelf-kiosk/app/src/main/res/values/strings.xml b/evershelf-kiosk/app/src/main/res/values/strings.xml index 6b2d495..3384770 100644 --- a/evershelf-kiosk/app/src/main/res/values/strings.xml +++ b/evershelf-kiosk/app/src/main/res/values/strings.xml @@ -27,6 +27,7 @@ L\'app è stata aggiornata. Download fallito Controlla la connessione e riprova. + Installazione fallita Abilita \'Installa app sconosciute\' nelle impostazioni, poi torna qui. ↩ Riprova