mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-16 15:23:16 -06:00
Android: the boot notice now clears, and kiosk survives a reboot
Two field reports from a customer running the player on Android x86. THE "STARTING DISPLAY…" BANNER NEVER CLEARED. Relauncher launches the activity directly when the overlay permission is granted — the normal kiosk setup — and THEN posts the notification, deliberately, so a device that could not auto-launch still has a tappable way back. On a device where the launch DID work, that ordering posts the prompt after onCreate has already cancelled it, and nothing cancels it again: a permanent banner over content that is already playing. They sent a photo of exactly that. Cancelling in onCreate only ever closed half the race. It now also clears on every foreground: if the player is on screen, a "Starting display…" prompt is stale by definition, whoever posted it and whenever. KIOSK MODE DID NOT SURVIVE A REBOOT. startLockTask() is a runtime call on the Activity, and nothing persisted the operator's intent — so a locked panel came back up unlocked, silently, and the only symptom is that someone can suddenly leave the app. The flag is now written BEFORE the lock is attempted, so a device that reboots mid-call still comes back in the state that was asked for, and a lock that fails is retried on the next start rather than forgotten. Restored in onStart rather than onCreate because lock-task can be dropped on some transitions. Also theirs: an "Exit kiosk mode" entry in the PIN menu, shown ONLY when locked. With kiosk on and no other input, that menu is the only way out of a panel, and a menu entry that does nothing is worse than no entry. Builds clean: versionCode 100, v1 JAR signature intact. Reported by chris@chris-pc. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
This commit is contained in:
parent
803f4ec26d
commit
b419830629
|
|
@ -784,14 +784,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
"screen_on" -> Log.w("MainActivity", "screen_on: no privileged wake path on a non-rooted panel — no-op")
|
"screen_on" -> Log.w("MainActivity", "screen_on: no privileged wake path on a non-rooted panel — no-op")
|
||||||
// #161 Tier-2 (all no-op off-owner via STPolicy): kiosk lock-task, time/tz, status bar,
|
// #161 Tier-2 (all no-op off-owner via STPolicy): kiosk lock-task, time/tz, status bar,
|
||||||
// uninstall block. Device owner enters lock-task silently; others get screen-pinning.
|
// uninstall block. Device owner enters lock-task silently; others get screen-pinning.
|
||||||
"kiosk_lock" -> {
|
"kiosk_lock" -> setKioskMode(true)
|
||||||
stPolicy().setLockTaskAllowed(true)
|
"kiosk_unlock" -> setKioskMode(false)
|
||||||
try { startLockTask() } catch (e: Throwable) { Log.w("MainActivity", "startLockTask: ${e.message}") }
|
|
||||||
}
|
|
||||||
"kiosk_unlock" -> {
|
|
||||||
try { stopLockTask() } catch (e: Throwable) { Log.w("MainActivity", "stopLockTask: ${e.message}") }
|
|
||||||
stPolicy().setLockTaskAllowed(false)
|
|
||||||
}
|
|
||||||
"set_time" -> { val ms = payload?.optLong("millis", 0L) ?: 0L; if (ms > 0) stPolicy().setTime(ms) }
|
"set_time" -> { val ms = payload?.optLong("millis", 0L) ?: 0L; if (ms > 0) stPolicy().setTime(ms) }
|
||||||
"set_timezone" -> { val tz = payload?.optString("timezone", "") ?: ""; if (tz.isNotEmpty()) stPolicy().setTimeZone(tz) }
|
"set_timezone" -> { val tz = payload?.optString("timezone", "") ?: ""; if (tz.isNotEmpty()) stPolicy().setTimeZone(tz) }
|
||||||
"status_bar" -> stPolicy().setStatusBarDisabled(payload?.optBoolean("disabled", true) ?: true)
|
"status_bar" -> stPolicy().setStatusBarDisabled(payload?.optBoolean("disabled", true) ?: true)
|
||||||
|
|
@ -1138,10 +1132,20 @@ class MainActivity : AppCompatActivity() {
|
||||||
getString(R.string.settings_exit)
|
getString(R.string.settings_exit)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Requested from the field: with kiosk on, the PIN menu is the ONLY way back out on a
|
||||||
|
// panel with no other input. Shown only when locked — an entry that does nothing is worse
|
||||||
|
// than no entry, and this menu is already long.
|
||||||
|
val kiosk = kioskModeEnabled()
|
||||||
|
val menu = if (kiosk) items + getString(R.string.settings_exit_kiosk) else items
|
||||||
|
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle(getString(R.string.settings_title))
|
.setTitle(getString(R.string.settings_title))
|
||||||
.setItems(items) { _, which ->
|
.setItems(menu) { _, which ->
|
||||||
when (which) {
|
when (which) {
|
||||||
|
items.size -> { // the appended exit-kiosk entry; only present when locked
|
||||||
|
setKioskMode(false)
|
||||||
|
Toast.makeText(this, getString(R.string.settings_exit_kiosk_done), Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
0 -> showChangeServerDialog(serverUrl)
|
0 -> showChangeServerDialog(serverUrl)
|
||||||
1 -> {
|
1 -> {
|
||||||
config.clearDeviceCredentials()
|
config.clearDeviceCredentials()
|
||||||
|
|
@ -1157,6 +1161,48 @@ class MainActivity : AppCompatActivity() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Kiosk lock, remembered.
|
||||||
|
*
|
||||||
|
* startLockTask() is a runtime call on the Activity — it does not survive a reboot. A panel
|
||||||
|
* locked from the dashboard came back up unlocked, with nothing to say so, and the only way
|
||||||
|
* to notice was that someone could suddenly leave the app. Reported from the field.
|
||||||
|
*
|
||||||
|
* The flag is written BEFORE the lock is attempted so a device that reboots mid-call still
|
||||||
|
* comes back in the state the operator asked for; a lock that then fails is retried on the
|
||||||
|
* next start rather than being forgotten.
|
||||||
|
*/
|
||||||
|
private fun setKioskMode(enabled: Boolean) {
|
||||||
|
try {
|
||||||
|
getSharedPreferences("screentinker", Context.MODE_PRIVATE)
|
||||||
|
.edit().putBoolean("kiosk_enabled", enabled).apply()
|
||||||
|
} catch (e: Throwable) { Log.w("MainActivity", "kiosk pref: ${e.message}") }
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
stPolicy().setLockTaskAllowed(true)
|
||||||
|
try { startLockTask() } catch (e: Throwable) { Log.w("MainActivity", "startLockTask: ${e.message}") }
|
||||||
|
} else {
|
||||||
|
try { stopLockTask() } catch (e: Throwable) { Log.w("MainActivity", "stopLockTask: ${e.message}") }
|
||||||
|
stPolicy().setLockTaskAllowed(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun kioskModeEnabled(): Boolean = try {
|
||||||
|
getSharedPreferences("screentinker", Context.MODE_PRIVATE).getBoolean("kiosk_enabled", false)
|
||||||
|
} catch (e: Throwable) { false }
|
||||||
|
|
||||||
|
/** Re-enter lock task after a restart, if that is the state the operator left it in. */
|
||||||
|
private fun restoreKioskMode() {
|
||||||
|
if (!kioskModeEnabled()) return
|
||||||
|
stPolicy().setLockTaskAllowed(true)
|
||||||
|
try {
|
||||||
|
startLockTask()
|
||||||
|
Log.i("MainActivity", "Kiosk mode restored after restart")
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.w("MainActivity", "Kiosk restore failed: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// #161: device-policy wrapper (degrades safely off-tier — every Tier-2 call no-ops when not owner).
|
// #161: device-policy wrapper (degrades safely off-tier — every Tier-2 call no-ops when not owner).
|
||||||
private fun stPolicy() = com.remotedisplay.player.admin.STPolicy(this)
|
private fun stPolicy() = com.remotedisplay.player.admin.STPolicy(this)
|
||||||
// #160 Track-A: no-device-owner system control (media volume, brightness, screen-off timeout).
|
// #160 Track-A: no-device-owner system control (media volume, brightness, screen-off timeout).
|
||||||
|
|
@ -1337,6 +1383,26 @@ class MainActivity : AppCompatActivity() {
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
if (::mediaPlayer.isInitialized) mediaPlayer.onAppForegrounded()
|
if (::mediaPlayer.isInitialized) mediaPlayer.onAppForegrounded()
|
||||||
|
|
||||||
|
// Clear the boot "Starting display…" prompt EVERY time the player becomes visible, not
|
||||||
|
// just in onCreate.
|
||||||
|
//
|
||||||
|
// Relauncher launches the activity directly when the overlay permission is granted — the
|
||||||
|
// normal kiosk setup — and THEN posts the notification, deliberately, so a device that
|
||||||
|
// could not auto-launch still has a tappable way back. On a device where the launch DID
|
||||||
|
// work, that ordering means the prompt is posted after onCreate already cancelled it, and
|
||||||
|
// nothing clears it again: a permanent "Starting display…" banner over content that is
|
||||||
|
// already playing. Reported from the field with a photo of exactly that.
|
||||||
|
//
|
||||||
|
// If the player is on screen, the prompt is stale by definition — so clearing it here is
|
||||||
|
// correct regardless of who posted it or when.
|
||||||
|
(getSystemService(Context.NOTIFICATION_SERVICE) as? android.app.NotificationManager)?.cancel(999)
|
||||||
|
|
||||||
|
// Re-enter kiosk if that is how the operator left it. Done in onStart rather than onCreate
|
||||||
|
// because lock-task can be dropped by the system on some transitions, and a panel that
|
||||||
|
// quietly stopped being locked is the failure people notice only when someone walks out of
|
||||||
|
// the app.
|
||||||
|
restoreKioskMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@
|
||||||
<string name="hw_enroll_constraints">Or scan the provisioning QR from the dashboard after a factory reset (tap the setup-wizard Welcome screen 6×). Device owner is optional — the app works fully without it.</string>
|
<string name="hw_enroll_constraints">Or scan the provisioning QR from the dashboard after a factory reset (tap the setup-wizard Welcome screen 6×). Device owner is optional — the app works fully without it.</string>
|
||||||
<string name="hw_recheck">Re-check</string>
|
<string name="hw_recheck">Re-check</string>
|
||||||
<string name="settings_info_device">Device</string>
|
<string name="settings_info_device">Device</string>
|
||||||
|
<string name="settings_exit_kiosk">Exit kiosk mode</string>
|
||||||
|
<string name="settings_exit_kiosk_done">Kiosk mode off. It stays off until re-enabled from the dashboard.</string>
|
||||||
<string name="settings_exit">Exit app</string>
|
<string name="settings_exit">Exit app</string>
|
||||||
<string name="settings_save">Save</string>
|
<string name="settings_save">Save</string>
|
||||||
<string name="settings_exit_title">Exit ScreenTinker?</string>
|
<string name="settings_exit_title">Exit ScreenTinker?</string>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue