diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt index 6f7823a4c..e364cd7f8 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt @@ -420,7 +420,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown // triggers ClickableSpan onClick() events movementMethod = EnhancedMovementMethod - setupBackspaceAndEnterDetection() + setupKeyListenersAndInputFilters() //disable auto suggestions/correct for older devices if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { @@ -440,12 +440,44 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown // Setup the keyListener(s) for Backspace and Enter key. // Backspace: If listener does return false we remove the style here // Enter: Ask the listener if we need to insert or not the char - private fun setupBackspaceAndEnterDetection() { + private fun setupKeyListenersAndInputFilters() { //hardware keyboard setOnKeyListener { _, _, event -> handleBackspaceAndEnter(event) } + // This InputFilter created only for the purpose of avoiding crash described here: + // https://android-review.googlesource.com/c/platform/frameworks/base/+/634929 + // https://github.com/wordpress-mobile/AztecEditor-Android/issues/729 + // the rationale behind this workaround is that the specific crash happens only when adding/deleting text right + // before an AztecImageSpan, so we detect the specific case and re-create the contents only when that happens. + // This is indeed tackling the symptom rather than the actual problem, and should be removed once the real + // problem is fixed at the Android OS level as described in the following url + // https://android-review.googlesource.com/c/platform/frameworks/base/+/634929 + val dynamicLayoutCrashPreventer = InputFilter { source, start, end, dest, dstart, dend -> + var temp = source + if (dest.length > dend+1) { + // if there are any images right after the destination position, hack the text + val spans = dest.getSpans(dstart, dend+1, AztecImageSpan::class.java) + if (spans.isNotEmpty()) { + // test: is this an insertion? + if (dstart == dend) { + // take the source (that is, what is being inserted), and append the Image to it. We will delete + // the original Image later so to not have a duplicate. + // use Spannable to copy / keep the current spans + temp = SpannableStringBuilder(source).append(dest.subSequence(dend, dend+1)) + + // delete the original AztecImageSpan + text.delete(dend, dend+1) + // now insert both the new insertion _and_ the original AztecImageSpan + text.insert(dend, temp) + temp = "" // discard the original source parameter as an ouput from this InputFilter + } + } + } + temp + } + val emptyEditTextBackspaceDetector = InputFilter { source, start, end, dest, dstart, dend -> if (selectionStart == 0 && selectionEnd == 0 && end == 0 && start == 0 @@ -462,7 +494,12 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown source } - filters = arrayOf(emptyEditTextBackspaceDetector) + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O || Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1) { + // dynamicLayoutCrashPreventer needs to be first in array as these are going to be chained when processed + filters = arrayOf(dynamicLayoutCrashPreventer, emptyEditTextBackspaceDetector) + } else { + filters = arrayOf(emptyEditTextBackspaceDetector) + } } private fun handleBackspaceAndEnter(event: KeyEvent): Boolean {