-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
192 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
187 changes: 187 additions & 0 deletions
187
aztec/src/main/kotlin/org/wordpress/aztec/SamsungInputConnection.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |