Skip to content

Commit 48020a8

Browse files
committed
Switched to using InputConnectionNWrapper
1 parent 3d452ac commit 48020a8

File tree

2 files changed

+150
-93
lines changed

2 files changed

+150
-93
lines changed

aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt

Lines changed: 20 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ import android.os.Parcel
3535
import android.os.Parcelable
3636
import android.text.Editable
3737
import android.text.InputFilter
38-
import android.text.Selection
3938
import android.text.Spannable
4039
import android.text.SpannableStringBuilder
4140
import android.text.Spanned
@@ -51,8 +50,8 @@ import android.view.View
5150
import android.view.View.OnLongClickListener
5251
import android.view.WindowManager
5352
import android.view.inputmethod.BaseInputConnection
54-
import android.view.inputmethod.CompletionInfo
55-
import android.view.inputmethod.CorrectionInfo
53+
import android.view.inputmethod.EditorInfo
54+
import android.view.inputmethod.InputConnection
5655
import android.widget.CheckBox
5756
import android.widget.EditText
5857
import android.widget.Toast
@@ -220,30 +219,6 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
220219
}
221220
}
222221

223-
override fun onBeginBatchEdit() {
224-
super.onBeginBatchEdit()
225-
}
226-
227-
override fun onEditorAction(actionCode: Int) {
228-
super.onEditorAction(actionCode)
229-
}
230-
231-
override fun onPrivateIMECommand(action: String?, data: Bundle?): Boolean {
232-
return super.onPrivateIMECommand(action, data)
233-
}
234-
235-
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
236-
return super.onKeyDown(keyCode, event)
237-
}
238-
239-
override fun onCommitCorrection(info: CorrectionInfo?) {
240-
super.onCommitCorrection(info)
241-
}
242-
243-
override fun onCommitCompletion(text: CompletionInfo?) {
244-
super.onCommitCompletion(text)
245-
}
246-
247222
private val REGEXP_EMAIL = Regex("^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$",
248223
setOf(RegexOption.DOT_MATCHES_ALL, RegexOption.IGNORE_CASE))
249224
private val REGEXP_STANDALONE_URL = Regex("^(?:[a-z]+:|#|\\?|\\.|/)", RegexOption.DOT_MATCHES_ALL)
@@ -682,6 +657,15 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
682657
return super.onTouchEvent(event)
683658
}
684659

660+
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
661+
val baseInputConnection = requireNotNull(super.onCreateInputConnection(outAttrs))
662+
return if (Build.MANUFACTURER == "samsung" && Build.VERSION.SDK_INT == 33 && overrideSamsungPredictiveBehavior) {
663+
SamsungInputConnection(this, baseInputConnection)
664+
} else {
665+
baseInputConnection
666+
}
667+
}
668+
685669
// Setup the keyListener(s) for Backspace and Enter key.
686670
// Backspace: If listener does return false we remove the style here
687671
// Enter: Ask the listener if we need to insert or not the char
@@ -691,58 +675,6 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
691675
handleBackspaceAndEnter(event)
692676
}
693677

694-
// This InputFilter is creating for fixing the issue with predictive text on Samsung devices with API 33
695-
// (at least this time) https://github.com/wordpress-mobile/AztecEditor-Android/issues/1023
696-
// We are detecting when content of the editor is replaced with identical content, by comparing string values
697-
// and ranges of incoming and exiting content. When this happens, instead of using incoming source content
698-
// we use original content, with SuggestionSpan from incoming content added to it.
699-
val samsungContentReplacementPreventer = InputFilter { source, start, end, dest, dstart, dend ->
700-
var temp: CharSequence? = null
701-
if (overrideSamsungPredictiveBehavior) {
702-
val equalStringValues = source.toString() == dest.toString()
703-
val equalRange = start == 0 && dstart == 0 && end == source.length && dend == source.length
704-
705-
if (equalStringValues && equalRange) {
706-
overrideSamsungPredictiveBehavior = false
707-
// we can't just return a dest, so we need to copy it into a new spannable string
708-
// this will also strip all the internal "service" spans
709-
temp = SpannableStringBuilder(dest)
710-
// TextUtils.copySpansFrom(dest, 0, dest.length, Any::class.java, temp, 0)
711-
// copy all the suggestion spans from the source, so we can see underlines
712-
disableCrashPreventerInputFilter()
713-
disableTextChangedListener()
714-
disableMediaDeletedListener()
715-
716-
if (source is Spanned) {
717-
TextUtils.copySpansFrom(source, 0, dest.length, SuggestionSpan::class.java, temp, 0)
718-
}
719-
720-
val selStart = Selection.getSelectionStart(text)
721-
val selEnd = Selection.getSelectionEnd(text)
722-
val len = text.length
723-
724-
text.clearSpans()
725-
setTextKeepState(temp)
726-
727-
if (selStart >= 0 || selEnd >= 0) {
728-
Selection.setSelection(text,
729-
Math.max(0, Math.min(selStart, len)),
730-
Math.max(0, Math.min(selEnd, len)))
731-
}
732-
733-
734-
contentChangeWatcher.notifyContentChanged()
735-
736-
temp = null
737-
enableTextChangedListener()
738-
enableMediaDeletedListener()
739-
enableCrashPreventerInputFilter()
740-
overrideSamsungPredictiveBehavior = true
741-
}
742-
}
743-
temp
744-
}
745-
746678
// This InputFilter created only for the purpose of avoiding crash described here:
747679
// https://android-review.googlesource.com/c/platform/frameworks/base/+/634929
748680
// https://github.com/wordpress-mobile/AztecEditor-Android/issues/729
@@ -803,16 +735,12 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
803735
source
804736
}
805737

806-
filters =
807-
if (Build.MANUFACTURER == "samsung" && Build.VERSION.SDK_INT == 33) {
808-
arrayOf(samsungContentReplacementPreventer, emptyEditTextBackspaceDetector)
809-
} else
810-
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O || Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1) {
811-
// dynamicLayoutCrashPreventer needs to be first in array as these are going to be chained when processed
812-
arrayOf(dynamicLayoutCrashPreventer, emptyEditTextBackspaceDetector)
813-
} else {
814-
arrayOf(emptyEditTextBackspaceDetector)
815-
}
738+
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O || Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1) {
739+
// dynamicLayoutCrashPreventer needs to be first in array as these are going to be chained when processed
740+
filters = arrayOf(dynamicLayoutCrashPreventer, emptyEditTextBackspaceDetector)
741+
} else {
742+
filters = arrayOf(emptyEditTextBackspaceDetector)
743+
}
816744
}
817745

818746
private fun isCleanStringEmpty(text: CharSequence): Boolean {
@@ -1797,14 +1725,13 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
17971725
bypassMediaDeletedListener = false
17981726
}
17991727

1728+
// removes Grammarly suggestions from default keyboard on Samsung devices on Android 13 (API 33)
1729+
// Grammarly implementation is often messing spans and cursor position, as described here:
1730+
// https://github.com/wordpress-mobile/AztecEditor-Android/issues/1023
18001731
fun enableSamsungPredictiveBehaviorOverride() {
18011732
overrideSamsungPredictiveBehavior = true
18021733
}
18031734

1804-
fun disableSamsungPredictiveBehaviorOverride() {
1805-
overrideSamsungPredictiveBehavior = false
1806-
}
1807-
18081735
fun isMediaDeletedListenerDisabled(): Boolean {
18091736
return bypassMediaDeletedListener
18101737
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package org.wordpress.aztec
2+
3+
import android.os.Build
4+
import android.widget.TextView
5+
import android.view.inputmethod.BaseInputConnection
6+
import android.text.Editable
7+
import android.view.inputmethod.CompletionInfo
8+
import android.view.inputmethod.CorrectionInfo
9+
import android.view.inputmethod.ExtractedTextRequest
10+
import android.view.inputmethod.ExtractedText
11+
import android.view.inputmethod.InputConnection
12+
import android.os.Bundle
13+
import android.view.KeyEvent
14+
import android.view.inputmethod.InputContentInfo
15+
16+
/**
17+
* Wrapper around proprietary Samsung InputConnection. Forwards all the calls to it, except for getExtractedText
18+
*/
19+
class SamsungInputConnection(
20+
private val mTextView: TextView,
21+
private val baseInputConnection: InputConnection,
22+
) : BaseInputConnection(mTextView, true) {
23+
24+
override fun getEditable(): Editable {
25+
return mTextView.editableText
26+
}
27+
28+
override fun beginBatchEdit(): Boolean {
29+
return baseInputConnection.beginBatchEdit()
30+
}
31+
32+
override fun endBatchEdit(): Boolean {
33+
return baseInputConnection.endBatchEdit()
34+
}
35+
36+
override fun clearMetaKeyStates(states: Int): Boolean {
37+
return baseInputConnection.clearMetaKeyStates(states)
38+
}
39+
40+
override fun sendKeyEvent(event: KeyEvent?): Boolean {
41+
return super.sendKeyEvent(event)
42+
}
43+
44+
override fun commitCompletion(text: CompletionInfo?): Boolean {
45+
return baseInputConnection.commitCompletion(text)
46+
}
47+
48+
override fun commitCorrection(correctionInfo: CorrectionInfo?): Boolean {
49+
return baseInputConnection.commitCorrection(correctionInfo)
50+
}
51+
52+
override fun performEditorAction(actionCode: Int): Boolean {
53+
return baseInputConnection.performEditorAction(actionCode)
54+
}
55+
56+
override fun performContextMenuAction(id: Int): Boolean {
57+
return baseInputConnection.performContextMenuAction(id)
58+
}
59+
60+
// Extracted text on Samsung devices on Android 13 is somehow used for Grammarly suggestions which causes a lot of
61+
// issues with spans and cursors. We do not use extracted text, so returning null
62+
// (default behavior of BaseInputConnection) fixes the problem
63+
override fun getExtractedText(request: ExtractedTextRequest?, flags: Int): ExtractedText? {
64+
return null
65+
}
66+
67+
override fun performPrivateCommand(action: String?, data: Bundle?): Boolean {
68+
return baseInputConnection.performPrivateCommand(action, data)
69+
}
70+
71+
override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean {
72+
return baseInputConnection.setComposingText(text, newCursorPosition)
73+
}
74+
75+
override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
76+
return baseInputConnection.commitText(text, newCursorPosition)
77+
}
78+
79+
override fun commitContent(inputContentInfo: InputContentInfo, flags: Int, opts: Bundle?): Boolean {
80+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
81+
baseInputConnection.commitContent(inputContentInfo, flags, opts)
82+
} else {
83+
super.commitContent(inputContentInfo, flags, opts)
84+
}
85+
}
86+
87+
override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
88+
return baseInputConnection.deleteSurroundingText(beforeLength, afterLength)
89+
}
90+
91+
override fun requestCursorUpdates(cursorUpdateMode: Int): Boolean {
92+
return baseInputConnection.requestCursorUpdates(cursorUpdateMode)
93+
}
94+
95+
override fun reportFullscreenMode(enabled: Boolean): Boolean {
96+
return baseInputConnection.reportFullscreenMode(enabled)
97+
}
98+
99+
override fun setSelection(start: Int, end: Int): Boolean {
100+
return baseInputConnection.setSelection(start, end)
101+
}
102+
103+
override fun finishComposingText(): Boolean {
104+
return baseInputConnection.finishComposingText()
105+
}
106+
107+
override fun setComposingRegion(start: Int, end: Int): Boolean {
108+
return baseInputConnection.setComposingRegion(start, end)
109+
}
110+
111+
override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean {
112+
return baseInputConnection.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
113+
}
114+
115+
override fun getCursorCapsMode(reqModes: Int): Int {
116+
return baseInputConnection.getCursorCapsMode(reqModes)
117+
}
118+
119+
override fun getSelectedText(flags: Int): CharSequence? {
120+
return baseInputConnection.getSelectedText(flags)
121+
}
122+
123+
override fun getTextAfterCursor(length: Int, flags: Int): CharSequence {
124+
return baseInputConnection.getTextAfterCursor(length, flags)
125+
}
126+
127+
override fun getTextBeforeCursor(length: Int, flags: Int): CharSequence {
128+
return baseInputConnection.getTextBeforeCursor(length, flags)
129+
}
130+
}

0 commit comments

Comments
 (0)