Skip to content

FreeKiosk v1.2.20-beta.4 - Launcher Mode, 2‑Way Audio & Stability

Pre-release
Pre-release

Choose a tag to compare

@valentingomy valentingomy released this 09 Jul 09:16

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 at LOCKED_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 at BOOT_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 via AudioManager.AudioRecordingCallback and switches to MODE_IN_COMMUNICATION only 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 to findNodeHandle() — but that ref is a methods-only imperative handle (goBack, reload, injectJavaScript, …), not a ReactComponent, so findNodeHandle threw Argument appears to not be a ReactComponent inside a useEffect, which in a release build is a fatal JavascriptException: 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 native pauseWebView() 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 the onAppReturned event to React Native before taking the fast-path that restarts the OverlayService and relaunches the external app (#106). The JS handler answers that event with an unconditional stopOverlayService() scheduled via setTimeout — 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 sending onAppReturned when 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 single addView failed (e.g. transient overlay-permission/window state), overlayView was left non-null but detached, so the 3-second re-pin's removeView threw and aborted before recreating the overlay — every subsequent cycle failed the same way, permanently. The re-pin now guards each removeView individually and always recreates, and a failed addView resets 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 1234 was 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 default 1234 worked 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, so setGenericPassword reported success but never persisted a readable value — at unlock verifySecurePin then found nothing and fell back to the 1234 default. 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/clearSecurePin account 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.onRenderProcessGone already returns true so the app process survives, but FreeKiosk had no onRenderProcessGone handler, and per Android's contract a WebView whose renderer is gone is defunct and must be remounted, not reused. Fixed by handling onRenderProcessGone in WebViewComponent: it best-effort clears the WebView cache (to rebuild the corrupted Chromium code-cache index that an OOM crash leaves behind) and asks KioskScreen to bump webViewKey for 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): MotionDetector starts capturing via a setTimeout(…, CAMERA_READY_DELAY) (1.5 s) that then arms the capture interval, but stopDetection() 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, isFocused flipping quickly), the timeout still fired afterwards with a stale enabled = true closure 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 in stopDetection() (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: BlockingOverlayManager decides whether a region applies by comparing its targetPackage against the current foreground package, but setForegroundPackage() was never called from anywhere, so currentForegroundPackage stayed permanently null and every targeted region failed the match. Fixed by tracking the foreground app in FreeKioskAccessibilityService.onAccessibilityEvent (on TYPE_WINDOW_STATE_CHANGED) and forwarding the package to BlockingOverlayManager, 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_RESET via 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.