Added
-
🔒 Boot Lock Activity (#98): New lightweight native Android activity (
BootLockActivity) that enters lock-task mode immediately after boot — before React Native loads- Eliminates the window where users could interact with the OS freely on low-spec devices (e.g. Nokia C210) where RN can take 1-2 minutes to initialize
- Shows a minimal loading screen (app icon + spinner) and automatically hands off to MainActivity once React Native is ready
- Only activates for Device Owner installs with kiosk mode enabled; non-DO installs use the existing delayed-launch path
-
🛡️ Kiosk Watchdog Service (#96): New
KioskWatchdogServiceforeground service usingSTART_STICKYflag to survive OOM kills- On low-RAM devices (e.g. 2GB AndroidTV), automatically relaunches FreeKiosk within seconds if the kernel kills it due to memory pressure
- Includes relaunch cooldown (15s) to prevent relaunch storms
- Self-disables when kiosk mode is turned off; uses a silent minimal-priority notification
-
🎬 Media Player Mode: Brand-new display mode alongside Website and External App
- Play videos and images in full-screen kiosk mode with playlist support
- Local file support: Pick videos and images directly from the device via Android's native file picker; files are copied to app internal storage for reliable WebView playback; local files show a 📱 badge and filename in the playlist
- Native FilePickerModule: New Kotlin native module (
FilePickerModule.kt) usingACTION_OPEN_DOCUMENTintent; includesdeleteMediaFile,listMediaFiles,clearMediaFileshelpers - Remote URL support: Also accepts remote
http://andhttps://URLs for hosted media content - Playlist management: Add multiple media URLs or pick local files from General settings, with auto-detection of media type based on file extension or MIME type
- Video support: MP4, WebM, OGG formats with optional mute toggle
- Image support: PNG, JPG, GIF, SVG, WebP with configurable per-item or default display duration (seconds)
- Playback options: Auto-play, loop, shuffle, and optional on-screen controls (prev/play-pause/next with progress bar)
- Display options: Fit mode (contain/cover/fill), background color, crossfade transitions with configurable duration
- Full kiosk integration: Brightness control, screensaver, screen always on, status bar, lock mode, volume button return, and touch blocking all work identically to Website mode
- WebView-based rendering: Uses an embedded HTML5 player with
allowFileAccess,allowFileAccessFromFileURLs, andallowUniversalAccessFromFileURLsenabled for local file playback; dual-slot crossfade transitions - Error handling: Auto-skips unplayable items with retry; shows friendly empty state when no items configured
- Settings persistence: All media player settings saved to AsyncStorage, included in backup/restore, and properly reset
- Android 13+ permissions: Added
READ_MEDIA_VIDEOandREAD_MEDIA_IMAGESfor granular media access
-
📊 Dashboard Mode: New display mode that shows a configurable grid of URL tiles instead of a single WebView
- Users can create tiles with custom names and URLs, each automatically assigned a distinct color
- Tapping a tile opens its URL in the WebView with a navigation bar (back/forward/refresh/home)
- Configurable in Settings → Dashboard tab
-
📱 Multi-App Mode (#67): External App mode now supports managing multiple apps
- Add apps from the new "Managed Apps" section in General settings — each app appears on a home screen grid with icon circles
- All managed apps are automatically whitelisted in Lock Task Mode, so users can switch between them without escaping the kiosk
- The primary app (single package) still works exactly as before for backward compatibility
-
🚀 Launch App on Boot (#37): Managed apps with "Launch on Boot" enabled are automatically started in the background when the device boots, before FreeKiosk's own UI loads
- Combined with "Keep Alive", apps can be maintained as persistent background services
-
💓 Keep Alive Background Monitor (#37): New
BackgroundAppMonitorServiceforeground service checks every 30 seconds (viaUsageStatsManager) if managed apps with "Keep Alive" enabled are still running, and relaunches them if they've stopped or crashed- Starts automatically on boot when at least one keep-alive app is configured
-
♿ Accessibility Whitelist for Other Apps (#66): Device Owners can now allow other apps' accessibility services via a per-app "Allow Accessibility" toggle in Managed Apps settings
- Uses
DevicePolicyManager.setPermittedAccessibilityServices()to whitelist selected packages alongside FreeKiosk's own service - Applied at boot, on save, and when enabling via Device Owner
- Uses
-
⚙️ Android Settings Button (#89): New "Android System Settings" section in the Advanced tab
- Main button to open the native Android settings, plus quick-access shortcuts for WiFi, Sound, Display, Bluetooth, Date & Time, and Apps
- Fully compatible with Lock Task Mode: automatically pauses the lock, opens the settings, and re-engages kiosk mode when the user returns to FreeKiosk
- An info banner warns when kiosk mode is active
- Useful for devices with no physical navigation buttons where ADB commands are restricted by Admin mode
-
🔍 WebView Zoom Level (Display settings): New slider to control how web pages are rendered in WebView mode
- Range: 50%–200%, default 100% (matches Chrome's default rendering)
- Quick presets at 75%, 100%, 125%, 150%
- An info hint appears when zoom is not at default; only available in WebView mode
- Persisted to storage and included in backup/restore
Changed
- 🏪 Play Store Compliance: Conditional Self-Update (#playstore): In-app self-update via GitHub is now completely disabled when building for the Play Store
- A single Gradle flag (
-Pplaystore) controls everything at compile time — no separate codebase needed - When active:
REQUEST_INSTALL_PACKAGESpermission removed,UpdateInstallReceiverdisabled, entire "Updates" UI section hidden from Settings → Advanced, all native update methods become no-ops - R8 strips the dead update code from the final bytecode
- Normal sideload/F-Droid builds (
./gradlew assembleRelease) remain fully functional with self-update enabled - Play Store builds:
./gradlew bundleRelease -Pplaystore
- A single Gradle flag (
Fixed
-
🔌 MQTT Doesn't Connect When Password Is Set (#97): Fixed a crash in the password masking logic —
String.repeat(password.length - 6)produced a negative count for passwords shorter than 7 characters, throwingIllegalArgumentExceptionbefore the MQTT client was even built- This silently aborted
connect(), resulting in zero network traffic and an immediate return to "Disconnected" - Also fixed authentication being skipped entirely when a password was configured without a username
- This silently aborted
-
🔄 Inactivity Return Now Works in Dashboard Mode: Previously had no effect because the feature required a base URL (empty in dashboard mode)
- Now correctly returns to the dashboard grid after the configured timeout
-
🔄 URL Planner Return to Dashboard Grid: When a scheduled planner event ended while in Dashboard Mode, the app did not return to the dashboard grid due to a stale closure in the planner callback
- Fixed by using a ref to track the active event
-
♿ Accessibility Auto-Enable Fails with "Permission Denial: WRITE_SECURE_SETTINGS" (#99): The "Enable Automatically (Device Owner)" button crashed because
Settings.Secure.putString()requiresandroid.permission.WRITE_SECURE_SETTINGS, which is not automatically granted to Device Owners- Added the permission to the manifest, wrapped the secure settings write in a
SecurityExceptioncatch with a specificWRITE_SECURE_SETTINGS_REQUIREDerror code - Improved UI shows a clear dialog with the one-time ADB command:
adb shell pm grant com.freekiosk android.permission.WRITE_SECURE_SETTINGS BootReceiveralso handles the missing permission gracefully instead of crashing silently
- Added the permission to the manifest, wrapped the secure settings write in a
-
🔒 PIN Bypass via Back Gesture (#93): Fixed a security issue where swiping back (Android predictive back gesture) from the Settings or PIN screen could bypass PIN protection on Android 16+ devices (e.g. Lenovo Idea Tab Pro)
- Disabled swipe-back gestures on PIN and Settings screens
- Added
BackHandlerto block hardware/gesture back navigation - Replaced
navigation.navigatewithnavigation.resetto fully clear the navigation stack when returning to kiosk mode
-
📦 Self-Update Fails with "No Storage Permission" (#88): The APK download used
setDestinationInExternalPublicDir()which requiresWRITE_EXTERNAL_STORAGEruntime permission — never requested at runtime- Switched to
setDestinationInExternalFilesDir()(app-private directory), eliminating the storage permission requirement entirely - Old downloaded APKs are now cleaned up automatically before each new download
- Switched to
-
📦 Self-Update Fails to Install on Android 8+ (#88): APK install was blocked because "Install from unknown sources" was not enabled for the app
- Added a pre-download permission check (
canRequestPackageInstalls()) with a user-friendly dialog that opens the system settings page to grant the permission - On restricted devices (e.g. Amazon Echo Show / Fire OS) where the settings page doesn't exist, a clear fallback message guides the user to use
adb install -r <apk>instead
- Added a pre-download permission check (
-
🔧 Duplicate Import in HttpServerModule (#88): Removed duplicate
android.location.LocationManagerimport that caused Kotlin compilation failure -
🎮 Remote Control Now Works Natively Like a Physical Keyboard (#85): Remote key commands (
/api/remote/up,down,left,right,select,back,home,menu,playpause) were previously routed through a JavaScript round-trip via React Native bridge- Now handled entirely in native code — dispatched via the AccessibilityService (cross-app D-pad navigation with UI element highlighting) or
activity.dispatchKeyEvent()(in-app fallback) - On /e/OS, LineageOS, and other custom ROMs, this enables proper focus-based UI navigation identical to a physical remote/keyboard
- Now handled entirely in native code — dispatched via the AccessibilityService (cross-app D-pad navigation with UI element highlighting) or
-
📡 MQTT Now Supports Remote Control and Keyboard Commands (#85): Added 12 new MQTT command topics for full remote control parity with the REST API
remote_up,remote_down,remote_left,remote_right,remote_select,remote_back,remote_home,remote_menu,remote_playpause,keyboard_key,keyboard_combo,keyboard_text- Home Assistant Discovery registers 9 new button entities (remote D-pad) and 3 new text entities (keyboard input), bringing the total from 30 to 42 auto-discovered entities
-
📡 MQTT Background Persistence (#80): MQTT now stays alive when the app is in background or the screen is off, with 4 layers of protection:
PARTIAL_WAKE_LOCK+WIFI_MODE_FULL_HIGH_PERFkeep CPU and WiFi active for MQTT PING packetsNetworkCallbackdetects WiFi recovery and triggers immediate reconnect instead of waiting for TCP timeout + exponential backoff- OverlayService watchdog checks MQTT health every 60 seconds from the existing foreground service
SCREEN_ONreceiver triggers an instant MQTT reconnect check when the screen wakes up
-
🏷️ MQTT Device Name Prompt on Every Keystroke (#80): Changing the Device Name no longer triggers a reconnect popup on every key press
- The reconnect prompt now only appears once when the field loses focus (onBlur)
-
📶 WiFi
connectedField Reporting True When Not on WiFi (#80): MQTT status and REST API usedipAddress != "0.0.0.0"to determine WiFi connection, which returnedtruefor cellular, Ethernet, or USB tethering- Now uses
ConnectivityManager.getNetworkCapabilities(TRANSPORT_WIFI)consistently across all 3 modules — only returnstruewhen actually connected to WiFi
- Now uses
-
📶 SSID Shows "WiFi" Instead of Actual Network Name (#80): When Android blocks SSID access, the app now shows a diagnostic message —
"WiFi (no permission)"or"WiFi (location off)"— instead of silently showing"WiFi"- Also handles the
0xedge case on some Chinese tablets
- Also handles the