kiosk: fix install dialog + screensaver always-on

Fallback install (Intent.ACTION_VIEW):
- Remove FLAG_ACTIVITY_NEW_TASK: it caused startActivityForResult to return
  RESULT_CANCELED immediately, making the system installer dialog disappear in ~1s
- After fallback returns with app not installed: show '🔄 Riprova installazione'
  button that calls tryFallbackInstall() directly (skips PackageInstaller which
  is known to give STATUS=1 on this device)

Screensaver:
- KioskActivity.applyScreensaverFlag(): always add FLAG_KEEP_SCREEN_ON, never
  clear it — screen must ALWAYS stay on in kiosk mode
- The 'salvaschermo' toggle controls the in-app JS clock overlay (webapp setting),
  NOT the Android screen timeout
- finishSetup(): always push screensaver_enabled to webapp API (not just when scale
  is configured)
- SettingsActivity save: remove FLAG_KEEP_SCREEN_ON conditional; push
  screensaver_enabled to server API on save
- Update setup wizard description + strings to clarify in-app overlay vs screen off

Bump version to 1.5.2 (versionCode 8)
This commit is contained in:
dadaloop82
2026-05-05 16:01:47 +00:00
parent 84d2ff0264
commit 6500d22242
6 changed files with 86 additions and 43 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 = 7 versionCode = 8
versionName = "1.5.1" versionName = "1.5.2"
} }
signingConfigs { signingConfigs {
@@ -439,11 +439,10 @@ class KioskActivity : AppCompatActivity() {
} }
private fun applyScreensaverFlag() { private fun applyScreensaverFlag() {
if (prefs.getBoolean(KEY_SCREENSAVER, false)) { // The kiosk screen must ALWAYS stay on — the in-app screensaver is a JS overlay
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) // (clock + info) shown by the webapp after inactivity, not an Android screen timeout.
} else { // Never clear FLAG_KEEP_SCREEN_ON regardless of the screensaver preference.
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
} }
// ── Inject kiosk overlay (exit + refresh buttons) ──────────────────── // ── Inject kiosk overlay (exit + refresh buttons) ────────────────────
@@ -106,12 +106,24 @@ class SettingsActivity : AppCompatActivity() {
.putString(KEY_URL, url) .putString(KEY_URL, url)
.putBoolean(KEY_SCREENSAVER, screensaverOn) .putBoolean(KEY_SCREENSAVER, screensaverOn)
.apply() .apply()
// Apply FLAG_KEEP_SCREEN_ON immediately based on new setting // Screen always stays on in kiosk mode — no FLAG_KEEP_SCREEN_ON change needed here.
if (screensaverOn) { // Push screensaver preference to the webapp so the in-app clock overlay is toggled.
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) Thread {
} else { try {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) val apiUrl = "$url/api/index.php?action=save_settings"
} val body = "{\"screensaver_enabled\":$screensaverOn}"
val conn = (java.net.URL(apiUrl).openConnection() as java.net.HttpURLConnection).apply {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json")
connectTimeout = 5000
readTimeout = 5000
doOutput = true
}
conn.outputStream.use { it.write(body.toByteArray()) }
conn.inputStream.close()
conn.disconnect()
} catch (_: Exception) {}
}.start()
Toast.makeText(this, "Impostazioni salvate", Toast.LENGTH_SHORT).show() Toast.makeText(this, "Impostazioni salvate", Toast.LENGTH_SHORT).show()
finish() finish()
} }
@@ -1068,31 +1068,35 @@ class SetupActivity : AppCompatActivity() {
private fun finishSetup() { private fun finishSetup() {
prefs.edit().putBoolean(KEY_SETUP_COMPLETE, true).apply() prefs.edit().putBoolean(KEY_SETUP_COMPLETE, true).apply()
// ── Pre-configure Scale Gateway URL in webapp settings ────────────────── // ── Sync settings to webapp API ─────────────────────────────────────────
// If the user has a scale and the gateway is installed, write the WebSocket // Always push: screensaver_enabled (in-app clock overlay preference).
// URL and enable the scale in the webapp's server settings (.env via API) // Conditionally add: scale settings when gateway is installed.
// so the webapp works out-of-the-box without manual settings configuration. val baseUrl = (prefs.getString(KEY_URL, "") ?: "").trimEnd('/')
if (prefs.getBoolean(KEY_HAS_SCALE, false) && isGatewayInstalled()) { if (baseUrl.isNotEmpty()) {
val baseUrl = (prefs.getString(KEY_URL, "") ?: "").trimEnd('/') val hasScale = prefs.getBoolean(KEY_HAS_SCALE, false) && isGatewayInstalled()
if (baseUrl.isNotEmpty()) { val screensaver = prefs.getBoolean(KEY_SCREENSAVER, false)
val gwUrl = "ws://127.0.0.1:8765" Thread {
Thread { try {
try { val url = "$baseUrl/api/index.php?action=save_settings"
val url = "$baseUrl/api/index.php?action=save_settings" val body = buildString {
val body = """{"scale_enabled":true,"scale_gateway_url":"$gwUrl"}""" append("{\"screensaver_enabled\":$screensaver")
val conn = (java.net.URL(url).openConnection() as java.net.HttpURLConnection).apply { if (hasScale) {
requestMethod = "POST" append(",\"scale_enabled\":true,\"scale_gateway_url\":\"ws://127.0.0.1:8765\"")
setRequestProperty("Content-Type", "application/json")
connectTimeout = 5000
readTimeout = 5000
doOutput = true
} }
conn.outputStream.use { it.write(body.toByteArray()) } append("}")
conn.inputStream.close() }
conn.disconnect() val conn = (java.net.URL(url).openConnection() as java.net.HttpURLConnection).apply {
} catch (_: Exception) {} requestMethod = "POST"
}.start() setRequestProperty("Content-Type", "application/json")
} connectTimeout = 5000
readTimeout = 5000
doOutput = true
}
conn.outputStream.use { it.write(body.toByteArray()) }
conn.inputStream.close()
conn.disconnect()
} catch (_: Exception) {}
}.start()
} }
setResult(RESULT_OK) setResult(RESULT_OK)
finish() finish()
@@ -1148,7 +1152,33 @@ class SetupActivity : AppCompatActivity() {
getString(R.string.install_success_detail), 0xFF34d399.toInt(), btnEnabled = false) getString(R.string.install_success_detail), 0xFF34d399.toInt(), btnEnabled = false)
Handler(Looper.getMainLooper()).postDelayed({ checkGatewayStatus() }, 1500) Handler(Looper.getMainLooper()).postDelayed({ checkGatewayStatus() }, 1500)
} else { } else {
checkGatewayStatus() // Install failed or user cancelled. Show an explicit retry button
// that re-launches the system installer directly (skipping PackageInstaller,
// which is known to give STATUS=1 on this device).
val retryFile = pendingInstallFile
val retryPkg = pendingInstallPkg
setGatewayUI(
"⚠️",
"Installazione non completata",
"L'app non risulta installata. Premi il pulsante sotto per riprovare.",
0xFFfbbf24.toInt()
)
btnInstallGateway.visibility = View.VISIBLE
btnInstallGateway.text = "🔄 Riprova installazione"
btnInstallGateway.setOnClickListener {
// Reset button back to default before retrying
btnInstallGateway.text = "📥 Installa Scale Gateway"
btnInstallGateway.setOnClickListener {
pendingApkDownloadUrl = GATEWAY_DOWNLOAD_URL
triggerApkDownload(GATEWAY_DOWNLOAD_URL)
}
if (retryFile != null && retryFile.exists()) {
tryFallbackInstall(retryFile, retryPkg)
} else {
pendingApkDownloadUrl = GATEWAY_DOWNLOAD_URL
triggerApkDownload(GATEWAY_DOWNLOAD_URL)
}
}
} }
}, 800) }, 800)
} }
@@ -1162,7 +1192,9 @@ class SetupActivity : AppCompatActivity() {
) )
val intent = Intent(Intent.ACTION_VIEW).apply { val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "application/vnd.android.package-archive") setDataAndType(uri, "application/vnd.android.package-archive")
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK // Note: do NOT add FLAG_ACTIVITY_NEW_TASK — it breaks startActivityForResult:
// Android would return RESULT_CANCELED immediately without waiting for the user.
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
} }
pendingInstallFile = file pendingInstallFile = file
pendingInstallPkg = targetPkg pendingInstallPkg = targetPkg
@@ -985,7 +985,7 @@
android:id="@+id/tvScreensaverDesc" android:id="@+id/tvScreensaverDesc"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Mostra un orologio con fatti utili dopo 5 minuti di inattività. Di default è disattivato (lo schermo resta sempre acceso)." android:text="Dopo 5 minuti di inattività mostra un overlay con l'orologio e informazioni utili (statistiche, piano pasti). Lo schermo rimane SEMPRE acceso — questa opzione riguarda solo l'overlay visivo in-app."
android:textColor="#94a3b8" android:textColor="#94a3b8"
android:textSize="15sp" android:textSize="15sp"
android:gravity="center" android:gravity="center"
@@ -59,10 +59,10 @@
<string name="wizard_server_error">Server not reachable ⚠️</string> <string name="wizard_server_error">Server not reachable ⚠️</string>
<string name="wizard_server_error_detail">Install errors won\'t reach GitHub Issues. Check the URL entered in step 2.</string> <string name="wizard_server_error_detail">Install errors won\'t reach GitHub Issues. Check the URL entered in step 2.</string>
<!-- Screensaver step --> <!-- Screensaver step -->
<string name="setup_screensaver_title">Screensaver</string> <string name="setup_screensaver_title">Salvaschermo in-app</string>
<string name="setup_screensaver_desc">Shows a clock with useful facts after 5 minutes of inactivity. Off by default (screen stays always on).</string> <string name="setup_screensaver_desc">Shows a clock with useful facts after 5 minutes of inactivity. Off by default (screen stays always on).</string>
<string name="setup_screensaver_toggle_label">Enable screensaver</string> <string name="setup_screensaver_toggle_label">Abilita salvaschermo orologio</string>
<string name="setup_screensaver_toggle_hint">If disabled, the screen stays always on.</string> <string name="setup_screensaver_toggle_hint">Mostra l&#39;overlay orologio dopo 5 min. Lo schermo resta sempre acceso.</string>
<!-- Summary --> <!-- Summary -->
<string name="summary_lang">Language</string> <string name="summary_lang">Language</string>