Added
- 🖨️ Allow Printing toggle (#NEW): New "Allow Printing" setting (off by default) in General → Printing that enables
window.print()support in kiosk mode. When enabled: (1) thewindow.print()JavaScript call is intercepted and routed to Android's nativePrintManager, (2) print spooler packages (com.android.printspooler+ all installed print services like Samsung Print, HP Print, etc.) are dynamically discovered viaqueryIntentServicesand automatically whitelisted in Lock Task mode so the system print dialog can appear, (3) immersive mode is suspended while the print dialog is open and re-applied after it closes, (4)onResumelock task re-entry is deferred during printing to avoid killing the print UI, and (5)data:URLs are allowed in the WebView to support popup-based print flows (label printers, receipt generators). Supports WiFi, Bluetooth, USB printers and Save as PDF. Requested by @Poppy - ☀️ Auto-Brightness Offset (#92): New slider in Display → Auto-Brightness settings that lets you add a fixed percentage offset to the calculated auto-brightness value. For example, setting +10% means if the light sensor calculates 30% brightness, FreeKiosk will apply 40% instead. Useful when auto-brightness is consistently too dim for your environment but you still want it to adapt to ambient light. The offset is clamped at 100% maximum. Available in the settings UI (0–50% range with 5% steps) and via the REST API (
POST /api/autoBrightness/enablenow accepts an optionaloffsetparameter, 0–100). Requested by @Delivator
Fixed
- TTS silent for non-English text (#115):
/api/ttsonly spoke English and was completely silent for Chinese, Japanese, Korean and other non-Latin text becauseTextToSpeech.setLanguage()was never called — the engine defaulted to English. Added automatic language detection based on Unicode script analysis (CJK → Chinese, Hangul → Korean, Hiragana/Katakana → Japanese, Arabic, Thai, Hindi, Cyrillic, etc.). Also added an optionallanguageparameter (BCP 47 tag, e.g."zh-CN","ja","ko") for explicit control. The locale is now set before eachspeak()call. Requires the target language TTS voice data to be installed on the device. Reported by @nowpast - Some packages do not show up in app picker (#112): Packages without a launchable UI (services, VPN tools like gnirehtet, etc.) were excluded from the managed apps picker because
getInstalledApps()filtered ongetLaunchIntentForPackage() != null. Added a new native methodgetAllInstalledApps()that includes user-installed (non-system) packages even when they have no launcher activity. The managed apps picker now uses this method and offers a "Show all packages" toggle (off by default) to reveal background services/VPNs. Non-UI apps display a "service" badge for clarity. The single-app primary picker remains launcher-only since launching a non-UI package as the main app is not meaningful. Reported by @Royalflamejlh - ADB configuration doesn't support multi-app mode (#111): Added
external_app_modeandmanaged_appsADB intent extras to configure multi-app mode via ADB. You can now set--es external_app_mode "multi"and provide a JSON array of apps with--es managed_apps '[{"packageName":"com.app1"},{"packageName":"com.app2"}]'. Each app supportsshowOnHomeScreen,launchOnBoot,keepAlive, andallowAccessibilityflags. Both individual intent extras and full JSON config (--es config '{...}') are supported. Uninstalled packages are silently skipped. Display names are auto-resolved from the system if not provided. Reported by @Royalflamejlh - Volume buttons trigger PIN request when held (#110): Holding the volume button to adjust volume would trigger the PIN request because both
MainActivity.onKeyDownandVolumeChangeReceivercounted auto-repeat/rapid events as separate taps. Fixed by ignoringKeyEventwithrepeatCount > 0inMainActivity, and adding a minimum 250ms interval between counted volume changes inVolumeChangeReceiverto filter out the rapid events (~50-100ms) generated by holding the button. Deliberate separate presses (≥250ms apart) still work normally. Reported by @Mkdir1511 - Crash on boot with Lock Mode enabled (#109):
BootLockActivitycrashed immediately on boot with aNullPointerExceptioninhideSystemUI()becausewindow.insetsControllerwas called beforesetContentView(). On Android R+ (API 30+), this internally accesses theDecorViewwhich is only created bysetContentView()— so the DecorView wasnull. Fixed by reordering the calls sosetContentView()runs first, and added a try-catch safety net inhideSystemUI()for extra robustness on devices with unusual boot timing. Only affected v1.2.17-beta.1; v1.2.16 was unaffected because it didn't haveBootLockActivity. Reported by @sharkooon - Backup import from other devices / ADB push not working (#107): On Android 11+ (Scoped Storage), backup files pushed via
adb pushor copied from another device were invisible to the import list becauseRNFS.readDir()can only see files created by the app itself. Added a "Browse device for backup file..." button in the import modal that uses Android's Storage Access Framework (SAF) viaACTION_OPEN_DOCUMENTto open the native file picker — this bypasses Scoped Storage restrictions entirely. The JSON content is read directly throughContentResolver(no file copy needed). Also addedimportBackupFromContent()andparseBackupContent()toBackupServicefor content-based import/preview, and improved the empty-state message to guide users toward the browse button. Reported by @sharkooon - 📖 Device Owner setup incorrectly requires factory reset (#68): Updated all setup documentation (README, INSTALL.md, ADB_CONFIG.md, FAQ) to clarify that a factory reset is not required to activate Device Owner. Android's actual requirement is that no user accounts are active on the device — users can simply sign out of all accounts, run the
dpmcommand, and sign back in. Factory reset is now documented as a fallback only. Also added notes about SIM profiles/accounts that some devices retain. Reported by @realAllonZ, confirmed by @hapishyguy - WebView blocked by hosting providers (SiteGround, etc.): The hardcoded User-Agent (
Chrome/120.0.0.0onX11; Linux x86_64) was outdated and had a platform mismatch — hosting WAFs flagged it as a bot. Updated the default UA to a modern Chrome 131 on Android (Mozilla/5.0 (Linux; Android 13; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36). Also added a new Custom User Agent setting in Display settings, allowing users to override the UA string if specific sites require it - Screen Sleep Schedule not saving in App mode (#103): The Screen Sleep Scheduler settings (enabled, rules, wake-on-touch) were only persisted when in Website or Media Player mode. In External App mode, the save function forcibly reset
screenSchedulerEnabledtofalseand discarded all rules — even though the UI allowed configuring them in all modes. Moved scheduler save calls out of the mode-conditional block so they are now saved unconditionally, consistent with how they are loaded and executed. Reported by @hungrycactus - MQTT audio commands not working (#102):
audio_play,audio_beep, andaudio_stopMQTT commands were not functional while their REST API equivalents worked fine. The MQTT path forwarded audio commands to JS (ApiService.ts) which had no handler for them, whereas the REST API handled them natively in Kotlin (HttpServerModule). Added native audio handling (MediaPlayer,AudioTrack) directly inMqttModule.kt, matching the existing REST API implementation. Reported by @zeroping - MQTT still fails on older devices after #97 fix (#97): On older devices (Android 11 and below), R8 obfuscation of HiveMQ's internal classes (Dagger 2 IoC components, staged auth builder interfaces, lazy
InstanceHolderfactories) causedAbstractMethodErrororIncompatibleClassChangeErroron older ART runtimes when the auth code path was taken. Added comprehensive ProGuard keep rules for HiveMQ, Dagger, javax.inject, and RxJava. Hardened error handling withcatch (Throwable)(instead ofcatch (Exception)) to properly catchErrorsubclasses from Netty/Dagger static initialization and propagate them to the UI - Kiosk Watchdog not stopping on exit (#96): Fixed KioskWatchdogService continuing to run (and relaunching the app) after intentionally exiting kiosk mode. The watchdog now writes
@kiosk_enabled=falseto AsyncStorage and explicitly stops the service before closing the activity. Also clears the watchdog notification on exit. Reported by @krheinwald