Escape apostrophes and normalize multiline strings in de/es/fr/it so assembleDebug no longer fails with invalid unicode escape sequences.
Co-authored-by: Cursor <cursoragent@cursor.com>
Block web access to sensitive paths, require API_TOKEN for mutations, encrypt GitHub issue credentials in .env, auto-provision tokens for same-origin clients, and pass api_token in scale relay URLs since EventSource cannot send headers.
Co-authored-by: Cursor <cursoragent@cursor.com>
SetupActivity:
- btnTestRetry click handler: add step3NextButtons.visibility = VISIBLE
so the Indietro/Avanti buttons reappear after pressing No-retry
(previously they stayed hidden → user was stuck with no way to go back)
- onDisconnected(): always re-enable btnTestRetry so user is never stuck
when scale drops unexpectedly before a weight reading arrives
SettingsActivity / activity_settings.xml:
- Add 'IMPOSTAZIONI AVANZATE' section explaining that HA, Gemini AI,
- Add '← Torna all'app per le impostazioni avanzate' button (finish())
Android TTS was silently failing because engine.speak() used the default
audio stream, which may be muted while the media stream is not.
Changes:
- KioskActivity: speak() now passes Bundle with KEY_PARAM_STREAM=STREAM_MUSIC
so TTS plays on the same channel as beep/media audio
- KioskActivity: add UtteranceProgressListener that calls window._kioskTtsDone()
or window._kioskTtsError(uid, code) back into the WebView on completion/failure
- app.js testTTS(): kiosk path now shows real result from Android callbacks
(success/error) with a 4s fallback timeout showing actionable troubleshooting hints
Step 5 del wizard ora mostra 4 toggle (pre-compilati se già configurati):
- Salvaschermo orologio (screensaver_enabled)
- Prezzi lista spesa (price_enabled)
- Piano pasti (meal_plan_enabled)
- Suggerimenti zero-waste durante cottura (zerowaste_tips_enabled)
Solo i toggle NON ancora impostati in prefs partono da false (fresh install).
Tutti e 4 vengono salvati in SharedPreferences e inviati al server via
save_settings al completamento del wizard.
PHP/JS: zerowaste_tips_enabled aggiunto come impostazione server-side
(ZEROWASTE_TIPS_ENABLED in .env), sincronizzata nel WebView via
_applySyncedSettings() al caricamento.
- GITHUB_RELEASES_API ora punta a /releases/tags/kiosk-latest (non alla
webapp latest) per confrontare versioni kiosk vs kiosk
- checkForUpdates() estrae la versione reale dal body della release con
regex kiosk-X.Y.Z invece di usare il tag non-semver 'kiosk-latest'
- installApk() aggiunge validazione pre-install via PackageArchiveInfo:
package name diverso → errore + issue report
versionCode uguale/inferiore → banner dismesso + report install_no_upgrade
- Bump versionCode 16→17, versionName 1.7.15→1.7.16
Fix: STATUS=1 causato da confronto versione webapp (1.7.22) vs kiosk
(1.7.15) → falso update → scaricava stesso APK già installato → rifiuto
- Kiosk (Android): btnSettings was positioned top|end with alpha=0.12,
sitting invisibly on top of the HTML scan button in the webapp header.
Moved to bottom|end (marginBottom=80dp, alpha=0.28) so it never
overlaps the header. Kiosk versionCode 15→16, versionName 1.7.15.
- Web (Android Chrome/Brave): pointerleave fired before pointerup when
finger drifted, cancelling the long-press timer and letting a synthetic
click bubble to an unintended handler. Fixed with setPointerCapture +
preventDefault + replaced pointerleave with pointercancel. Added
touch-action:manipulation to .header-scan-btn CSS.
- onRenderProcessGone: WebView renderer crash/OOM kill now reported
and Activity is recreated automatically (no more silent crash)
- onReceivedHttpError: HTTP 5xx from server now reported
- onLowMemory: system low-memory event reported
- onTrimMemory: moderate+ memory trim events reported
Every error path in the kiosk now sends an auto-report issue.
style.css:
- Scale connected dot: bright #4ade80 fill + white border + double shadow
so it pops on the dark green header (was white on green = invisible)
index.html:
- Scale settings tab: add kiosk panel with 'Riconfigura bilancia BLE'
button (hidden in browser, shown in kiosk mode)
- Wrap gateway download section and WebSocket URL section with IDs
so JS can hide them in kiosk mode
- CSS cache bust ?v=20260506b
app.js:
- syncSettingsFromDB: in kiosk mode hide scale gateway download section,
WebSocket URL section and test button; show kiosk BLE panel instead;
auto-set URL to ws://localhost:8765
- Add _kioskReconfigureScale() helper that calls _kioskBridge.reconfigureScale()
KioskActivity.kt:
- Add reconfigureScale() @JavascriptInterface: stops GatewayService,
clears saved scale device prefs, launches SetupActivity at step 4
- Import GatewayService
app.js:
- _injectKioskOverlay: move kiosk-mode class assignment BEFORE the
_kiosk_overlay existence guard — fixes race where Kotlin onPageFinished
injects buttons during the await api() pause, then JS skips the class
- _PREF_LOC_NEEDED: 3 → 2 (remember use-location after 2 picks, not 3)
KioskActivity.kt:
- showNativeUpdateBanner: remove auto-start of triggerApkDownload;
banner now shows with 'Scarica' button enabled, download only starts
when user taps it (confirms before install)
GatewayService:
- onScanStopped() now calls scheduleReconnect() when not connected, so
if the scale is off at startup the service keeps retrying every 8s
instead of giving up after the first 20s scan window
SettingsActivity:
- Add 'Riconfigura bilancia' button (yellow outline) when a scale is
already configured — clears saved device, stops the gateway service,
and opens SetupActivity directly at step 4 (scale scan step)
SetupActivity:
- Accept 'start_step' intent extra to jump directly to a specific step
(used by SettingsActivity reconfigure flow)
kiosk setup wizard:
- Scale step: ask user to power on the scale before scanning (new
'Accendi la bilancia' card with 'Bilancia accesa → Cerca' button)
- BLE scan: filter results to only show compatible scales (scaleScore>0)
hiding clearly non-scale BLE devices from the list
- After device selection: run a live connection test — connect to the
scale, display the live weight, ask 'Corrisponde al peso sulla bilancia?'
with ✅ Sì / ❌ Riprova / Skip buttons before confirming the device
webapp:
- Scale indicator not live on first load: scaleInit() was firing before
syncSettingsFromDB() resolved; fixed by chaining .then(scaleInit)
- Scale icon green-on-green: connected state dot changed from #22c55e
(green, invisible on dark-green header) to white with green border+glow,
visible on any background color
webapp:
- Scale indicator: replace plain green dot with ⚖️ emoji + colored
status badge (green/amber/grey/red); icon fades out when disconnected;
tap shows a toast with device name + battery level
- Logo images: crop excess transparent padding from logo.png and
logo_icon.png so content fills the frame at small display sizes
- style.css: reworked .scale-status-indicator CSS for emoji+badge
kiosk:
- SetupActivity: use device's real LAN IP for scale_gateway_url
(was hardcoded 127.0.0.1 — only worked if server and kiosk run on
the same machine); added getDeviceLanIp() helper (prefers wlan/eth)
- activity_setup.xml: reduce welcome step padding/margins so step 1
fits on screen without scrolling; text sizes slightly reduced
- activity_setup.xml: fix feature bullet 'Bilancia Bluetooth via
Gateway app' → 'Bilancia BLE integrata (nessuna app esterna)'
- strings.xml (en + it): rewrite all wizard_gateway_* strings to
reflect integrated BLE service instead of external gateway APK
- ic_logo.png: regenerated at all densities from cropped source
- SettingsActivity: replace GATEWAY_PACKAGE / PackageManager check with
GatewayService status; show BLE device name + live :8765 probe;
buttons now restart GatewayService or redirect to setup wizard
- activity_settings.xml: rename section label to 'BILANCIA SMART',
update button text to '⚙️ Configura bilancia'
- evershelf-scale-gateway/README.md: add DEPRECATED notice (gateway is
now integrated into kiosk v1.6.0+, this app is no longer maintained)
- evershelf-kiosk/README.md: full rewrite — reflects v1.6.0, integrated
BLE gateway, 6-step wizard, permissions table, protocol reference
- README.md: update kiosk features (remove gateway APK install/launch,
add integrated BLE service), update scale section, update architecture,
add kiosk v1.6.0 entry in Recent Updates
- KioskActivity: move launchGatewayInBackground() BEFORE enableKioskLock() so
Android's lock-task restriction does not block starting the gateway Activity
- KioskActivity: webView.clearCache(true) before loadUrl — no caching
- KioskActivity: checkForUpdates() uses proper semver > comparison (not !=)
to avoid false-positive 'update available' when already up-to-date
- KioskActivity: showNativeUpdateBanner() removed 30s auto-hide, now auto-
triggers download immediately when update detected
- SetupActivity: onResume() re-checks gateway status when returning from gateway
config (user opens gateway, configures it, presses back → wizard refreshes)
- SetupActivity: checkGatewayStatus() probes TCP 127.0.0.1:8765 to show whether
gateway is actually running, with clear 'not running' warning to configure first
- SettingsActivity: same TCP probe for live gateway status in settings screen
- build.gradle.kts: versionCode 9, versionName 1.5.3
- assets/img/logo/logo.png: trimmed full logo (icon + text, transparent bg)
- assets/img/logo/logo_icon.png: icon-only crop (no text, for header)
- drawable-*/ic_logo.png: multi-density PNGs for Android splash (mdpi→xxxhdpi)
- activity_kiosk.xml: replace ic_launcher_foreground with ic_logo at 260dp,
remove redundant 'EverShelf' text row (already in logo image)
- index.html: add logo_icon.png in header title, logo.png in preloader
- style.css: add .app-preloader-logo and .header-logo-icon rules
Fallback install (Intent.ACTION_VIEW):
- Remove FLAG_ACTIVITY_NEW_TASK: it caused startActivityForResult to return
RESULT_CANCELED immediately, making the system installer dialog disappear in ~1s
- After fallback returns with app not installed: show '🔄 Riprova installazione'
button that calls tryFallbackInstall() directly (skips PackageInstaller which
is known to give STATUS=1 on this device)
Screensaver:
- KioskActivity.applyScreensaverFlag(): always add FLAG_KEEP_SCREEN_ON, never
clear it — screen must ALWAYS stay on in kiosk mode
- The 'salvaschermo' toggle controls the in-app JS clock overlay (webapp setting),
NOT the Android screen timeout
- finishSetup(): always push screensaver_enabled to webapp API (not just when scale
is configured)
- SettingsActivity save: remove FLAG_KEEP_SCREEN_ON conditional; push
screensaver_enabled to server API on save
- Update setup wizard description + strings to clarify in-app overlay vs screen off
Bump version to 1.5.2 (versionCode 8)
- Wizard step 4 (scale): skip question if scale already configured (KEY_HAS_SCALE=true), show gateway status directly
- Add '⚙️ Apri Gateway per configurarlo' button in setup wizard after gateway is installed
- Add same button in SettingsActivity SMART SCALE section (visible only when installed)
- Fix btnRunWizard in SettingsActivity: immediately launches SetupActivity instead of just showing a toast
- Bump version to 1.5.1 (versionCode 7)
- Before launching system installer (ACTION_VIEW fallback), show a
non-cancellable dialog warning: press Fine, NOT Apri
- After installer returns, force SetupActivity back to foreground in case
user pressed Open anyway (FLAG_ACTIVITY_REORDER_TO_FRONT)
- SetupActivity: catch STATUS_FAILURE=1 separately and immediately retry with
Intent.ACTION_VIEW (system installer dialog) instead of showing a dead error.
STATUS=1 is a generic PackageInstaller failure that can happen on many
Android 14 devices even with a valid APK, but the system installer handles it.
- SetupActivity: remove misleading 'incompatibile' hint for status=1 (was wrong;
STATUS_FAILURE_INCOMPATIBLE = 7, not 1).
- SetupActivity: deduplicate buildDeviceLabel() to shared private method
- KioskActivity: fix KIOSK_DOWNLOAD_URL to point to kiosk-latest release
(was pointing to 'latest' which only has the gateway APK, so self-update
was silently broken).
- Bump version 1.4.0 -> 1.5.0 (versionCode 5 -> 6)
- KioskActivity: remove .use{} on PackageInstaller session to prevent premature
session close causing STATUS_FAILURE=1; align with SetupActivity pattern
- SetupActivity: show full diagnostic info (status code + human-readable hint,
device, Android version) in the UI card instead of just 'status=1'
- SetupActivity: use Build.PRODUCT/BOARD fallback when MANUFACTURER='unknown'
- ErrorReporter: add forceReport param to bypass in-session dedup for retries
- ErrorReporter: include Android SDK version in deviceInfo; fallback for
'unknown' MANUFACTURER/MODEL using PRODUCT/HARDWARE/BOARD
- installApk(): add runtime canRequestPackageInstalls() check with user dialog
(permission may have been revoked or not active even if declared in manifest)
- STATUS_FAILURE else branch: show AlertDialog with full diagnostics on screen
(status code, message, APK size, Android version, device model) so the
problem is visible even when the server error report fails to deliver
- Dialog has Riprova button (retries installWithPackageInstaller immediately)
and Salta button (skips to gateway status check)
- INSTALL_PERM_REQUEST onActivityResult: resume from installApk() if a local
APK file is already present, not just from triggerApkDownload()
- ErrorReporter context enriched with device model string
Two bugs caused the gateway APK install to always fail with status=1:
1. setAppPackageName() removed from SessionParams
This optional call forces the installer to verify the package name
against the APK's manifest. On some OEM/Android versions this
comparison fails even when the name is correct, returning the generic
STATUS_FAILURE (1) with no EXTRA_STATUS_MESSAGE. Removing it lets
the installer proceed without the extra check.
2. BroadcastReceiver was unregistered on STATUS_PENDING_USER_ACTION
On Android 11+ the final install result (STATUS_SUCCESS/STATUS_FAILURE)
arrives as a SECOND broadcast AFTER the user confirms the dialog.
The receiver was being unregistered immediately on the first broadcast
(PENDING_USER_ACTION), so the final result was never received.
Fix: only unregister on terminal statuses (SUCCESS, FAILURE, ABORTED).
Additional improvements:
- STATUS_FAILURE_ABORTED (-1) handled explicitly: resets UI without
showing an error (user just pressed back on the confirmation dialog)
- session.abandon() called on exception instead of letting .use{} close
- ErrorReporter now includes apk_kb and android API level in context
- onActivityResult(INSTALL_CONFIRM_REQUEST) no longer sets success/failure
UI (the BroadcastReceiver is responsible for the final result)
SetupActivity was sending type 'install-failure' (hyphen) but the PHP
version-guard bypass list only checked for 'install_failure' (underscore).
Result: if the kiosk was not on the latest released version the error was
silently discarded and no GitHub issue was created.
Fix:
- SetupActivity: change type to 'install_failure' (underscore, consistent
with KioskActivity which already used the correct name)
- api/index.php: add 'install-failure' (hyphen) to the bypass list as
defensive fallback so old APK builds already in the field are covered too
ErrorReporter:
- Init ErrorReporter at Setup onCreate using any previously saved URL
(before the fix, init() was only called at step 3, so install errors
happening in step 4 were silently dropped)
- Add ErrorReporter.reportMessage() call in the STATUS_FAILURE else branch
of installWithPackageInstaller (was showing error UI but sending nothing)
Gateway pre-configuration:
- finishSetup() now detects has_scale=true + gateway installed
- If so, POSTs scale_enabled=true + scale_gateway_url=ws://127.0.0.1:8765
to the EverShelf server's save_settings API endpoint
- This means the webapp works with the scale out-of-the-box after setup
without the user having to go into web Settings and configure it manually
Permissions step:
- Add btnGrantPerms as class field (was only inline findViewById)
- Extract onPermissionsGranted() helper: transforms button to green '✅ Permessi
concessi — Continua →' and sets click listener to showStep(3)
instead of calling onPermissionsGranted() which advances to step 3 (Server)
- Remove the 600ms auto-advance; user controls when to proceed
- Add setup_perms_granted_next string in EN/IT/DE
Network discovery — wrong subnet fix:
- Skip virtual/VPN/cellular interfaces: tun, ppp, rmnet, pdp, ccmni, dummy, sit,
gre, v4-, v6-, p2p, ham, nordlynx prefixes
- Also skip intf.isVirtual interfaces
- Sort: wlan*/eth* interfaces first (highest priority), others after
- Show detected subnet(s) in UI immediately before scan starts
Problems fixed:
- f.get() sequential collection blocked on timed-out futures in submission order
→ replaced with ExecutorCompletionService: results collected as soon as ready
- WifiManager.getConnectionInfo() deprecated on Android 10+, could return IP=0
→ replaced with NetworkInterface.getNetworkInterfaces() for subnet detection
- No real-time feedback: status stuck on 'Scanning…' throughout
→ UI updated every ~120ms showing current IP:port (n/total)
- TCP socket pre-check (600ms) before HTTP probe: filters unreachable hosts instantly
→ reduces scan time from minutes to seconds on typical /24 networks
- executor.shutdownNow() cancels remaining tasks the moment server is found
- 60-thread pool instead of 40 for faster parallel scanning
- Remove unused WifiManager import
- SetupActivity: new Step 0 — language picker (IT/EN/DE) with large buttons,
hardcoded trilingual title so it's always readable; saves 'kiosk_language' pref,
calls recreate() via onSaveInstanceState to reload the Activity in chosen locale
- SetupActivity: new Step 5 — screensaver toggle (before Done), saves 'screensaver_enabled'
- All existing steps shifted: Welcome→1, Permissions→2, Server→3, Scale→4, Done→6
- Progress dots updated to 5 dots (steps 1-5)
- attachBaseContext override in SetupActivity, KioskActivity, SettingsActivity to
apply the saved locale to all Activities via SetupActivity.applyLocale()
- buildSummary now shows language, screensaver setting, and scale status
- New string resources: setup_screensaver_*, summary_lang, summary_scale_skip,
summary_screensaver_on/off in IT, EN, DE
The try expression had a spurious 'true' result in one branch which
made Kotlin infer the type as Any? instead of ByteArray?.
Simplified to a single try block with explicit type annotation
ByteArray? to eliminate the ambiguity.
Bug 1 — Uninstall loop (kiosk lock task blocks system uninstall UI):
startActivityForResult(ACTION_DELETE) was called while lock task was
active. The system uninstall activity is not in the lock task whitelist
so it either silently fails or creates an unresolvable loop.
Fix: call disableKioskLock() immediately before every ACTION_DELETE
intent (3 call sites). Call enableKioskLock() at the start of
onActivityResult(UNINSTALL_REQUEST) before retrying install.
Added 600 ms delay after uninstall so PackageManager finishes cleanup.
Bug 2 — Step 2 only checks HTTP connectivity, not PHP API:
testConnection() was checking the root URL only. A generic web server
could pass while the EverShelf PHP API was absent.
Fix: after HTTP 200-399 on the root URL, do a second GET to
/api/?action=check_update and check the response body contains
'latest_tag'|'webapp_version'|'ok'. Shows:
✅ Server EverShelf trovato e API attiva!
⚠ Server raggiungibile ma API PHP non trovata (codice N)
Bug 3 — STATUS_FAILURE=1 even after uninstall (invalid APK file):
GitHub DownloadManager follows redirects; if the release asset does
not exist yet, GitHub returns a 404 HTML page but DownloadManager
still reports STATUS_SUCCESSFUL. PackageInstaller then tries to parse
HTML as an APK and returns STATUS_FAILURE=1.
Fix: validate APK magic bytes (0x504B = 'PK') before calling
installWithPackageInstaller. If invalid: show error, delete corrupt
file, send ErrorReporter event, re-enable retry button.
Also renamed install error string to install_error_install (separate
from install_error_download) for clarity.
Bug 4 — ErrorReporter.serverBaseUrl empty during wizard install:
ErrorReporter.init() is called in onCreate() with the saved URL.
On first setup the URL is typed in step 2 and saved to prefs, but
ErrorReporter still has serverBaseUrl='' for the rest of that session.
Any install error in step 3 silently failed to POST.
Fix: call ErrorReporter.init(this, url) in btnStep2Next immediately
after prefs.edit().putString(KEY_URL, url) so step 3 has a live URL.
Server check (wizard step 3):
- New horizontal card above the scale question always shows server status
as soon as step 3 is entered: ⏳ checking → ✅ reachable / ⚠️ not reachable
- Pings GET $serverUrl/api/?action=check_update (5 s timeout)
- If reachable: 'Error reporting active — failures sent to GitHub Issues'
- If not reachable: 'Check the URL in step 2' warning
- checkServerReachability() called every time goToStep(3) runs
- Strings added in EN / IT / DE
Signature-conflict fallback (else branch in installWithPackageInstaller):
- When PackageInstaller returns a generic STATUS_FAILURE and the target
package is already installed, a signature conflict is the most likely
cause (CONFLICT/INCOMPATIBLE are caught separately earlier)
- New AlertDialog: 'Disinstalla e riprova' → startActivityForResult
ACTION_DELETE → UNINSTALL_REQUEST → auto-retries install on return
- Only shown when all else has already failed