diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt index a6b2beeb7..7c61c2d03 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt @@ -48,6 +48,8 @@ import android.view.MotionEvent import android.view.View import android.view.WindowManager import android.view.inputmethod.BaseInputConnection +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputConnection import android.widget.CheckBox import android.widget.EditText import android.widget.Toast @@ -70,6 +72,7 @@ import org.wordpress.aztec.handlers.ListHandler import org.wordpress.aztec.handlers.ListItemHandler import org.wordpress.aztec.handlers.PreformatHandler import org.wordpress.aztec.handlers.QuoteHandler +import org.wordpress.aztec.ime.EditorInfoUtils import org.wordpress.aztec.plugins.IAztecPlugin import org.wordpress.aztec.plugins.IToolbarButton import org.wordpress.aztec.source.Format @@ -118,6 +121,7 @@ import org.wordpress.aztec.watchers.event.text.BeforeTextChangedEventData import org.wordpress.aztec.watchers.event.text.OnTextChangedEventData import org.wordpress.aztec.watchers.event.text.TextWatcherEvent import org.xml.sax.Attributes +import java.lang.ref.WeakReference import java.security.MessageDigest import java.security.NoSuchAlgorithmException import java.util.ArrayList @@ -291,6 +295,9 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown private var focusOnVisible = true + var inputConnectionRef: WeakReference? = null + var inputConnectionEditorInfo: EditorInfo? = null + interface OnSelectionChangedListener { fun onSelectionChanged(selStart: Int, selEnd: Int) } @@ -660,6 +667,45 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown } } + override fun onCreateInputConnection(outAttrs: EditorInfo) : InputConnection { + // limiting the reuseInputConnection fix for Anroid 8.0.0 for now + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) { + return handleReuseInputConnection(outAttrs) + } + + return super.onCreateInputConnection(outAttrs) + } + + private fun handleReuseInputConnection(outAttrs: EditorInfo) : InputConnection { + // initialize inputConnectionEditorInfo + if (inputConnectionEditorInfo == null) { + inputConnectionEditorInfo = outAttrs + } + + // now init the InputConnection, or replace if EditorInfo contains anything different + if (inputConnectionRef?.get() == null || !EditorInfoUtils.areEditorInfosTheSame(outAttrs, inputConnectionEditorInfo!!)) { + // we have a new InputConnection to create, save the new EditorInfo data and create it + // we make a copy of the parameters being received, because super.onCreateInputConnection may make changes + // to EditorInfo params being sent to it, and we want to preserve the same data we received in order + // to compare. + // (see https://android.googlesource.com/platform/frameworks/base/+/jb-mr0-release/core/java/android/widget/ + // TextView.java#5404) + inputConnectionEditorInfo = EditorInfoUtils.copyEditorInfo(outAttrs) + val localInputConnection = super.onCreateInputConnection(outAttrs) + if (localInputConnection == null) { + // in case super returns null, let's just observe the base implementation, no need to make + // an InputConnectionWrapper of a null target + return localInputConnection + } + // if non null, wrap the new InputConnection around our wrapper (used for logging purposes only) + //inputConnection = AztecTextInputConnectionWrapper(localInputConnection, this) + inputConnectionRef = WeakReference(localInputConnection) + } + + // return the existing inputConnection + return inputConnectionRef?.get()!! + } + override fun onRestoreInstanceState(state: Parcelable?) { disableTextChangedListener() diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/ime/EditorInfoUtils.kt b/aztec/src/main/kotlin/org/wordpress/aztec/ime/EditorInfoUtils.kt new file mode 100644 index 000000000..abb591fe0 --- /dev/null +++ b/aztec/src/main/kotlin/org/wordpress/aztec/ime/EditorInfoUtils.kt @@ -0,0 +1,55 @@ +package org.wordpress.aztec.ime + +import android.os.Build +import android.view.inputmethod.EditorInfo +import java.util.Arrays + +object EditorInfoUtils { + @JvmStatic + fun areEditorInfosTheSame(ed1: EditorInfo, ed2: EditorInfo): Boolean { + if (ed1 == ed2) { + return true + } + + if (ed1.actionId == ed2.actionId + && (ed1.actionLabel != null && ed1.actionLabel.equals(ed2.actionLabel) || ed1.actionLabel == null && ed2.actionLabel == null) + && ed1.inputType == ed2.inputType + && ed1.imeOptions == ed2.imeOptions + && (ed1.privateImeOptions != null && ed1.privateImeOptions.equals(ed2.privateImeOptions) || ed1.privateImeOptions == null && ed2.privateImeOptions == null) + && ed1.initialSelStart == ed2.initialSelStart + && ed1.initialSelEnd == ed2.initialSelEnd + && ed1.initialCapsMode == ed2.initialCapsMode + && ed1.fieldId == ed2.fieldId + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + // specific comparisons here + if (ed1.contentMimeTypes != null && ed2.contentMimeTypes != null) { + return Arrays.equals(ed1.contentMimeTypes, ed2.contentMimeTypes) + } + } + return true + } + return false + } + + @JvmStatic + fun copyEditorInfo(ed1: EditorInfo) : EditorInfo { + val copy = EditorInfo() + copy.actionId = ed1.actionId + copy.actionLabel = ed1.actionLabel?.toString() + copy.inputType = ed1.inputType + copy.imeOptions = ed1.imeOptions + copy.privateImeOptions = ed1.privateImeOptions?.toString() + copy.initialSelStart = ed1.initialSelStart + copy.initialSelEnd = ed1.initialSelEnd + copy.initialCapsMode = ed1.initialCapsMode + copy.fieldId = ed1.fieldId + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + // specific comparisons here + if (ed1.contentMimeTypes != null) { + copy.contentMimeTypes = Arrays.copyOf(ed1.contentMimeTypes, ed1.contentMimeTypes.size) + } + } + return copy + } +} \ No newline at end of file diff --git a/aztec/src/main/kotlin/org/wordpress/aztec/plugins/CssUnderlinePlugin.kt b/aztec/src/main/kotlin/org/wordpress/aztec/plugins/CssUnderlinePlugin.kt index c0b35efa0..426dd93c7 100644 --- a/aztec/src/main/kotlin/org/wordpress/aztec/plugins/CssUnderlinePlugin.kt +++ b/aztec/src/main/kotlin/org/wordpress/aztec/plugins/CssUnderlinePlugin.kt @@ -41,7 +41,9 @@ class CssUnderlinePlugin : ISpanPostprocessor, ISpanPreprocessor { if (hiddenSpan.TAG == SPAN_TAG) { val parentStyle = hiddenSpan.attributes.getValue(CssStyleFormatter.STYLE_ATTRIBUTE) val childStyle = calypsoUnderlineSpan.attributes.getValue(CssStyleFormatter.STYLE_ATTRIBUTE) - hiddenSpan.attributes.setValue(CssStyleFormatter.STYLE_ATTRIBUTE, CssStyleFormatter.mergeStyleAttributes(parentStyle, childStyle)) + if (parentStyle != null && childStyle != null) { + hiddenSpan.attributes.setValue(CssStyleFormatter.STYLE_ATTRIBUTE, CssStyleFormatter.mergeStyleAttributes(parentStyle, childStyle)) + } // remove the extra child span spannable.removeSpan(calypsoUnderlineSpan)