github RushB-fr/freekiosk v1.2.0.0-beta.1
FreeKiosk v2.0.0-beta.1 - Cloud Management & Remote Control

pre-release3 hours ago
  • ☁️ FreeKiosk Cloud is switched on. The groundwork shipped compiled-but-disabled in 1.2.20-beta.6 (CLOUD_ENABLED) is now active, which is what takes this line to 2.0. It remains a closed beta: the platform is invitation-only and the app is handed out from the dashboard. This entry gathers the whole cloud feature, previously listed as unreleased, together with the defects found while validating it on a device.

Added

  • ☁️ FreeKiosk Cloud integration, remote fleet management: FreeKiosk can enroll into FreeKiosk Cloud, the companion SaaS platform, to be monitored and managed remotely from a web dashboard. A device enrolls with a one-time token (Advanced → Cloud Management), after which it speaks to the cloud over the documented REST contract (/api/v1/…, Authorization: Bearer fk_…). Once enrolled it does three things on a rolling 30-second heartbeat (POST /devices/{id}/heartbeat/): (1) reports live telemetry for the dashboard, that is battery level and charging, Wi-Fi SSID and signal, IP, screen on/off and brightness, current URL, kiosk and Device Owner state, model, manufacturer, Android version, free storage and memory, uptime, app version, and the device's capability list; (2) performs two-way config sync, pushing its current settings up (change-detected via a config hash so nothing is sent when nothing changed) and applying a config the dashboard pushes down (sync_action: "apply"), reloading the running kiosk in place so remote changes take effect without a restart; and (3) when the heartbeat reports pending work, pulls and executes remote commands. All settings, including secrets such as the PIN, API key, MQTT and basic-auth passwords, are synced through a dedicated sensitive_config channel and stored in the Keychain, never in plain config. The whole feature is opt-in and inert until you enroll: nothing is sent to any server otherwise. ⚠️ Enrolling wipes the tablet's local FreeKiosk settings, which then come from the cloud. Leaving cloud management wipes them again.

  • 🕹️ Remote commands and OTA app updates from the dashboard: Enrolled devices consume a cloud command queue (GET /devices/{id}/commands/) and report each outcome back (POST /commands/{id}/result/). Supported: reload, clear_cache, screensaver_on/screensaver_off, wake, speak (TTS), play_sound/audio_stop, toast, execute_js, launch_app, reboot, and screenshot (captures the current screen and uploads the PNG to POST /devices/{id}/screenshot/). Commands that complete natively take the native path directly, so they work whatever the JS thread is doing; the rest fall back to the shared ApiService.executeAction used by the local REST and MQTT server. Expired commands are skipped and overlapping polls de-duplicated, so a command cannot run twice. Note that screen_on / screen_off are deliberately not offered by the dashboard: turning a device's screen off puts it into Doze where it stops heartbeating, so the matching screen_on could never reach it. Timed on/off is handled by the on-device Screen Scheduler instead. A separate authenticated update channel (GET /devices/{id}/updates/) delivers over-the-air APK installs: FreeKiosk downloads the signed APK with its device key and installs it via the native ManagedAppInstallerModule (silent as Device Owner, system installer prompt otherwise). The download is restricted to HTTPS and, before a silent install, the downloaded APK's package name is verified against the expected package, which the cloud reads from the APK itself at upload, so a cleartext or intercepted download cannot substitute a different app.

  • 📷 QR-code and zero-touch enrollment: The Advanced → Cloud Management screen can scan the enrollment QR, and the cloud URL defaults to https://cloud.freekiosk.app. Devices provisioned as Device Owner via the Android setup-wizard QR enroll zero-touch: on first launch FreeKiosk consumes the token handed over by provisioning, auto-enrolls, and (Device Owner only) pins itself as the persistent Home launcher so there is no "choose launcher" prompt, which the dashboard can still override later. The server can also remotely un-enroll and wipe a device (via force_unenroll or a 401/403), clearing all settings and Keychain secrets and returning it to a clean state.

  • 📲 Device Owner provisioning via setup-wizard QR (fully-managed enrollment): The native pieces that let a factory-reset device become a managed FreeKiosk kiosk by scanning a QR at the Android setup wizard, the standard Android Enterprise flow. Two provisioning activities (ProvisioningModeActivity for GET_PROVISIONING_MODE, which always requests a fully-managed device, and PolicyComplianceActivity for ADMIN_POLICY_COMPLIANCE) are required on Android 12+, without which the wizard aborts with "Can't set up device". The cloud packs { enroll_token, cloud_url, org_id } into the provisioning admin-extras bundle; it is read on both the modern path (PolicyComplianceActivity) and the legacy PROFILE_PROVISIONING_COMPLETE path (DeviceAdminReceiver), persisted, and consumed on first launch for the zero-touch enrollment above.

  • 🧭 Guided permission wizard and device capability reporting: A PermissionWizard (shown right after enrollment and reachable from settings) walks the device to its fullest capability set: as Device Owner it confirms everything is auto-granted and can enable the accessibility service programmatically; as a standard app it steps through each runtime and special-access permission (camera, location, accessibility, display-over-apps, usage access, battery-optimization exemption) with a deep link into the relevant system screen and a live status refresh when the app returns to the foreground. On close it fires a heartbeat so the refreshed capabilities reach the cloud immediately. Backing it, a capabilities probe reports what the device actually supports (baseline actions always, plus device_owner, reboot, silent_install and screen_off_lock when Device Owner, and accessibility, overlay, usage_access, camera, location and bluetooth when granted) at enrollment and on every heartbeat, so the dashboard only offers actions that can really run.

  • 🔋 Cloud comms stay alive with the screen off: CloudSyncService holds a PARTIAL_WAKE_LOCK and a WifiLock while running (released when it stops) and requests a Doze battery-optimization exemption on start, without which the device drops off the cloud and can no longer be woken remotely. Both are best-effort; the Doze-exemption permission is stripped from the Play Store manifest for policy compliance. The config round-trip was also rounded out so lockscreen, launcher, zoom, screensaver and media-player keys are included in both cloud sync and local backup/restore.

Fixed

  • 📡 A cloud-managed tablet went offline two minutes after launching an external app, while running perfectly: in External App mode the heartbeat stopped completely, so the dashboard reported the device as unreachable and no remote command could arrive. Measured: zero heartbeats over three minutes. The cause is not Doze or a killed process, both ruled out (the process stays alive at normal priority, and a foreground service changes nothing): React Native stops dispatching JS timers when the activity pauses (JavaTimerManager.onHostPause clears the Choreographer callback that drives setInterval), and the heartbeat loop is a JS timer. It is now driven from a native ticker in KioskWatchdogService through a headless task (CloudHeartbeatTaskService), which is the supported way to re-arm those timers; a 20 s debounce collapses the duplicate beat the wake-up would otherwise produce. Measured after the fix: six heartbeats, thirty seconds apart, with commands and screenshots executing while the external app stays in front. The keep-alive foreground service introduced for MQTT in #234 now also covers an enrolled device, so the process survives OEM battery managers.

  • 🔁 A remote reboot, and a self-update, never reported its outcome and stayed "sent" for ever: the server only ever hands out pending commands, so nothing redelivered a command whose device died while executing it, and the dashboard could not tell a successful reboot from one that never ran. Commands are now persisted before dispatch and settled on the next start: a command expected to take the device down reports success, anything else reports honestly that it was interrupted. A result the network refused is queued and retried instead of being dropped.

  • ⚙️ A cloud command was less reliable than the same command over the local REST API: the cloud channel dispatched everything through the JS layer, while the local REST server has always executed a good part of its commands natively. tts, reboot, audio, lock, remote-key and keyboard commands now take that same native path from the cloud, so they work whatever the JS thread is doing. play_sound was falling through to JS over a plain name mismatch (playSound vs audioPlay) and is now aliased.

  • 🖥️ A display-mode change pushed from the dashboard was stored and echoed back, but never applied: the tablet reported the new mode and its URL to the cloud while still showing the external app, so the dashboard displayed something that was not true. FreeKiosk now returns to the foreground on such a change, with the same blockAutoRelaunch guard the REST/MQTT setMode path received in #209 so the app just left is not relaunched.

  • 📦 After an update, a kiosk in External App mode was left with no watchdog, no overlay and no heartbeat until someone touched it: Android restarts the process only for what it still binds (in practice the accessibility service) and never recreates MainActivity, and the launched app keeps the foreground, so nothing brought FreeKiosk back. BootReceiver now handles MY_PACKAGE_REPLACED and restores the services, relaunching the activity only on devices configured to run unattended so an ordinary Play Store update does not yank the app to the front. This is also the path a cloud OTA update takes.

  • ⬆️ In-app updates never offered a newer beta: the settings screen compares the GitHub tag against the installed versionName and ranks a version with no pre-release suffix above any pre-release. Since versionName stayed at 1.2.20 for every beta, an installed beta always looked like a stable release, so 1.2.20-beta.6 was reported as older than the running 1.2.20 and the app answered "you are up to date". This had been true since 1.2.20-beta.1. The version now carries its pre-release suffix (2.0.0-beta.1), so the comparison behaves as written: a beta offers the next beta, and a stable release still supersedes any beta.

  • 🔋 The permission wizard listed "Ignore battery optimizations" as outstanding for ever, and tapping it did nothing: the request is a no-op once the exemption is held, and the step never read the state back although the native API for it has existed since #234. It now shows the real state.

  • 🧭 The wizard's accessibility button landed on the plain settings list, where OEM skins bury third-party services (One UI files them under "Installed apps") and users could not find FreeKiosk. It now opens the service's own page directly, falling back to the old behaviour where a manufacturer does not support that.

Don't miss a new freekiosk release

NewReleases is sending notifications on new releases.