github RushB-fr/freekiosk v2.1.0-beta.1
FreeKiosk v2.1.0-beta.1 - Android TV, Digital Signage & 4 New Languages

pre-release5 hours ago

Added

  • 🌐 Digital signage: a web page and a YouTube video can be items of a cloud playlist (#308, #309; docs/digital-signage.md). A page is shown full screen for a duration (30 seconds when none is given, kept between 5 seconds and an hour) in a sandboxed frame that takes no taps; a YouTube video plays to its end, with an optional cap. Neither goes through the offline cache, and both are skipped when the device is offline, with no error on an unattended screen. YouTube refuses to play for a page that has no origin (error 153), which is the case of the player page, so the video is played by a page of the cloud (/player/youtube/?v=<id>) that tells the player when it ends; the app accepts the item only for that page and only listens to messages from its origin. The manifest is additive (kind: web, kind: youtube with video_id), an older app drops what it does not know, and the app now reports signage_web and signage_youtube in its capabilities so the dashboard can say "App too old". Checked on an Android TV emulator against a local cloud: a web page, a YouTube video and a page that forbids framing played in order and looped, the video item ending with the video; the parsing and the player page are unit tested. Not checked on a physical device or the optional cap of a video, and YouTube's terms for use on a business's public screens are not read yet.
  • 🇩🇪 The app is available in German. Settings > General > Language now offers Deutsch. Without a choice, a device set to any German locale (Germany, Austria, Switzerland) opens the app in German. Every string of the app is translated, addressing the user informally. As in the other languages, Device Owner, ADB and MQTT stay in English, and texts sent to the REST API, MQTT and FreeKiosk Cloud stay in English too. Translated with a shared glossary and checked by a test that every language file has the keys and {{placeholders}} of en.json; not yet reviewed by a native speaker.
  • 🇮🇹 The app is available in Italian. Settings > General > Language now offers Italiano. Without a choice, a device set to any Italian locale (Italy, Switzerland) opens the app in Italian. Every string of the app is translated, addressing the user informally. As in the other languages, Device Owner, ADB and MQTT stay in English, and texts sent to the REST API, MQTT and FreeKiosk Cloud stay in English too. Translated with a shared glossary and checked by a test that every language file has the keys and {{placeholders}} of en.json; not yet reviewed by a native speaker.
  • 🇳🇱 The app is available in Dutch. Settings > General > Language now offers Nederlands. Without a choice, a device set to any Dutch locale (Netherlands, Belgium) opens the app in Dutch. Every string of the app is translated, addressing the user informally. As in the other languages, Device Owner, ADB and MQTT stay in English, and texts sent to the REST API, MQTT and FreeKiosk Cloud stay in English too. Translated with a shared glossary and checked by a test that every language file has the keys and {{placeholders}} of en.json; not yet reviewed by a native speaker.
  • 🇪🇸 The app is available in Spanish. Settings > General > Language now offers Español next to English, French and Brazilian Portuguese. Without a choice, a device set to any Spanish locale (Spain, Mexico, Argentina…) opens the app in Spanish. Every string of the app is translated, in neutral Spanish addressing the user as tú. As in French, Device Owner, ADB and MQTT stay in English, and texts sent to the REST API, MQTT and FreeKiosk Cloud stay in English too.
  • 🖥️ Digital signage: a playlist built in FreeKiosk Cloud plays on the screen (docs/digital-signage.md). In the cloud dashboard, a playlist of videos and images (by URL, with a duration per image) is assigned to a device or to a group. At its next heartbeat the device downloads the list, goes into Media Player mode (remembering the mode it had) and plays it from the offline cache; removing the playlist puts the previous mode back. The heartbeat answer carries playlist_version, the manifest comes from GET /devices/{id}/playlist/, and the device reports the version it plays (system.signage_playlist_version) so the dashboard can say whether a screen is up to date: all additive, an older cloud or app is unaffected. Settings > General > Media Playlist says when the playlist comes from the cloud. Schedules: a playlist can play on chosen days, between two times and within a date range, with a priority; the screen picks the playlist with its own clock, so it changes on time even when offline, keeps the files of every scheduled playlist in the offline cache, and shows a blank screen when nothing is due. A playlist item can also be a file of the cloud's media library: its address is signed (the files are private) with a path that is never reused, the offline cache leaves the signature out when it names a file so a renewed manifest downloads nothing again, and it keeps the file like any other item (checked on a tablet emulator: a playlist of two images and a video, from a private library, played in order and cached byte for byte; a renewed manifest with new signatures left the three cached files untouched). Checked on a tablet emulator against a local cloud (assign, change, remove, and a timed switch to another playlist and back with the network cut); the logic is unit tested. The schedule follows the cloud's time, not a device clock that may be off: a TV box often has no battery-backed clock and can be hours wrong, so the app keeps the gap between the cloud's time (in every heartbeat answer) and its own, and reads the schedule with it (a gap under a minute is ignored, and the gap is kept across restarts; the device's own timers keep its clock). Checked on an Android TV emulator whose clock is ten hours behind: a rule at 16:03 UTC switched the playlist at 16:03 UTC, where it never did before. The heartbeat also carries the time zone of the screen (system.timezone and system.utc_offset_minutes, additive) so the cloud can say when a screen changes playlist next. A security review of this work fixed a playlist title that could end the player's script and run as code, and made the cache read what is saved without waiting behind a long download. A screen whose playlist comes from the cloud is not covered by the screensaver (inactivity timer), which used to turn the playlist into a blank or white screen; the scheduled sleep still applies (checked on a tablet emulator: black during the window, the playlist resumes after). Not tested on a physical device or an Android TV, across a daylight-saving change, and no uploaded files yet.
  • 💾 Media Player: optional offline cache (docs/android-tv.md). Settings > General > Playback > Play from the device (offline cache) downloads each http(s) video and image of the playlist to the device and plays the file, so a network drop no longer stops the playlist. On by default on a TV, off everywhere else, so a tablet or a phone is unchanged unless you turn it on. A download is moved in place only when complete; each file is checked again every 10 minutes with a conditional request, so an unchanged file is not downloaded again and a changed one is; a failed check keeps the file; a file removed from the playlist, and anything else in the cache folder, is deleted; a download that would leave less than 100 MB free is refused. Part of the config export/import (general.mediaPlayer.cache), the backup and ADB (media_player_cache). Checked on a tablet emulator with the network cut (plays with the cache, black screen without), with the option off (no cache folder, no call to the cache, same playback as before), and on the Android TV emulator (on by default). A download that is cut resumes where it stopped (Range with If-Range: a file changed meanwhile starts again, never a mix of two versions), the cache has a size limit (2 GB by default, media_player_cache_limit_mb over ADB) and a download that would leave under 100 MB free is refused, and Settings show what is on the device with a Clear the cache button. Checked against a local test server on a tablet emulator: a download cut at 3 MB resumed and ended byte for byte identical to the source, a file changed during a resume was downloaded again whole, a 12 MB file was refused under a 5 MB limit, an unchanged file got a 304. On the emulator a server-closed connection loses a byte every 8,640 in the last ~170 KB (an emulator network artifact); the size check refused that file instead of accepting it. No checksum yet. Unattended playback (Settings > General > Playback, media_player_unattended over ADB; on by default on a TV, off elsewhere) hides the error message, which carried the URL of the file, and skips an item that does not load or whose video freezes for 30 seconds: neither fires an error, so the screen used to stay on it for good. Checked on a tablet emulator with the setting off and on against a 404, a server that never answers and a stream that stops. On a TV without Device Owner, Settings > Security > Launch on Boot now says whether Android will let FreeKiosk start at boot ("display over other apps") and shows the command to grant it, since the TV has no screen for it: checked on the Android TV emulator with a reboot, FreeKiosk does not come back without the permission and does with it (or when it is the Home app). The cache state (files needed and present, size, time of the last check, an error code) is reported to FreeKiosk Cloud in the heartbeat (system.media_cache) once the cache has been on. Not tested on a physical device, with a very large video, or on a full storage.
  • 📺 Android TV, Google TV and Fire TV: the app installs, shows in the apps row and can be driven with a remote (docs/android-tv.md). FreeKiosk is declared as a TV app (leanback, with the touchscreen, telephony, Wi-Fi, Bluetooth and location features optional, so it stays installable everywhere) and has a banner. On a TV, Back ×5 (each press within 2 seconds of the previous one) on the kiosk screen opens the PIN screen: Volume Up ×5 and the tap escape cannot work there, since the volume keys never reach the app and there is no touch. The focused element is outlined, OK toggles a switch row, Enter validates the PIN, and Back in the settings offers to leave without saving. A mouse click now counts toward the tap sequence. Turning Bluetooth off from the lock-screen dialog asks for confirmation on a TV, since the remote is often Bluetooth. A device with no battery sends none to the cloud (so no battery alert), reports present: false over REST and MQTT and hides the battery in the status bar; an Ethernet-only box is reported online and still gets its CPU wake lock. Brightness settings are hidden on a TV. On a TV the default user agent no longer says Mobile, so sites serve their wide-screen layout. An app that only declares a TV launcher can now be picked as external app, launched, relaunched and started on boot. Checked on Android TV and Google TV emulators (API 36, 1080p) with remote keys sent through adb, and on a tablet emulator to confirm nothing changed there (5 taps still open the PIN screen, Back ×5 does nothing). Not tested on a physical TV or box, on Fire TV, with HDMI-CEC or an infrared remote. Not covered: External App mode is not offered on a TV, since no remote key reaches FreeKiosk while another app is in front (a device already in that mode keeps it; use the REST API, MQTT or a cloud command to get out), and Device Owner over adb fails on a TV image that lacks android.software.device_admin.

Fixed

  • 🧹 Android lint now passes: the 7 errors it reported are dealt with (./gradlew lintDebug: 0 errors, it had 7 on top of the API-level ones). Bluetooth scan: reading whether a found device is already paired needs BLUETOOTH_CONNECT on Android 12+ while the scan only checks BLUETOOTH_SCAN; if the first was missing the exception left onReceive and killed the app, it now reports the device as not paired. Stopping a scan without BLUETOOTH_SCAN also unregisters the receiver now (the exception used to skip that). Bluetooth audio: a revoked BLUETOOTH_CONNECT returns an empty list. Camera stream: the CAMERA check was already in place, lint cannot see it, so it is now marked. Update download: the receiver for the download-complete broadcast is registered with ContextCompat.registerReceiver and RECEIVER_EXPORTED, which is the same registration as before on every Android version, written the way lint accepts. Back button, not changed: MainActivity.onBackPressed is marked, not moved to an OnBackPressedCallback. Measured on an Android 15 emulator (release build), Back does not reach that method, and Back still works there through React Native; on Android 16 React Native calls it, below Android 13 the platform does. For the three valid back_button_mode values it ends in super.onBackPressed(), so moving it would only change what an unknown value set by hand over ADB does, and would change how Back is routed on Android 13 to 15. Checked on an Android 15 emulator after the change: the app starts, /api/status answers and the screenshot still returns a PNG. Not exercised: the Bluetooth scan and audio code (no Bluetooth device on an emulator) and the update download (the installed version is the latest one).
  • 📱 FreeKiosk no longer crashes on Android 7 where it called Android 8 and 9 APIs (#303, #304). The project builds for Android 7.0 (minSdk 24) but several calls existed only from Android 8 or 9, with no version check: on Android 7 they threw NoSuchMethodError, which a catch (e: Exception) does not catch, so the app died. Screenshots (GET /api/screenshot, cloud command): PixelCopy.request needs Android 8, so Android 7 now answers 503 Screenshots need Android 8.0 or later instead of crashing. Lock Mode whitelist (Device Owner): getLockTaskPackages needs Android 8; on Android 7 the temporary opening of the battery, Wi-Fi, Bluetooth and emergency-dialer system dialogs is skipped, since the list cannot be read back there (it is still set when the lock starts), and launching an external app no longer tries to extend it. Bluetooth audio outputs: AudioDeviceInfo.getAddress needs Android 9, so below it the A2DP and SCO outputs are listed once without an address (the selection already reads that as any device). Wi-Fi (#304): setSecurityParams is public from Android 11, not Android 10, so adding an open or WPA network on Android 10 no longer calls it. Android lint NewApi errors: 13 to 0. Checked on an Android 7.0 emulator (API 24, Device Owner): with 2.0.0-beta.6 the screenshot request killed the app with NoSuchMethodError: PixelCopy.request, with the fix it answers 503 and the app keeps running; on Android 15 the screenshot still returns a PNG. Not exercised: the whitelist, Bluetooth audio and Wi-Fi changes (they need a system dialog tap, a Bluetooth headset and an Android 10 device): checked by lint and by reading the code. Android 7 is otherwise untested, so the documented minimum stays Android 8.0.
  • 💓 The cloud heartbeat no longer stops for good after the app restarts its screen (#302). The kiosk screen can be mounted twice in a row (an ADB config restarts the activity), and the first one's cleanup ran after the second one's start and stopped the heartbeat loop: the device went offline in the dashboard, though the playlist kept playing, until the app process was restarted. The loop now belongs to the screens that use it and stops with the last one. Reproduced on a tablet emulator with an ADB display_mode intent (no heartbeat for 12 minutes), then checked with the fix: same intent, heartbeat every 30 s without a break.
  • 📷 Always-on motion detection now comes back after another app has taken the camera (#301). When an app in front of FreeKiosk opened the camera (Chrome launched from the page, a video call), CameraX closed the session and never reopened it: no more motion events until the setting was switched off and on, even once FreeKiosk was back in front (21 hours in the report). The detector now treats a camera error, or a session that stops while it should be open, as a loss, and reopens the camera by cycling it off and on: only while FreeKiosk is in the foreground (a background app must not take the camera from the app the person just opened), 0.5 s after coming back to the foreground, otherwise after 2, 5, 15, 30 then every 60 seconds until the camera answers. The cycle does not call onInitialized again, so detection restarts on the camera's onStarted event. A working camera is not touched: nothing new runs unless a loss was reported, and turning detection off or leaving the screen cancels the retries. Covered by unit tests (loss by error and by stop, growing delays, foreground only, cancellation, no change on a working camera) with a simulated camera. Checked on a tablet emulator (Android 15) with always-on detection on and the Android Camera app taking the front camera for 60 seconds: with 2.0.0-beta.6 the camera reconnected once the other app left, but detection never restarted (one warm-up frame, then no capture for over a minute); with the fix, 0.5 s after FreeKiosk came back to the front it reopened the camera, the warm-up frames ran and captures and motion events resumed. While the other app was in front, FreeKiosk made no attempt to take the camera back. A brief conflict (the other app releasing the camera within a second) also recovers, and a working camera shows no change. Not tested on a physical device (Xiaomi Pad 6 in the report), with Chrome as the other app, or on the Camera2 fallback path, which is unchanged.
  • 🔌 The "Allow USB debugging?" prompt no longer disappears a few seconds after it shows in Lock Mode (#268). With Allow the USB Debugging Prompt In Lock Mode on, the prompt (an activity of SystemUI) came up, then FreeKiosk's watchdog, which checks every 10 s that the kiosk is in front, saw that FreeKiosk was not and relaunched it over the prompt; the 15 s pause between two relaunches is why it sometimes stayed. The watchdog now leaves SystemUI alone while the option is on and SystemUI is what last came to the front; anything else still gets the relaunch, and with the option off nothing changes. Checked on a tablet emulator (Android 15) in Lock Mode with another SystemUI screen standing in for the prompt (the emulator does not enforce ADB authorization): with 2.0.0-beta.6 the screen was pulled back after 2 to 4 seconds, with the fix it stayed 60 seconds, Back returned to FreeKiosk, and with the option off Lock Mode still refused the screen. The check reads the usage events, so it needs Usage access as the other foreground checks do; without it the relaunch happens as before. Not reproduced or checked: the real prompt, a Samsung tablet on Android 16 as in the report, and Single App mode: on the emulator the watchdog did not pull the screen back there with 2.0.0-beta.6 (4 trials), so the report may have another cause in that mode.
  • ⏰ The screen schedule wakes the screen on time, not up to an hour late (#307). The wake at the end of a sleep window was an inexact Android alarm, which the system may deliver anywhere in a window of 75% of the time left, capped at one hour: set in the evening for 07:00, it could fire as late as 08:00 (a device dump in the report shows a window of 4 min 18 s for an alarm set 7 minutes ahead, and 1 hour for the ones set the evening before). It is now an exact alarm (setAlarmClock: allowed in Doze, no extra permission), so it fires at the set time. The system lists it as the next alarm (an alarm in the system clock UI; the kiosk's own status bar is unaffected). Only the wake alarm changed: the sleep alarm is a backup to the JS timer. Checked on a tablet emulator (Android 15, Device Owner) with a 9-hour sleep window and the clock moved to 10 seconds before the wake time: the alarm window went from +1h0m0s to 0, and the wake alarm fired at 21:41:00.008 for 21:41:00, where 2.0.0-beta.6 (same alarm code as 2.0.0) fired it at 21:43:05, 2 min 5 s late. Not checked on the reporter's Samsung tablet, nor over a real night.

Don't miss a new freekiosk release

NewReleases is sending notifications on new releases.