Added
-
🏠 Opt-in "Set FreeKiosk as default launcher" mode (#199): New Security → Auto Launch toggle (off by default) that makes FreeKiosk the Home/launcher app, so the system itself relaunches FreeKiosk at every boot. This addresses devices — Samsung/OneUI in particular — that drop out of kiosk mode after a reboot or system update: FreeKiosk's boot relaunch normally relies on OEM "Appear on top" / Autostart / background pop-up permissions, which Samsung resets on major OS updates, so the kiosk failed to come back. As the Home app, FreeKiosk is brought to the foreground by the system, with no dependency on those losable permissions. Two modes: with Device Owner the policy is persistent and locked (
addPersistentPreferredActivity) — the user can't change it and it survives OS updates as long as Device Owner stays active (the standard Android Enterprise dedicated-device pattern); it is re-asserted on every app launch (self-healing after an update), cleared when the toggle is turned off, and cleared before Device Owner is relinquished, so the normal launcher is always restored. Without Device Owner the toggle opens the system Home-app picker so the user can select FreeKiosk manually (not enforced, and may be reset by an OS update — Device Owner makes it permanent). Zero behavior change when off. ⚠️ While on, FreeKiosk is the device launcher (no fallback if it crash-loops on launch), so test on one device before a fleet rollout. -
🔐 Opt-in "System screen-lock compatibility" mode (#199): New Security → Auto Launch toggle (Device Owner only, off by default) for the rare case where a device has a native Android screen-lock (PIN/password) set in addition to FreeKiosk. Previously this combination could freeze/boot-loop the device: FreeKiosk's fast-boot-lock path (
BootLockActivity, directBoot-aware) entered lock-task over the secure keyguard atLOCKED_BOOT_COMPLETED— before the user unlocks and credential-encrypted storage is available — fighting the lock screen and dead-locking the boot, while lock-task also suppressed the keyguard so the password "never took effect". When the new setting is enabled and a secure screen-lock is actually configured (KeyguardManager.isDeviceSecure()), FreeKiosk now defers to the secure keyguard at boot and starts normally atBOOT_COMPLETED(after the user unlocks), so the password works and there's no boot conflict. The flag is mirrored to device-encrypted storage so it's readable before unlock. Zero behavior change when the toggle is off or no screen-lock is set — every new code path is gated on both conditions. Note: a native screen-lock inherently requires entering the password on the device after every reboot before the kiosk starts (unsuitable for unattended fleets); the in-app warning makes this explicit. -
🎙️ Opt-in "2-way audio (intercom) mode" (#205): New Display → Web Media toggle (WebView mode, off by default) for WebRTC talk-back use cases — e.g. a Home Assistant / go2rtc doorbell intercom card. The microphone permission was already auto-granted to the WebView (
onPermissionRequest), but the microphone back-channel wouldn't transmit because the device stayed in the normal audio mode. When enabled, FreeKiosk watches for active microphone capture viaAudioManager.AudioRecordingCallbackand switches toMODE_IN_COMMUNICATIONonly while the mic is actually recording (i.e. while you're talking back), restoring the previous audio mode as soon as it stops — so listen-only playback, which already worked, is left untouched. Re-applied on each launch (the native callback doesn't survive an app restart). Zero behavior change when off — the callback isn't even registered. Reported by @Jakopinus.
Fixed
-
💤 Screensaver still never activates in External App mode when motion detection is enabled (#190, follow-up): The beta.3 fix moved the inactivity countdown to native code, but when it expired with motion detection enabled the activation path still armed the 10-second motion pre-check window with a JS
setTimeout— the very timer class React Native freezes while FreeKiosk is backgrounded behind the external app. The logcat provided by the reporter shows exactly this: "Native inactivity event received — triggering screensaver" → "starting motion pre-check" → nothing, ever. The pre-check is now skipped in External App mode and the screensaver activates immediately: besides the frozen timer, a backgrounded app cannot capture from the camera on modern Android, so the pre-check could never have observed motion there anyway. Wake-on-motion is unaffected — once the screensaver brings FreeKiosk to the foreground, the camera works and motion dismisses the screensaver as usual. WebView/media modes keep the pre-check unchanged. Reported by @elcomandante. -
💥 App crashes the moment the screensaver activates in WebView mode (#190 follow-up, regression introduced by #177 in beta.4):
pauseMedia()/resumeMedia()passed the react-native-webview ref tofindNodeHandle()— but that ref is a methods-only imperative handle (goBack,reload,injectJavaScript, …), not a ReactComponent, sofindNodeHandlethrewArgument appears to not be a ReactComponentinside auseEffect, which in a release build is a fatalJavascriptException: FreeKiosk crashed at every screensaver activation (and app-backgrounding) whenever "Pause audio/video when hidden" — default on — was enabled. Fixed by resolving the node handle from the WebView's container host view instead (the nativepauseWebView()already walks the subtree to find the actual WebView, so the behavior is identical), and wrapping the lookup in a defensive try/catch so this code path can never take the app down again. Reported by @elcomandante. -
🖐️ 5-tap return-to-settings randomly stops working in External App mode, leaving the tablet stuck in the launched app (#203): The tap detection in App mode lives in the native
OverlayService(an invisible watch-outside-touch overlay), and it could die through two independent paths. (1) A JS/native race: on an involuntary return to FreeKiosk with kiosk lock enabled,MainActivity.onResume()sent theonAppReturnedevent to React Native before taking the fast-path that restarts theOverlayServiceand relaunches the external app (#106). The JS handler answers that event with an unconditionalstopOverlayService()scheduled viasetTimeout— but FreeKiosk goes straight back to the background, where React Native freezes JS timers, so the pending stop fired on the next foreground pass (typically right after leaving Settings, matching the report) and killed the overlay service that had just been restarted: no more 5-tap, no more auto-relaunch monitoring, device stuck in the app until a forced restart. Fixed by not sendingonAppReturnedwhen the native fast-path relaunches the app directly — the event now only fires when FreeKiosk genuinely stays in the foreground, where stopping the overlay is correct. (2) A self-poisoning failure mode in the overlay re-pin loop (#121): if a singleaddViewfailed (e.g. transient overlay-permission/window state),overlayViewwas left non-null but detached, so the 3-second re-pin'sremoveViewthrew and aborted before recreating the overlay — every subsequent cycle failed the same way, permanently. The re-pin now guards eachremoveViewindividually and always recreates, and a failedaddViewresets the reference so the service self-heals on the next cycle instead of staying dead. Reported by @pdien. -
🔑 Custom PIN not persisting on some signage firmwares — only the default
1234was accepted (#200): On certain devices (reported on a Rockchip RK3568 signage board, Android 11) setting a custom PIN appeared to succeed — the "Configuration saved" confirmation showed and the field read "Password configured" — but on the next unlock only the default1234worked and every custom PIN was silently rejected. Other settings (e.g. the REST API toggle) persisted normally, which pinpointed the cause: the PIN is the only setting stored in the Android Keystore (via react-native-keychain), and on these firmwares the hardware-backed keystore is broken/absent, sosetGenericPasswordreported success but never persisted a readable value — at unlockverifySecurePinthen found nothing and fell back to the1234default. Fixed by (1) reading the value straight back after every Keychain write and, when it didn't persist, falling back to AsyncStorage — which does survive restarts on these boards — storing only the irreversible PBKDF2 hash + salt, never the plaintext PIN; (2) reading the keystore defensively at verify time (a broken keystore can throw rather than return null) and consulting the AsyncStorage fallback before the default;hasSecurePin/clearSecurePinaccount for the fallback too. Devices with a working keystore are unchanged — no fallback copy is written when the read-back succeeds, so security is not weakened where the keystore works. Reported by @RafaelDava. -
⬜ Blank white screen after the WebView renderer process dies, with no recovery (#198): When Android's Chromium renderer process for the content WebView was killed — typically an out-of-memory kill, which on long-running kiosks could be triggered by a heavy page such as a Home Assistant dashboard — FreeKiosk was left sitting on a dead/empty WebView surface (a blank white screen) until something manually remounted it (e.g. an MQTT/REST reload). The native
RNCWebViewClient.onRenderProcessGonealready returnstrueso the app process survives, but FreeKiosk had noonRenderProcessGonehandler, and per Android's contract a WebView whose renderer is gone is defunct and must be remounted, not reused. Fixed by handlingonRenderProcessGoneinWebViewComponent: it best-effort clears the WebView cache (to rebuild the corrupted Chromium code-cache index that an OOM crash leaves behind) and asksKioskScreento bumpwebViewKeyfor a full WebView remount — the same recovery pattern already used for inactivity return and the planner. A crash-loop guard backs off to a delayed remount if the renderer dies repeatedly in quick succession, so a page that crashes on every load cannot pin the CPU in a tight remount loop. Reported by @pantherale0. -
🧹 Orphaned motion-detection interval after a rapid enable/disable (#198, aside):
MotionDetectorstarts capturing via asetTimeout(…, CAMERA_READY_DELAY)(1.5 s) that then arms the capture interval, butstopDetection()only cleared the interval — never the pending startup timeout. If motion detection was disabled or the camera restarted within that 1.5 s window (screensaver toggling motion on/off, returning from Settings after disabling motion,isFocusedflipping quickly), the timeout still fired afterwards with a staleenabled = trueclosure and spun up an extra, untracked capture interval that ran until the next full cycle. Fixed by tracking the startup timer in a ref and clearing it instopDetection()(both the vision-camera and Camera2 fallback paths). Reported by @pantherale0. -
🟦 Per-app blocking overlays never appeared over the targeted app (#199): A blocking-overlay region with a Target App Package set (e.g.
com.android.settings, to mask part of an allowed app's UI in multi-app mode) never rendered — only regions left "always show" (empty target package) worked. Root cause:BlockingOverlayManagerdecides whether a region applies by comparing itstargetPackageagainst the current foreground package, butsetForegroundPackage()was never called from anywhere, socurrentForegroundPackagestayed permanentlynulland every targeted region failed the match. Fixed by tracking the foreground app inFreeKioskAccessibilityService.onAccessibilityEvent(onTYPE_WINDOW_STATE_CHANGED) and forwarding the package toBlockingOverlayManager, so targeted regions now show/hide for the right app. Note: per-package targeting therefore requires FreeKiosk's Accessibility Service to be enabled; "always show" regions are unaffected. -
🔒 Factory reset reachable from the Settings app in multi-app mode (#201): When the system Settings app is added to the multi-app whitelist so users can reach a specific setting, they could also open Settings → System → Reset and factory-reset the device, wiping the kiosk. Lock-task restrictions don't cover this — factory reset is gated by a Device Owner user restriction, not a lock-task feature. Added an opt-in "Block factory reset" setting (Security, Device Owner only, off by default) that applies
DISALLOW_FACTORY_RESETvia the Device Owner policy; it is a persistent restriction that survives reboots, and is reconciled on every kiosk launch and cleared when turned off. Zero behavior change when off or non-Device-Owner.