Android key monitoring and keyboard event flow (deletion key cannot be monitored)

Time:2021-8-14

Android key monitoring and keyboard event flow (deletion key cannot be monitored)

Recently, when doing a password key input function, we need to process each key, so we used itOnKeyListenerInterface listening. You can listen to input key events in normal text format, but once the input type of EditText is modified to numberpassword (Android: inputtype = “numberpassword”), you cannot listen to delete button events on the keyboard.

Therefore, access to information:

General useOnKeyListenerThe interface monitoring key events are as follows:

editText.setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if(event.getAction() == KeyEvent.ACTION_DOWN) {
                switch (keyCode) {
                    case KeyEvent.KEYCODE_DEL:
                        //Handling related backspace key behavior
                        return true;
                    ...
                }
            }
            return false;
        }
    });

The above scheme has no problem in most cases, but when usedandroid:inputType="numberPassword"The event was not responded. So I read aboutOnKeyListenerNotes for:

/**
     * Interface definition for a callback to be invoked when a hardware key event is
     * dispatched to this view. The callback will be invoked before the key event is
     * given to the view. This is only useful for hardware keyboards; a software input
     * method has no obligation to trigger this listener.
     */
    public interface OnKeyListener {
        /**
         * Called when a hardware key is dispatched to a view. This allows listeners to
         * get a chance to respond before the target view.
         * Key presses in software keyboards will generally NOT trigger this method,
         * although some may elect to do so in some situations. Do not assume a
         * software input method has to be key-based; even if it is, it may use key presses
         * in a different way than you expect, so there is no way to reliably catch soft
         * input key presses.
         */
        boolean onKey(View v, int keyCode, KeyEvent event);
    }

The general meaning of class annotation is that hardware keys will call back this interface, which is only useful for hardware keyboards. The software ime has no obligation to trigger this listener.
The annotation of the method roughly means that the hardware key will be sent here, but the key in the software keyboard usually does not trigger this method. Although you may choose to do so in some cases, do not assume that the software input method must be based on a key. Even so, it may use keys differently than you expect, so it cannot reliably capture soft input keys( This monitoring is not reliable for soft keyboards. Since it is not reliable, what method is recommended by Google? It is known by consulting materials.

InputConnectionInterface

/**
 * The InputConnection interface is the communication channel from an
 * {@link InputMethod} back to the application that is receiving its
 * input. It is used to perform such things as reading text around the
 * cursor, committing text to the text box, and sending raw key events
 * to the application.
 ....
 */
public interface InputConnection {
    ...
}

From the above note, we know that the inputconnection interface is the communication channel from {@ link inputmethod} to the application receiving its input. It is used to perform things such as reading the text around the cursor, submitting the text to the text box, and sending the original key event to the application.
In fact, Google’s keyboard input streaming is done like this:
avatar
Inputconnection has several key methods. By rewriting these methods, we can basically intercept all input and click events of the soft keyboard:

//When the input method inputs characters, including expressions, letters, words, numbers and symbols, it will call back the method
public boolean commitText(CharSequence text, int newCursorPosition) 

//When there is a key input, the method will be called back. For example, when clicking the backspace key, the Sogou input method should call this method,
//The keyevent is sent, but the Google input method does not call this method, but calls the following deletesurroundingtext() method.  
public boolean sendKeyEvent(KeyEvent event);   

//When there is a text deletion operation (cutting, click the backspace key), this method will be triggered 
public boolean deleteSurroundingText(int beforeLength, int afterLength) 

//This method is called back when the combined text input is finished
public boolean finishComposingText();

thatInputConnectionHow to associate with EditText?

In fact, when EditText establishes a connection with the input method, EditText’sonCreateInputConnection()Method is triggered:

/**
     * Create a new InputConnection for an InputMethod to interact
     * with the view.  The default implementation returns null, since it doesn't
     * support input methods.  You can override this to implement such support.
     * This is only needed for views that take focus and text input.
     *
     * When implementing this, you probably also want to implement
     * {@link #onCheckIsTextEditor()} to indicate you will return a
     * non-null InputConnection.
     *
     * Also, take good care to fill in the {@link android.view.inputmethod.EditorInfo}
     * object correctly and in its entirety, so that the connected IME can rely
     * on its values. For example, {@link android.view.inputmethod.EditorInfo#initialSelStart}
     * and  {@link android.view.inputmethod.EditorInfo#initialSelEnd} members
     * must be filled in with the correct cursor position for IMEs to work correctly
     * with your application.
     */
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        return null;
    }

The comment only pastes the core part, which roughly means: create a new inputconnection for inputmethod to interact with the view. The default implementation returns null because it does not support input methods. You can override it to achieve this support. Required only for views with focus and text input.
When implementing this feature, you may also want to implement oncheckistexteditor () to indicate that you will return a non null inputconnection.
In addition, be sure to fill in the editorinfo object correctly and completely so that the connected ime can depend on its value. For example, the initialselstart and initialselend members must be populated with the correct cursor position for ime to work correctly with your application.

In other words, we only need to implement this method and give a return of the implementation interface, and we can take over keyboard input. This method is the view method, which expands the imagination, that is, any view can respond to keys. Can we use it directly here? Not because the interface does not provide routine processing. If it is fully implemented by ourselves, we need to complete other key related processing, and the workload is still huge. If EditText has this function, it should also be implemented in real time. stayTextViewIt is provided inEditableInputConnectionClass to handle input, but it is hide and cannot be inherited. Maybe from a security point of view, so there is no way? In fact, Google provides us with a classInputConnectionWrapperA default proxy class that completes most of the normal operations. We can inherit this class to implement replacement for the part we want.

/**
 * Wrapper class for proxying calls to another InputConnection.  Subclass and have fun!
 */
public class InputConnectionWrapper implements InputConnection {
    ...
}

Comment explanation: wrapper class, used to proxy the call to another inputconnection. Subclass, have fun( Google engineers are still humorous)
Here, we can complete the interception and monitoring of the keyboard by implementing this class.

/**
 *Edit text controls that are always entered from the tail
 */
public class TailInputEditText extends AppCompatEditText {

    public TailInputEditText(Context context) {
        this(context, null);
    }

    public TailInputEditText(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.editTextStyle);
    }

    public TailInputEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onSelectionChanged(int selStart, int selEnd) {
        super.onSelectionChanged(selStart, selEnd);
        If (selstart = = selend) {// prevent multiple selections
            If (gettext() = = null) {// judge null to prevent null pointers
                setSelection(0);
            }else {
                setSelection(getText().length()); //  Make sure the cursor is always at the back
//                setSelection(0, getText().length());
            }
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        //Other key event responses
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        //Return to your own implementation
        return new BackspaceInputConnection(super.onCreateInputConnection(outAttrs), true);
    }

    private class BackspaceInputConnection extends InputConnectionWrapper {

        public BackspaceInputConnection(InputConnection target, boolean mutable) {
            super(target, mutable);
        }

        /**
         *Before the soft keyboard deletes the text, it will call this method to notify the input box. We can override this method and determine whether to intercept the deletion event.
         *On the Google input method, {@ link #sendkeyevent (keyevent)} will not be called when clicking the backspace key,
         *Instead, you directly call back this method, so you should also intercept this method;
         * */
        @Override
        public boolean deleteSurroundingText(int beforeLength, int afterLength) {
            //Do what you want to do is intercept him
            return super.deleteSurroundingText(beforeLength, afterLength);
        }

    }

}

The above is an implementation that contains interceptors and is associated with controls. Of course, you can also complete it without internal classes. I just briefly describe it.

Here we come to a short paragraph on the interception of keyboard events. What other ideas can be left for discussion.