Skip to content

Commit

Permalink
Add back SamsungInputConnection
Browse files Browse the repository at this point in the history
  • Loading branch information
danilo04 committed Nov 13, 2023
1 parent c36ac5f commit 79d476f
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 3 deletions.
8 changes: 5 additions & 3 deletions aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt
Original file line number Diff line number Diff line change
Expand Up @@ -698,12 +698,14 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection {
val inputConnection = requireNotNull(super.onCreateInputConnection(outAttrs)).wrapWithBackSpaceHandler()

if (shouldOverridePredictiveTextBehavior()) {
return if (shouldOverridePredictiveTextBehavior()) {
AppLog.d(AppLog.T.EDITOR, "Disabling autocorrect on Samsung device with Samsung Keyboard with API 33")
outAttrs.inputType = outAttrs.inputType or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
}

return inputConnection
SamsungInputConnection(this, inputConnection)
} else {
inputConnection
}
}

private fun InputConnection.wrapWithBackSpaceHandler(): InputConnection {
Expand Down
187 changes: 187 additions & 0 deletions aztec/src/main/kotlin/org/wordpress/aztec/SamsungInputConnection.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package org.wordpress.aztec

import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.text.Selection
import android.text.Spanned
import android.text.style.SuggestionSpan
import android.view.KeyEvent
import android.view.inputmethod.BaseInputConnection
import android.view.inputmethod.CompletionInfo
import android.view.inputmethod.CorrectionInfo
import android.view.inputmethod.ExtractedText
import android.view.inputmethod.ExtractedTextRequest
import android.view.inputmethod.InputConnection
import android.view.inputmethod.InputContentInfo

/**
* Wrapper around proprietary Samsung InputConnection. Forwards all the calls to it, except for getExtractedText and
* some custom logic in commitText
*/
class SamsungInputConnection(
private val mTextView: AztecText,
private val baseInputConnection: InputConnection
) : BaseInputConnection(mTextView, true) {

override fun getEditable(): Editable {
return mTextView.editableText
}

override fun beginBatchEdit(): Boolean {
return baseInputConnection.beginBatchEdit()
}

override fun endBatchEdit(): Boolean {
return baseInputConnection.endBatchEdit()
}

override fun clearMetaKeyStates(states: Int): Boolean {
return baseInputConnection.clearMetaKeyStates(states)
}

override fun sendKeyEvent(event: KeyEvent?): Boolean {
return super.sendKeyEvent(event)
}

override fun commitCompletion(text: CompletionInfo?): Boolean {
return baseInputConnection.commitCompletion(text)
}

override fun commitCorrection(correctionInfo: CorrectionInfo?): Boolean {
return baseInputConnection.commitCorrection(correctionInfo)
}

override fun performEditorAction(actionCode: Int): Boolean {
return baseInputConnection.performEditorAction(actionCode)
}

override fun performContextMenuAction(id: Int): Boolean {
return baseInputConnection.performContextMenuAction(id)
}

// Extracted text on Samsung devices on Android 13 is somehow used for Grammarly suggestions which causes a lot of
// issues with spans and cursors. We do not use extracted text, so returning null
// (default behavior of BaseInputConnection) prevents Grammarly from messing up content most of the time
override fun getExtractedText(request: ExtractedTextRequest?, flags: Int): ExtractedText? {
return null
}

override fun performPrivateCommand(action: String?, data: Bundle?): Boolean {
return baseInputConnection.performPrivateCommand(action, data)
}

override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean {
return baseInputConnection.setComposingText(text, newCursorPosition)
}

override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
val incomingTextHasSuggestions = text is Spanned &&
text.getSpans(0, text.length, SuggestionSpan::class.java).isNotEmpty()

// Sometime spellchecker tries to commit partial text with suggestions. This mostly works ok,
// but Aztec spans are finicky, and tend to get messed when content of the editor is replaced.
// In this method we do everything replaceText method of EditableInputConnection does, apart from actually
// replacing text. Instead we copy the suggestions from incoming text into editor directly.
if (incomingTextHasSuggestions) {
// delete composing text set previously.
var composingSpanStart = getComposingSpanStart(editable)
var composingSpanEnd = getComposingSpanEnd(editable)

if (composingSpanEnd < composingSpanStart) {
val tmp = composingSpanStart
composingSpanStart = composingSpanEnd
composingSpanEnd = tmp
}

if (composingSpanStart != -1 && composingSpanEnd != -1) {
removeComposingSpans(editable)
} else {
composingSpanStart = Selection.getSelectionStart(editable)
composingSpanEnd = Selection.getSelectionEnd(editable)
if (composingSpanStart < 0) composingSpanStart = 0
if (composingSpanEnd < 0) composingSpanEnd = 0
if (composingSpanEnd < composingSpanStart) {
val tmp = composingSpanStart
composingSpanStart = composingSpanEnd
composingSpanEnd = tmp
}
}

var cursorPosition = newCursorPosition
cursorPosition += if (cursorPosition > 0) {
composingSpanEnd - 1
} else {
composingSpanStart
}
if (newCursorPosition < 0) cursorPosition = 0
if (newCursorPosition > editable.length) cursorPosition = editable.length
Selection.setSelection(editable, cursorPosition)

(text as Spanned).getSpans(0, text.length, SuggestionSpan::class.java).forEach {
val st: Int = text.getSpanStart(it)
val en: Int = text.getSpanEnd(it)
val fl: Int = text.getSpanFlags(it)

if (editable.length > composingSpanStart + en) {
editable.setSpan(it, composingSpanStart + st, composingSpanStart + en, fl)
}
}

return true
}
return baseInputConnection.commitText(text, newCursorPosition)
}

override fun commitContent(inputContentInfo: InputContentInfo, flags: Int, opts: Bundle?): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
baseInputConnection.commitContent(inputContentInfo, flags, opts)
} else {
super.commitContent(inputContentInfo, flags, opts)
}
}

override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
return baseInputConnection.deleteSurroundingText(beforeLength, afterLength)
}

override fun requestCursorUpdates(cursorUpdateMode: Int): Boolean {
return baseInputConnection.requestCursorUpdates(cursorUpdateMode)
}

override fun reportFullscreenMode(enabled: Boolean): Boolean {
return baseInputConnection.reportFullscreenMode(enabled)
}

override fun setSelection(start: Int, end: Int): Boolean {
return baseInputConnection.setSelection(start, end)
}

override fun finishComposingText(): Boolean {
return baseInputConnection.finishComposingText()
}

override fun setComposingRegion(start: Int, end: Int): Boolean {
return baseInputConnection.setComposingRegion(start, end)
}

override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean {
return baseInputConnection.deleteSurroundingTextInCodePoints(beforeLength, afterLength)
}

override fun getCursorCapsMode(reqModes: Int): Int {
return baseInputConnection.getCursorCapsMode(reqModes)
}

override fun getSelectedText(flags: Int): CharSequence? {
return baseInputConnection.getSelectedText(flags)
}

override fun getTextAfterCursor(length: Int, flags: Int): CharSequence? {
return baseInputConnection.getTextAfterCursor(length, flags)
}

override fun getTextBeforeCursor(length: Int, flags: Int): CharSequence? {
return baseInputConnection.getTextBeforeCursor(length, flags)
}
}

0 comments on commit 79d476f

Please sign in to comment.