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

6.1 KiB

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.