Skip to content

Commit 4fa7f3d

Browse files
committed
v1.2.11
1 parent 45a03e9 commit 4fa7f3d

File tree

20 files changed

+1231
-98
lines changed

20 files changed

+1231
-98
lines changed

CHANGELOG.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,78 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
## [Unreleased]
1414

1515

16+
***
17+
18+
## [1.2.11] - 2026-02-16
19+
20+
### Added
21+
- ⌨️ **Keyboard Emulation API**: Full keyboard input simulation via REST API ([#keyboard](https://github.com/FreeKiosk/FreeKiosk/issues))
22+
- **Single key press** (`GET|POST /api/remote/keyboard/{key}`): Send any keyboard key
23+
- Supports: a-z, 0-9, F1-F12, space, tab, enter, escape, backspace, delete, arrows, symbols, media keys
24+
- Over 80 named keys + single character support
25+
- **Keyboard shortcuts** (`GET|POST /api/remote/keyboard?map=ctrl+c`): Send key combinations with modifiers
26+
- Supports: ctrl, alt, shift, meta (Windows/Cmd key)
27+
- Examples: `ctrl+c`, `ctrl+v`, `alt+f4`, `ctrl+shift+a`
28+
- **Text input** (`POST /api/remote/text`): Type full text strings into focused input fields
29+
- Body: `{"text": "Hello World!"}`
30+
- Uses `Instrumentation.sendStringSync()` for natural text input
31+
- All keyboard operations handled natively (no JS bridge — fast and reliable)
32+
- 📍 **GPS Location API** (`GET /api/location`): New endpoint for device GPS coordinates
33+
- Returns: latitude, longitude, accuracy, altitude, speed, bearing, provider, timestamp
34+
- Uses GPS, Network, and Passive location providers (best accuracy wins)
35+
- Permissions already declared in manifest (`ACCESS_FINE_LOCATION` + `ACCESS_COARSE_LOCATION`)
36+
- 🔋 **Enriched Battery API**: `GET /api/battery` now returns additional data
37+
- New fields: `temperature` (°C), `voltage` (V), `health` (good/overheat/dead/etc.), `technology` (Li-ion/etc.)
38+
- Backward compatible: existing `level`, `charging`, `plugged` fields unchanged
39+
- 🔒 **Lock Device API** (`GET|POST /api/lock`): New endpoint to lock the device screen
40+
- Uses `DevicePolicyManager.lockNow()` for a true screen lock (Device Owner required)
41+
- Returns clear error message if Device Owner mode is not active
42+
- 🔄 **Restart UI API** (`GET|POST /api/restart-ui`): New endpoint to restart the app UI
43+
- Calls `activity.recreate()` to fully restart the React Native activity
44+
- Useful for remote troubleshooting without rebooting the device
45+
- 🗣️ **Text-to-Speech (TTS)**: Fully implemented native TTS via Android `TextToSpeech` engine
46+
- TTS engine is initialized when the HTTP server starts
47+
- Handled natively (no JS bridge dependency — works even if React Native is unresponsive)
48+
- Auto-retries if TTS engine is not ready on first call
49+
- 📊 **Volume Read API** (`GET /api/volume`): New endpoint to read current volume level
50+
- Returns `{ level: 0-100, maxLevel: 100 }` for easy integration with Home Assistant sensors
51+
52+
### Fixed
53+
- 🐛 **Screen Sleep Scheduler - Black Screen & Navigation Lockout**: Fixed 4 critical bugs causing scheduler to malfunction
54+
- **Feedback loop**: Scheduler re-entered sleep immediately after wake due to `isScheduledSleep` in useEffect dependency array
55+
- **Navigation lockout**: Scheduler interval kept running while on PIN/Settings screen, calling `lockNow()` and locking user out
56+
- **Wake-on-touch broken**: Touch events during sleep did nothing — never restored brightness or called `exitScheduledSleep()`
57+
- **Stale closure**: `checkScreenSchedule()` used outdated state variable instead of ref
58+
- **N-tap during sleep**: 5-tap for settings now properly exits scheduled sleep before navigating to PIN
59+
- **Activity null after lockNow()**: `turnScreenOn()` now acquires WakeLock before checking for activity availability
60+
- Fixes black screen issue on Android 8.1+ and impossible settings access during sleep windows
61+
- 🐛 **Power menu dismissed immediately on some devices (TECNO/HiOS)**: Fixed GlobalActions (power menu) being closed ~900ms after appearing when "Allow Power Button" is enabled in Lock Mode
62+
- Root cause: `onWindowFocusChanged` aggressively re-applied immersive mode, stealing focus back from the system power menu window
63+
- Additionally, `onResume` would re-trigger `startLockTask()` during the brief focus transition, compounding the issue
64+
- Fix: debounced `hideSystemUI()` by 600ms on focus regain, and deferred `startLockTask()` re-lock when power button is allowed and focus was recently lost
65+
- No security impact: Lock Task Mode remains fully active throughout — only the cosmetic immersive mode re-application is delayed
66+
- Affects TECNO, Infinix, itel (HiOS) and potentially other OEMs with aggressive WindowManager behavior on Android 14+
67+
- 🐛 **Device Owner Status Hardcoded `false` in API**: Fixed `/api/info` and `/api/status` always reporting `isDeviceOwner: false`
68+
- Was hardcoded to `false` in `HttpServerModule.getDeviceStatus()`
69+
- Now performs a real `DevicePolicyManager.isDeviceOwnerApp()` check
70+
- This caused external dashboards to incorrectly show Device Owner as inactive
71+
- 📺 **Screen On Not Working After lockNow()**: Fixed `GET /api/screen/on` failing when screen was off
72+
- `reactContext.currentActivity` was `null` after `lockNow()` and the code silently did nothing
73+
- WakeLock is now acquired **before** checking for activity (WakeLock works without activity)
74+
- Added keyguard dismissal to properly wake from locked state
75+
- Screen now reliably turns on whether activity is available or not
76+
- 🧹 **Clear Cache Now Actually Clears**: Fixed `/api/clearCache` which only reloaded the WebView
77+
- Now performs a full native cache clear: WebView HTTP cache, cookies, Web Storage (localStorage/sessionStorage), form data
78+
- Then forces a WebView remount on the JS side for a complete fresh start
79+
- 🔄 **In-App Update 404 Error**: Fixed update download failing with 404 error
80+
- Now retrieves actual APK download URL from GitHub release assets instead of constructing it
81+
- Eliminates filename case sensitivity issues (FreeKiosk vs freeKiosk)
82+
- More robust: works regardless of APK naming convention changes
83+
- Fallback to constructed URL if asset parsing fails
84+
- 📸 **Screenshot Race Condition**: Fixed `/api/screenshot` returning 503 intermittently
85+
- Replaced `Thread.sleep(100)` with a proper `CountDownLatch` to wait for the UI thread
86+
- Screenshot capture now waits up to 5 seconds for the UI thread to complete
87+
1688
***
1789

1890
## [1.2.10] - 2026-02-11

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
</p>
1212

1313
<p>
14-
<img src="https://img.shields.io/badge/Version-1.2.10-blue.svg" alt="Version 1.2.9">
14+
<img src="https://img.shields.io/badge/Version-1.2.11-blue.svg" alt="Version 1.2.9">
1515
<a href="https://github.com/rushb-fr/freekiosk/releases"><img src="https://img.shields.io/github/downloads/rushb-fr/freekiosk/total.svg" alt="Downloads"></a>
1616
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT">
1717
<img src="https://img.shields.io/badge/Android-8.0%2B-green.svg" alt="Android 8.0+">
@@ -261,7 +261,19 @@ Done! Your tablet is now in kiosk mode.
261261

262262
## 🗺️ Roadmap
263263

264-
### ✅ v1.2.10 (Feb 2026) - URL Filtering, NFC Fix & Scroll to Top 🆕
264+
### ✅ v1.2.11 (Feb 2026) - Keyboard API, GPS, TTS & Major Fixes 🆕
265+
- ⌨️ **Keyboard Emulation API**: Full keyboard input, shortcuts & text typing via REST
266+
- 📍 **GPS Location API**: Device coordinates via `/api/location`
267+
- 🔋 **Enriched Battery API**: Temperature, voltage, health & technology fields
268+
- 🔒 **Lock Device API** & 🔄 **Restart UI API**: New remote control endpoints
269+
- 🗣️ **Text-to-Speech (TTS)**: Native Android TTS via REST API
270+
- 📊 **Volume Read API**: Current volume level via `/api/volume`
271+
- 🐛 **Screen Sleep Scheduler Fix**: Fixed black screen, lockout & wake-on-touch bugs
272+
- 🐛 **Power Menu Fix (TECNO/HiOS)**: Fixed power menu dismissed immediately
273+
- 🧹 **Clear Cache Fix**: Now performs full native cache clear
274+
- 🔄 **Update 404 Fix**: Retrieves actual APK URL from GitHub assets
275+
276+
### ✅ v1.2.10 (Feb 2026) - URL Filtering, NFC Fix & Scroll to Top
265277
- ⏱️ **Inactivity Return - Scroll to Top**: Smoothly scrolls to top when already on start page
266278
- 🔗 **URL Filtering (Blacklist / Whitelist)**: Control allowed URLs with wildcard patterns
267279
- 🔗 **URL Filtering Form Fix**: Fixed form submissions blocked in whitelist mode

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ android {
9090
applicationId "com.freekiosk"
9191
minSdkVersion rootProject.ext.minSdkVersion
9292
targetSdkVersion rootProject.ext.targetSdkVersion
93-
versionCode 25
94-
versionName "1.2.10"
93+
versionCode 26
94+
versionName "1.2.11"
9595

9696
manifestPlaceholders = [usesCleartextTraffic: "true"]
9797
}

android/app/src/main/java/com/freekiosk/AppLauncherModule.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,22 @@ class AppLauncherModule(reactContext: ReactApplicationContext) : ReactContextBas
5858
val cursor2 = db.rawQuery("SELECT value FROM catalystLocalStorage WHERE key = ?", arrayOf("@kiosk_allow_notifications"))
5959
val allowNotifications = if (cursor2.moveToFirst()) cursor2.getString(0) == "true" else false
6060
cursor2.close()
61+
62+
val cursor3 = db.rawQuery("SELECT value FROM catalystLocalStorage WHERE key = ?", arrayOf("@kiosk_allow_system_info"))
63+
val allowSystemInfo = if (cursor3.moveToFirst()) cursor3.getString(0) == "true" else false
64+
cursor3.close()
6165
db.close()
6266

67+
if (allowSystemInfo) {
68+
lockTaskFeatures = lockTaskFeatures or android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO
69+
}
6370
if (allowPowerButton) {
6471
lockTaskFeatures = lockTaskFeatures or android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS
6572
}
6673
if (allowNotifications) {
6774
lockTaskFeatures = lockTaskFeatures or android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
6875
}
69-
DebugLog.d("AppLauncherModule", "Lock task features: powerButton=$allowPowerButton, notifications=$allowNotifications (flags=$lockTaskFeatures)")
76+
DebugLog.d("AppLauncherModule", "Lock task features: powerButton=$allowPowerButton, notifications=$allowNotifications, systemInfo=$allowSystemInfo (flags=$lockTaskFeatures)")
7077
} catch (e: Exception) {
7178
DebugLog.d("AppLauncherModule", "Could not read settings, using LOCK_TASK_FEATURE_NONE: ${e.message}")
7279
}

android/app/src/main/java/com/freekiosk/KioskModule.kt

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class KioskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaM
8585
}
8686

8787
@ReactMethod
88-
fun startLockTask(externalAppPackage: String?, allowPowerButton: Boolean, allowNotifications: Boolean, promise: Promise) {
88+
fun startLockTask(externalAppPackage: String?, allowPowerButton: Boolean, allowNotifications: Boolean, allowSystemInfo: Boolean, promise: Promise) {
8989
try {
9090
val activity = reactApplicationContext.currentActivity
9191
if (activity != null && activity is MainActivity) {
@@ -113,6 +113,11 @@ class KioskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaM
113113
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
114114
var lockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE
115115

116+
// SYSTEM_INFO: shows non-interactive status bar info (time, battery)
117+
// Fixes Samsung/OneUI devices muting audio in lock task mode
118+
if (allowSystemInfo) {
119+
lockTaskFeatures = lockTaskFeatures or DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO
120+
}
116121
if (allowPowerButton) {
117122
lockTaskFeatures = lockTaskFeatures or DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS
118123
}
@@ -122,7 +127,7 @@ class KioskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaM
122127
lockTaskFeatures = lockTaskFeatures or DevicePolicyManager.LOCK_TASK_FEATURE_HOME
123128
}
124129
dpm.setLockTaskFeatures(adminComponent, lockTaskFeatures)
125-
android.util.Log.d("KioskModule", "Lock task features set: powerButton=$allowPowerButton, notifications=$allowNotifications (flags=$lockTaskFeatures)")
130+
android.util.Log.d("KioskModule", "Lock task features set: powerButton=$allowPowerButton, notifications=$allowNotifications, systemInfo=$allowSystemInfo (flags=$lockTaskFeatures)")
126131
}
127132

128133
dpm.setLockTaskPackages(adminComponent, whitelist.toTypedArray())
@@ -375,25 +380,29 @@ class KioskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaM
375380
@ReactMethod
376381
fun turnScreenOn(promise: Promise) {
377382
try {
383+
val powerManager = reactApplicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
384+
385+
// IMPORTANT: Acquire WakeLock FIRST, before checking for activity
386+
// After lockNow(), activity may be null, but WakeLock still works
387+
388+
// Release old wakeLock if exists
389+
wakeLock?.release()
390+
391+
// Create WakeLock to turn on screen
392+
@Suppress("DEPRECATION")
393+
wakeLock = powerManager.newWakeLock(
394+
PowerManager.FULL_WAKE_LOCK or
395+
PowerManager.ACQUIRE_CAUSES_WAKEUP or
396+
PowerManager.ON_AFTER_RELEASE,
397+
"FreeKiosk:ScreenOn"
398+
)
399+
wakeLock?.acquire(10*60*1000L) // 10 minutes timeout
400+
android.util.Log.d("KioskModule", "WakeLock acquired to turn screen ON")
401+
378402
val activity = reactApplicationContext.currentActivity
379403
if (activity != null) {
380404
activity.runOnUiThread {
381405
try {
382-
val powerManager = reactApplicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
383-
384-
// Release old wakeLock if exists
385-
wakeLock?.release()
386-
387-
// Create WakeLock to turn on screen
388-
@Suppress("DEPRECATION")
389-
wakeLock = powerManager.newWakeLock(
390-
PowerManager.FULL_WAKE_LOCK or
391-
PowerManager.ACQUIRE_CAUSES_WAKEUP or
392-
PowerManager.ON_AFTER_RELEASE,
393-
"FreeKiosk:ScreenOn"
394-
)
395-
wakeLock?.acquire(10*60*1000L) // 10 minutes timeout
396-
397406
// Show over lock screen and dismiss keyguard (in case PIN is set)
398407
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O_MR1) {
399408
activity.setShowWhenLocked(true)
@@ -417,17 +426,19 @@ class KioskModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaM
417426
layoutParams.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
418427
activity.window.attributes = layoutParams
419428

420-
android.util.Log.d("KioskModule", "Screen turned ON via WakeLock + keyguard dismissed")
421-
promise.resolve(true)
429+
android.util.Log.d("KioskModule", "Screen turned ON via WakeLock + activity flags")
422430
} catch (e: Exception) {
423-
android.util.Log.e("KioskModule", "Failed to turn screen on: ${e.message}")
424-
promise.reject("ERROR", "Failed to turn screen on: ${e.message}")
431+
android.util.Log.e("KioskModule", "Failed to set activity flags: ${e.message}")
425432
}
426433
}
427434
} else {
428-
promise.reject("ERROR", "Activity not available")
435+
android.util.Log.w("KioskModule", "Activity is null after lockNow() — WakeLock alone will wake screen")
429436
}
437+
438+
// Resolve immediately - WakeLock handles the wake even if activity is null
439+
promise.resolve(true)
430440
} catch (e: Exception) {
441+
android.util.Log.e("KioskModule", "Failed to turn screen on: ${e.message}")
431442
promise.reject("ERROR", "Failed to turn screen on: ${e.message}")
432443
}
433444
}

0 commit comments

Comments
 (0)