androidkotlininputandroid-webviewandroid-input-method

How to simulate typing with char-by-char delay using InputConnection?


I need to simulate typing text with char-by-char delays using soft input keyboard on a WebView. I can't use WebView#dispatchKeyEvent because it's not suitable for typing Unicode text and does not simulate soft input keyboard.

I am overriding WebView#onCreateInputConnection to get a reference to the actual input connection being used by the webview so I can send input to it like soft input keyboard does:

class CustomWebView(context: Context, attrs: AttributeSet): WebView(context, attrs) {
    private var inputConnection: InputConnection? = null

    override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection? {
        val newInputConnection = super.onCreateInputConnection(outAttrs) ?: return inputConnection
        inputConnection = newInputConnection
        return newInputConnection
    }

    suspend fun sendTextInput(text: String, keyDelayStart: Long, keyDelayEnd: Long) {
        val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager ?:
            throw Exception("could not get system's InputMethodManager")
        requestFocus()
        val defer = CompletableDeferred<Int>()
        // AsyncResultReceiver is a custom ResultReceiver which resolves a deferred on the callback
        imm.showSoftInput(this, 0, AsyncResultReceiver(WeakReference(defer)))
        val result = defer.await()
        if (result == InputMethodManager.RESULT_HIDDEN || result == InputMethodManager.RESULT_UNCHANGED_HIDDEN)
            throw Exception("could not show IME keyboard")
        val ic = onCreateInputConnection(EditorInfo()) ?:
            throw Exception("could not create InputConnection")
        for (char in text) {
            ic.commitText(char.toString(), 1)
            delay(Random.nextLong(keyDelayStart, keyDelayEnd))
        }
        imm.hideSoftInputFromWindow(windowToken, 0)
    }
}

I tested by typing Hello, world! on an input field, but when typing next chars it's clearing previous ones:

enter image description here

when typing backwards with ic.commitText(char.toString(), 0) it types fully:

enter image description here

The test is running on Android 12.


Solution

  • InputConnection#commitText() replaces the contents of the actual composing region. To add new characters you need to move the composing region forward (the "cursor") with InputConnection#setComposingRegion(). Try:

    for (i in text.indices) {
        ic.setComposingRegion(i, i + 1)
        ic.commitText(text[i].toString(), 1)
        delay(Random.nextLong(keyDelayStart, keyDelayEnd))
    }