Search code examples
androidandroid-edittextandroid-databinding

Databinding : InputMethodManager: startInputInner - mService.startInputOrWindowGainedFocus


For security needs I'm using the char array version of setText to avoid using String version.

public final void setText (char[] text, 
                int start, 
                int len)

BindingAdapter :

public class DataBindingAdapter {
    @BindingAdapter("android:text")
    public static void setCharArray(ClearableEditText view, char[] value) {
        if(value == null) return;
        Log.v("BindingAdapter", String.valueOf(value));
        view.setText(value, 0, value.length);
    }

    @InverseBindingAdapter(attribute = "android:text")
    public static char[] getArrayFromText(ClearableEditText view) {
        int length = view.getText().length();
        char[] password = new char[length];

        view.getText().getChars(0, length, password, 0);

        Log.v("BindingAdapter", String.valueOf(password));

        return password;
    }

}

Emulator behavior :

But the issue is when I enter ABCD in a EditText I got DCBA displayed plus the focus stay always in the beginning of the EditText.

Real device behavior :

Only the first char is displayed.

Logs :

2020-02-04 17:37:43.760 4110-4110/com.bla V/InputMethodManager: Starting input: tba=com.bla ic=com.android.internal.widget.EditableInputConnection@3e6e221 mNaviBarColor -16750956 mIsGetNaviBarColorSuccess true , NavVisible : true , NavTrans : false 2020-02-04 17:37:43.760 4110-4110/com.bla D/InputMethodManager: startInputInner - Id : 0 2020-02-04 17:37:43.767 4110-4110/com.bla I/InputMethodManager: startInputInner - mService.startInputOrWindowGainedFocus


Solution

  • IMO CharArray sucks with databinding because there is no issue when I change CharArray to String which mean the equals method is broken with the genrated code.

        @BindingAdapter("android:text")
        public static void setCharArray(EditText view, char[] value) {
            if(value == null) return;
    
            int length = view.length();
            char[] password = new char[length];
            view.getText().getChars(0, length, password, 0);
    
            if (Arrays.equals(value, password)) {
                Log.v("BindingAdapter out" + view.getId(), String.valueOf(value));
                view.setText(value, 0, value.length);
            }
        }
    

    Edit 1 : Better solution

    Use a custom class that implement CharSequence and hence the adapter version of setText is CharSequence based there is no need to have a BindingAdapter in my side.

    class SecureString(value: CharArray?) : CharSequence {
        @Transient
        private val value: CharArray
    
        init {
            requireNotNull(value) { "Value must not be null" }
            this.value = value
        }
    
        override val length: Int
            get() = value.size
    
        override fun get(index: Int): Char = value[index]
    
        override fun subSequence(start: Int, end: Int): CharSequence {
            throw UnsupportedOperationException()
        }
    
        fun clear() {
            Arrays.fill(value, '\u0000')
        }
    
        fun toCharArray(): CharArray {
            return value
        }
    
    }
    

    Adapt the InverseBindingAdapter to return SecureString:

        @InverseBindingAdapter(attribute = "android:text")
        public static SecureString getSecureString(EditText view) {
            int length = view.length();
            SecureString password = new SecureString(new char[length]);
    
            view.getText().getChars(0, length, password.toCharArray(), 0);
    
            return password;
        }