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:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user