Not able to track crash in the project, I got this error in play store pre-launch section, it showing on click of EditText
, it got the error. but not getting any cr
I was seeing his same error, both in the pre-launch report and in FireBase test lab. I spent several hours looking into this and I am sure it is a bug that only affects SDK <= 23 and is triggered by AccessibilityNodeInfo#performAction(ACTION_SET_TEXT). It seems this method completely ignores the maxLength attribute which in turn causes the IndexOutOfBoundsException.
You can duplicate this same exception with the following code, making sure the string "1234" is longer than your maxLength:
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "1234");
mEditText.performAccessibilityAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
So, what to do about it?
One option is to just ignore it as it is likely to affect a very small subset of users. that is unless you think you might have many users using accessibility functions to enter text and they are also using older SDKs (<= 23).
Another option would be to set your minSDKVersion in your build.gradle to 24. This might hurt if you have a lot of users using SDK <= 23
A third option would be to use this very ugly workaround I came up with:
if(Build.VERSION.SDK_INT <= 23){
ViewGroup rootView = findViewById(R.id.your_layout_that_contains_edittexts);
ArrayList<View> views = rootView.getTouchables();
for(View view : views){
if(view instanceof EditText){
EditText mEditText = (EditText)view;
mEditText.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
if (action == AccessibilityNodeInfo.ACTION_SET_TEXT) {
//do something here to make sure index out of bounds does not occur
int maxLength = 0;
for(InputFilter filter : mEditText.getFilters()){
if(filter instanceof InputFilter.LengthFilter) {
maxLength = ((InputFilter.LengthFilter)filter).getMax();
}
}
Set<String> keys = arguments.keySet();
for(String key : keys){
if(arguments.get(key) instanceof CharSequence){
if(arguments.get(key) != null) {
arguments.putCharSequence(key, ((CharSequence) arguments.get(key)).subSequence(0, maxLength));
mEditText.setText(arguments.getCharSequence(key));
}
}
}
}
return true;
}
});
}
}
}
OK, so I did say it was ugly, but it does work. Replace your_layout_that_contains_edittexts with the id of a layout that contains all your EditTexts.
Basically, if SDK <= 23, it cycles through all the EditTexts in the ViewGroup and for each one, overrides the performAccessibilityAction via AccessibilityDelegate and ensures that the text being entered never exceeds the EditText's maxLength value.
In my case EditText did not have a maxLength
set. However we are using some custom InputFilter
. Here how it looks like:
private val textFilter = object : InputFilter {
override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
return when {
good -> null // just allow change as is
bad -> "" // ignore change totally[*].
else -> magic // return a calculated value
}
}
}
Problem[*] was ignoring the value totally by returning empty String which was causing crash on Marshmellow and below devices. Here is how I have solved this:
private val textFilter = object : InputFilter {
override fun filter(source: CharSequence, start: Int, end: Int, dest: Spanned, dstart: Int, dend: Int): CharSequence? {
return when {
good -> null // just allow change as is
bad -> (dest.toString() + source.subSequence(start, end)).subSequence(dstart, dstart + (end - start)) // ignore change totally[*].
else -> magic // return a calculated value
}
}
}
Key Point is not to return anything smaller than the change (end-start)
(on M and below devices).
I still thank previous answers which helped me to identify the root cause.
Inspired by @smitty1's solution I made this one. I handle it in a custom EditText instead of checking all EditTexts in a view:
open class MaxLengthEditText : AppCompatAutoCompleteTextView {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
/*
* On Android API versions <= 23 the app crashes if a text that is longer than the max length of the EditText
* is set using an accessibility action. To avoid this we cut the text down to the maximum allowed length.
*/
override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
if (Build.VERSION.SDK_INT <= 23 && action == AccessibilityNodeInfo.ACTION_SET_TEXT) {
filters.forEach { filter ->
if (filter is InputFilter.LengthFilter) {
val maxLength = filter.max
arguments?.keySet()?.forEach { key ->
if (arguments[key] is CharSequence) {
val shorterText =
arguments.getCharSequence(key)?.subSequence(0, maxLength)
setText(shorterText)
return true
}
}
}
}
}
return super.performAccessibilityAction(action, arguments)
}
}