JoykooSmartApp/CLAUDE.md
caspar d22af8f256 Show an explicit camera/gallery choice for photo uploads
Image file inputs went straight to the gallery picker when the camera was
hidden by the system chooser or its permission was missing. They now show a
Tomar foto / Elegir de la galería dialog (capture fields open the camera
directly), and a denied camera permission explains how to enable it in
Settings instead of silently falling back to the picker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:07:04 -04:00

106 lines
6.1 KiB
Markdown

# JoykooScanner — Android Barcode Scanner Kiosk App
## What This Is
Native Android app (Kotlin) that wraps `https://smart.joykoo.mx/` in a fullscreen WebView for dedicated Chinese industrial handheld barcode scanners. The scanner operates in HID/keyboard-wedge mode — it sends Android key events like a physical keyboard.
## Critical Design Decision
The Android soft keyboard must NEVER appear. Keyboard suppression uses multiple layers — do not remove any of them:
1. `NoSoftKeyboardWebView.onCheckIsTextEditor()` → `false`
2. `EditorInfo` flags `IME_FLAG_NO_EXTRACT_UI | IME_FLAG_NO_FULLSCREEN` in `onCreateInputConnection()`
3. `InputMethodManager.hideSoftInputFromWindow()` posted after each InputConnection request and on page load / window focus
4. JavaScript injection setting `inputmode="none"` on all `<input>`, `<textarea>`, and `contenteditable` elements via `MutationObserver`
5. Manifest `windowSoftInputMode="stateAlwaysHidden|adjustNothing"`
The real `InputConnection` from `super.onCreateInputConnection()` **must be returned** — returning `null` breaks hardware key input from the barcode scanner on devices like the Urovo CT58C.
## Project Structure
```
app/src/main/java/mx/joykoo/scanner/
AppConfig.kt — URL, allowed hosts, admin triggers, scanner broadcasts (single config point)
MainActivity.kt — Fullscreen kiosk activity, WebView setup, navigation, lifecycle
NoSoftKeyboardWebView.kt — IME suppression via null InputConnection
ScannerIntegration.kt — Scanner lifecycle interface
BroadcastScannerIntegration.kt — Intent-output scans → JS injection (React-safe value setter)
KioskController.kt — Lock Task kiosk toggle (session-only until KIOSK_PERSISTED); device-owner full lockdown + HOME alias, else screen pinning
KioskAdminReceiver.kt — DeviceAdminReceiver for `dpm set-device-owner`
StatusStrip.kt — App-drawn status strip (Wi-Fi + battery only); system bars stay hidden
Permissions.kt — Runtime permission prompts (camera, storage ≤ Android 9); asked at launch and on demand
FileTransfers.kt — Downloads → Downloads folder; file uploads with camera option; photos kept in Pictures/Joykoo
```
## Build
- **Language**: Kotlin, targeting JVM 1.8 bytecode
- **SDK**: compileSdk 34, minSdk 24, targetSdk 34
- **Dependencies**: androidx core-ktx, appcompat, webkit, swiperefreshlayout
- **Gradle**: 8.7 with Kotlin DSL, AGP 8.5.2, Kotlin 1.9.24
- **Package**: `mx.joykoo.scanner`
### Build prerequisites (not yet installed on this server)
1. `sudo dnf install java-17-openjdk-devel` — only the JRE is installed, need javac
2. Android SDK cmdline-tools — download to `~/Android/Sdk/`, then install:
- `platform-tools`
- `platforms;android-34`
- `build-tools;34.0.0`
3. Set env vars:
```
JAVA_HOME=/usr/lib/jvm/java-17-openjdk-17.0.20.1.1-1.2.el9.x86_64
ANDROID_HOME=~/Android/Sdk
```
4. `./gradlew assembleDebug`
### Server environment (RHEL 9.8)
- JRE 17 installed at `/usr/lib/jvm/java-17-openjdk-17.0.20.1.1-1.2.el9.x86_64`
- 323 GB free on /home, 5.5 GB free on /
- curl, wget, unzip, zip available
- No Android SDK present anywhere
## Configuration
All configurable values are in `AppConfig.kt`:
- `HOME_URL` — the webpage to load
- `ALLOWED_HOSTS` — domains that stay inside the WebView
- `BACK_PRESS_EXIT_COUNT` / `BACK_PRESS_WINDOW_MS` — back-button exit guard
- `ADMIN_VOLUME_HOLD_MS` — hold Volume Up + Volume Down this long to open the admin dialog
- `ADMIN_BARCODE` — scanning this value (Intent mode) opens the admin dialog
- `KIOSK_BARCODE` — scanning this value (Intent mode) toggles kiosk lockdown
- `RENDERER_CRASH_LIMIT` / `RENDERER_CRASH_WINDOW_MS` — WebView crash auto-recovery limit
## Navigation Rules
- URLs on `joykoo.mx` or `smart.joykoo.mx` (and subdomains) load inside the WebView
- All other URLs are silently blocked
- Back button goes back in WebView history; 3 rapid presses exits when no history
- Admin dialog (exit / toggle soft keyboard): hold both volume keys 3 s, or scan `ADMIN_BARCODE`. Volume keys are consumed and never reach the page. No touch gestures — corner taps leaked through to site UI
## Kiosk & Errors
- Kiosk mode (`KioskController`): **off by default and not persisted** while it is being tested (`AppConfig.KIOSK_PERSISTED = false`) — each launch starts unlocked and clears any leftover lockdown; scanning `KIOSK_BARCODE` toggles it for the session. When on, admin dialog hides "Salir" and Back never exits. Device owner required for real lockdown; the app can't be uninstalled while device owner, so set it only with the release-signed APK.
- Error overlay: per-cause messages (no Wi-Fi with auto-retry on reconnect, unreachable, 5xx, SSL, repeated renderer crash). 4xx responses are left to the site.
- Renderer crash (`onRenderProcessGone`): the WebView is replaced and the last URL (tracked via `doUpdateVisitedHistory`) reloaded.
## Files & Permissions
- Permissions are requested at first launch (camera; storage on Android ≤ 9) and again when a feature needs one.
- Downloads: https on allowed hosts via `DownloadManager` (session cookie forwarded); `data:` decoded natively; `blob:` read in-page and returned via `evaluateJavascript` polling (no JS interface). A document-start hook keeps blobs readable if the site revokes the URL immediately. Saved to Downloads, with a toast on completion.
- Uploads: `<input type=file>` image fields show an in-app "Tomar foto / Elegir de la galería" dialog (the system chooser hid the camera behind the gallery picker); `capture` opens the camera directly. If camera permission is denied, a dialog offers app Settings or the file picker. Photos are saved to Pictures/Joykoo.
- In-page camera (`getUserMedia`) is granted to allowed hosts only, video only.
## Security
- HTTPS only, no cleartext, mixed content never allowed, no SSL error bypass
- No JavaScript interfaces exposed
- No file:// or content:// access
- Network security config enforces system trust anchors only
- Domain allowlist in WebViewClient
## UI Strings
Spanish (`es`) — this is for a Mexico-based company.