Restore real InputConnection to fix barcode scanner input
Returning null from onCreateInputConnection() prevented the Chromium renderer from relaying hardware key events into HTML inputs, breaking scanner input on the Urovo CT58C. Return the real InputConnection from super and rely on the multi-layer keyboard suppression (JS inputmode, InputMethodManager, EditorInfo flags, manifest flags) which is already proven to work on this device. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
72ee038cde
commit
b7cf7256ac
10
CLAUDE.md
10
CLAUDE.md
@ -6,7 +6,15 @@ Native Android app (Kotlin) that wraps `https://smart.joykoo.mx/` in a fullscree
|
||||
|
||||
## Critical Design Decision
|
||||
|
||||
The Android soft keyboard must NEVER appear. The custom `NoSoftKeyboardWebView` returns `null` from `onCreateInputConnection()` to prevent the IME from ever being invoked, while hardware key events from the scanner still flow through the standard View key-event dispatch chain. Do not revert this or add an InputConnection — it is the core of the app.
|
||||
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
|
||||
|
||||
|
||||
@ -4,46 +4,25 @@ import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputConnection
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.webkit.WebView
|
||||
|
||||
/**
|
||||
* WebView subclass that prevents the Android software keyboard (IME) from
|
||||
* appearing while preserving hardware keyboard / barcode-scanner key events.
|
||||
*
|
||||
* HOW IT WORKS
|
||||
* ============
|
||||
* When an HTML <input> inside a WebView gains focus, the framework calls
|
||||
* onCreateInputConnection() on the focused View. If this method returns a
|
||||
* non-null InputConnection, the InputMethodManager (IMM) has a channel to
|
||||
* the IME and will show the software keyboard.
|
||||
* Keyboard suppression uses multiple layers:
|
||||
* 1. onCheckIsTextEditor() → false: first signal to the IME not to show.
|
||||
* 2. EditorInfo flags (NO_EXTRACT_UI, NO_FULLSCREEN): prevent fullscreen IME.
|
||||
* 3. InputMethodManager.hideSoftInputFromWindow posted after each
|
||||
* InputConnection request: force-hides if the IME appears anyway.
|
||||
* 4. JavaScript inputmode="none" injection (in MainActivity): tells the
|
||||
* Chromium renderer not to request the keyboard for HTML inputs.
|
||||
* 5. Manifest stateAlwaysHidden|adjustNothing: system-level default.
|
||||
*
|
||||
* By returning null, we tell the IMM that this View does not participate in
|
||||
* the input-method protocol. The IMM therefore never shows the soft keyboard
|
||||
* for this View — it is prevented from opening in the first place rather
|
||||
* than being hidden reactively after it appears.
|
||||
*
|
||||
* WHY HARDWARE/SCANNER INPUT STILL WORKS
|
||||
* =======================================
|
||||
* Hardware key events (from a physical keyboard or a barcode scanner in
|
||||
* HID/keyboard-wedge mode) are delivered through the Android View key-event
|
||||
* dispatch pipeline: Activity.dispatchKeyEvent → ViewGroup.dispatchKeyEvent
|
||||
* → View.dispatchKeyEvent → View.onKeyDown/onKeyUp. This pipeline is
|
||||
* completely independent of InputConnection/IME. WebView's internal
|
||||
* key-event handling (which forwards events to the Chromium renderer and
|
||||
* into focused HTML inputs) works through this pipeline, so hardware key
|
||||
* events continue to reach whichever HTML input has focus inside the
|
||||
* WebView.
|
||||
*
|
||||
* The only thing lost by returning null is the ability to receive composed
|
||||
* text from a software IME (e.g. predictive text, swipe typing, CJK
|
||||
* composition). Since this device is a dedicated barcode scanner with no
|
||||
* need for software keyboard input, this is the desired behavior.
|
||||
*
|
||||
* ADDITIONAL SAFETY: onCheckIsTextEditor()
|
||||
* ========================================
|
||||
* Some Android IME implementations also check onCheckIsTextEditor() to
|
||||
* decide whether to show. Returning false provides a second signal that
|
||||
* this View does not want IME interaction.
|
||||
* The real InputConnection from super is returned so that hardware key
|
||||
* events from the barcode scanner are relayed into focused HTML inputs
|
||||
* by the Chromium renderer.
|
||||
*/
|
||||
class NoSoftKeyboardWebView : WebView {
|
||||
|
||||
@ -53,16 +32,18 @@ class NoSoftKeyboardWebView : WebView {
|
||||
super(context, attrs, defStyleAttr)
|
||||
|
||||
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection? {
|
||||
// Returning null prevents the IMM from establishing a connection to
|
||||
// any software IME. The soft keyboard cannot appear because there is
|
||||
// no InputConnection for it to communicate through. Hardware key
|
||||
// events still flow via the View key-event dispatch chain.
|
||||
return null
|
||||
val ic = super.onCreateInputConnection(outAttrs)
|
||||
outAttrs.imeOptions = outAttrs.imeOptions or
|
||||
EditorInfo.IME_FLAG_NO_EXTRACT_UI or
|
||||
EditorInfo.IME_FLAG_NO_FULLSCREEN
|
||||
post {
|
||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(windowToken, 0)
|
||||
}
|
||||
return ic
|
||||
}
|
||||
|
||||
override fun onCheckIsTextEditor(): Boolean {
|
||||
// Signal to the framework that this View is not a text editor,
|
||||
// preventing IME-related behaviors from triggering.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user