kiosk: gateway auto-launch, update auto-download, gateway setup UX

- KioskActivity: move launchGatewayInBackground() BEFORE enableKioskLock() so
  Android's lock-task restriction does not block starting the gateway Activity
- KioskActivity: webView.clearCache(true) before loadUrl — no caching
- KioskActivity: checkForUpdates() uses proper semver > comparison (not !=)
  to avoid false-positive 'update available' when already up-to-date
- KioskActivity: showNativeUpdateBanner() removed 30s auto-hide, now auto-
  triggers download immediately when update detected
- SetupActivity: onResume() re-checks gateway status when returning from gateway
  config (user opens gateway, configures it, presses back → wizard refreshes)
- SetupActivity: checkGatewayStatus() probes TCP 127.0.0.1:8765 to show whether
  gateway is actually running, with clear 'not running' warning to configure first
- SettingsActivity: same TCP probe for live gateway status in settings screen
- build.gradle.kts: versionCode 9, versionName 1.5.3
This commit is contained in:
dadaloop82
2026-05-05 16:48:32 +00:00
parent 30aa2db0b9
commit 8ee6fe8770
4 changed files with 76 additions and 8 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 = 8 versionCode = 9
versionName = "1.5.2" versionName = "1.5.3"
} }
signingConfigs { signingConfigs {
@@ -312,6 +312,10 @@ class KioskActivity : AppCompatActivity() {
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
private fun launchWebView() { private fun launchWebView() {
// Start gateway BEFORE entering kiosk lock — in lock task mode Android blocks
// startActivity() for other packages, so the gateway would never launch.
launchGatewayInBackground()
// Ensure kiosk lock and permissions are active // Ensure kiosk lock and permissions are active
enableKioskLock() enableKioskLock()
requestAllPermissions() requestAllPermissions()
@@ -432,9 +436,9 @@ class KioskActivity : AppCompatActivity() {
}, "_kioskBridge") }, "_kioskBridge")
val url = prefs.getString(KEY_URL, "http://evershelf.local") ?: "http://evershelf.local" val url = prefs.getString(KEY_URL, "http://evershelf.local") ?: "http://evershelf.local"
webView.clearCache(true)
webView.loadUrl(url) webView.loadUrl(url)
launchGatewayInBackground()
applyScreensaverFlag() applyScreensaverFlag()
} }
@@ -513,6 +517,19 @@ class KioskActivity : AppCompatActivity() {
val norm = { v: String -> v.trimStart('v') } val norm = { v: String -> v.trimStart('v') }
val isSemver = latestTag.trimStart('v').matches(Regex("\\d+\\.\\d+.*")) val isSemver = latestTag.trimStart('v').matches(Regex("\\d+\\.\\d+.*"))
// Compare semver: returns true if `remote` is strictly greater than `local`
fun semverNewer(remote: String, local: String): Boolean {
val r = remote.split(".").map { it.filter(Char::isDigit).toIntOrNull() ?: 0 }
val l = local.split(".").map { it.filter(Char::isDigit).toIntOrNull() ?: 0 }
val len = maxOf(r.size, l.size)
for (i in 0 until len) {
val rv = r.getOrElse(i) { 0 }
val lv = l.getOrElse(i) { 0 }
if (rv != lv) return rv > lv
}
return false
}
val assets = json.optJSONArray("assets") val assets = json.optJSONArray("assets")
var kioskApkUrl = "" var kioskApkUrl = ""
var gatewayApkUrl = "" var gatewayApkUrl = ""
@@ -527,9 +544,9 @@ class KioskActivity : AppCompatActivity() {
} }
val kioskNeedsUpdate = kioskApkUrl.isNotEmpty() && currentKiosk.isNotEmpty() && val kioskNeedsUpdate = kioskApkUrl.isNotEmpty() && currentKiosk.isNotEmpty() &&
(!isSemver || norm(latestTag) != norm(currentKiosk)) (!isSemver || semverNewer(norm(latestTag), norm(currentKiosk)))
val gatewayNeedsUpdate = currentGateway != null && gatewayApkUrl.isNotEmpty() && val gatewayNeedsUpdate = currentGateway != null && gatewayApkUrl.isNotEmpty() &&
(!isSemver || norm(latestTag) != norm(currentGateway)) (!isSemver || semverNewer(norm(latestTag), norm(currentGateway)))
if (!kioskNeedsUpdate && !gatewayNeedsUpdate) return@Thread if (!kioskNeedsUpdate && !gatewayNeedsUpdate) return@Thread
@@ -555,7 +572,9 @@ class KioskActivity : AppCompatActivity() {
pendingApkDownloadUrl = apkDownloadUrl pendingApkDownloadUrl = apkDownloadUrl
tvUpdateMessage.text = "⬆️ Aggiornamento disponibile: $message" tvUpdateMessage.text = "⬆️ Aggiornamento disponibile: $message"
updateBanner.visibility = View.VISIBLE updateBanner.visibility = View.VISIBLE
updateBanner.postDelayed({ updateBanner.visibility = View.GONE }, 30_000) // Auto-start download immediately — no timer hide, stays until done or dismissed
activeInstallBtn = btnInstallUpdate
triggerApkDownload(apkDownloadUrl)
} }
// ── APK Download + Install ───────────────────────────────────────────── // ── APK Download + Install ─────────────────────────────────────────────
@@ -77,6 +77,25 @@ class SettingsActivity : AppCompatActivity() {
val intent = packageManager.getLaunchIntentForPackage(GATEWAY_PACKAGE) val intent = packageManager.getLaunchIntentForPackage(GATEWAY_PACKAGE)
if (intent != null) startActivity(intent) if (intent != null) startActivity(intent)
} }
// Probe WebSocket port in background to show live status
Thread {
val running = try {
java.net.Socket().use { s ->
s.connect(java.net.InetSocketAddress("127.0.0.1", 8765), 1200); true
}
} catch (_: Exception) { false }
runOnUiThread {
if (running) {
statusView.text = "Attivo ✅"
statusView.setTextColor(0xFF34d399.toInt())
deviceView.text = "Gateway in ascolto su ws://127.0.0.1:8765"
} else {
statusView.text = "Installato, non avviato ⚠️"
statusView.setTextColor(0xFFfbbf24.toInt())
deviceView.text = "Premi \"Apri Gateway\" per avviarlo e configurarlo"
}
}
}.start()
} else { } else {
btnConfigureGateway.visibility = android.view.View.GONE btnConfigureGateway.visibility = android.view.View.GONE
} }
@@ -189,6 +189,14 @@ class SetupActivity : AppCompatActivity() {
super.onDestroy() super.onDestroy()
} }
override fun onResume() {
super.onResume()
// When returning from the gateway app (after pressing "Configura"), refresh status
if (currentStep == 4 && gatewayInstallCard.visibility == View.VISIBLE) {
checkGatewayStatus()
}
}
// ── Binding ──────────────────────────────────────────────────────────── // ── Binding ────────────────────────────────────────────────────────────
private fun bindViews() { private fun bindViews() {
@@ -701,12 +709,34 @@ class SetupActivity : AppCompatActivity() {
if (isGatewayInstalled()) { if (isGatewayInstalled()) {
gatewayStatusIcon.text = "" gatewayStatusIcon.text = ""
gatewayStatusText.text = getString(R.string.wizard_gateway_installed) gatewayStatusText.text = getString(R.string.wizard_gateway_installed)
gatewayStatusDetail.text = getString(R.string.wizard_gateway_installed_detail) gatewayStatusDetail.text = "⏳ Verifica connessione in corso..."
gatewayStatusDetail.setTextColor(0xFF34d399.toInt()) gatewayStatusDetail.setTextColor(0xFF94a3b8.toInt())
btnInstallGateway.visibility = View.GONE btnInstallGateway.visibility = View.GONE
btnConfigureGateway.visibility = View.VISIBLE btnConfigureGateway.visibility = View.VISIBLE
gatewayProgressBar.visibility = View.GONE gatewayProgressBar.visibility = View.GONE
gatewayProgressText.visibility = View.GONE gatewayProgressText.visibility = View.GONE
// Probe WebSocket port to tell user if gateway is actually running
Thread {
val running = try {
java.net.Socket().use { s ->
s.connect(java.net.InetSocketAddress("127.0.0.1", 8765), 1200)
true
}
} catch (_: Exception) { false }
runOnUiThread {
if (running) {
gatewayStatusDetail.text = "✅ Gateway attivo su ws://127.0.0.1:8765"
gatewayStatusDetail.setTextColor(0xFF34d399.toInt())
btnConfigureGateway.text = "⚙️ Riapri Gateway per configurarlo"
} else {
gatewayStatusDetail.text =
"⚠️ Gateway installato ma non ancora avviato.\n" +
"Premi il pulsante qui sotto per aprirlo e configurarlo, poi torna a questa schermata."
gatewayStatusDetail.setTextColor(0xFFfbbf24.toInt())
btnConfigureGateway.text = "▶️ Apri e configura Gateway"
}
}
}.start()
} else { } else {
gatewayStatusIcon.text = "📲" gatewayStatusIcon.text = "📲"
gatewayStatusText.text = getString(R.string.wizard_gateway_not_installed) gatewayStatusText.text = getString(R.string.wizard_gateway_not_installed)