How do I get non-keyboard characters from an input method without displaying a virtual keyboard?

72 Views Asked by At

I am writing an application for a Datalogic handheld barcode scanner running Android. My app can retrieve barcodes via API, but the scanner can also emulate the keyboard using an invisible input method. I want to use the second approach as a more universal one: then my app will work on any scanner that can emulate the keyboard, or even without a scanner but with a physical keyboard.

In keyboard emulation mode, all characters present on the keyboard are sent as keyboard events, and I can get them in the onKeyDown or onKeyUp method of the view.

Other characters are sent to the commitText() method of the InputConnection of the view.

Example: GS1 barcode (10)ABC(21)DEF with the actual content "10ABC{FNC1}21DEF":

  • characters "10ABC" are sent via onKeyDown()/onKeyUp().
  • character {FNC1} (aka group separator, code \x1D) is sent via commitText()
  • character "21DEF" is sent via onKeyDown()/onKeyUp()

Problem: if I use InputConnection, on a device with a hardware keyboard but no invisible input method (like Android Studio emulator), when a press a hardware key, a virtual keyboard opens. How can I avoid this?

The screen on which the barcodes are scanned consists of a focused LinearLayout with several unfocused TextViews showing the result of the scan. Simplified example:

package com.example.myapplication

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.InputType
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.inputmethod.BaseInputConnection
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.widget.LinearLayout
import android.widget.TextView

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val myLinearLayout = MyLinearLayout(this)
        myLinearLayout.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
        myLinearLayout.orientation = LinearLayout.VERTICAL
        myLinearLayout.isFocusableInTouchMode = true
        myLinearLayout.requestFocus()

        val textView = TextView(this)
        textView.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
        myLinearLayout.addView(textView)

        setContentView(myLinearLayout)
    }
}

class MyLinearLayout @JvmOverloads constructor(context: Context, attributeSet: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0) : LinearLayout(context, attributeSet, defStyleAttr, defStyleRes) {
    fun appendText(text: String) {
        val textView = getChildAt(0) as TextView
        textView.text = textView.text.toString() + text
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        if (event.unicodeChar != 0) {
            appendText(event.unicodeChar.toChar().toString())
            return true
        }
        return super.onKeyDown(keyCode, event)
    }

    override fun onCreateInputConnection(outAttrs: EditorInfo?): InputConnection {
        outAttrs?.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
        return MyInputConnection(this, false)
    }
}

class MyInputConnection(private val targetView: MyLinearLayout, fullEditor: Boolean) : BaseInputConnection(targetView, fullEditor) {
    override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
        targetView.appendText("\ncommitText: " + text.toString() + "\n")
        return true
    }
}
1

There are 1 best solutions below

5
Gabe Sechan On

You don't need a hidden UI element. It acts like a bluetooth keyboard. It will go to the Activity's onKeyUp and onKeyDown methods if no field has input focus.