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:
@@ -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) ────────────────────
|
||||||
|
|||||||
+18
-6
@@ -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'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>
|
||||||
|
|||||||
Reference in New Issue
Block a user