package net.mezimmah.wkt9 import android.annotation.SuppressLint import android.content.Intent import android.inputmethodservice.InputMethodService import android.provider.Settings import android.util.Log import android.view.KeyEvent import android.view.View import android.view.inputmethod.CursorAnchorInfo import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodSubtype import net.mezimmah.wkt9.entity.Word import net.mezimmah.wkt9.inputhandler.IdleInputHandler import net.mezimmah.wkt9.inputhandler.InputHandler import net.mezimmah.wkt9.inputhandler.LetterInputHandler import net.mezimmah.wkt9.inputhandler.NumberInputHandler import net.mezimmah.wkt9.inputhandler.WordInputHandler import net.mezimmah.wkt9.inputmode.InputModeManager import net.mezimmah.wkt9.inputmode.InputMode import net.mezimmah.wkt9.keypad.Event import net.mezimmah.wkt9.keypad.Key import net.mezimmah.wkt9.keypad.KeyEventStat import net.mezimmah.wkt9.layout.Words import net.mezimmah.wkt9.t9.T9 import java.util.Locale class WKT9IME: IME, InputMethodService() { private val tag = "WKT9" private val inputModeManager = InputModeManager(this) private lateinit var locale: Locale private var inputHandler: InputHandler? = null private var wordsView: Words? = null private val keyDownStats = KeyEventStat(0, 0) private val keyUpStats = KeyEventStat(0, 0) private var composing: Boolean = false private var selectionStart: Int = 0 private var selectionEnd: Int = 0 override fun onCandidates(candidates: ArrayList, current: Int?) { // this.candidates?.load(candidates, current) } override fun onCommit(text: CharSequence, beforeCursor: Int, afterCursor: Int) { currentInputConnection?.run { beginBatchEdit() setComposingRegion(selectionEnd - beforeCursor, selectionEnd + afterCursor) setComposingText(text, 1) finishComposingText() endBatchEdit() } } override fun onCompose(text: CharSequence) { currentInputConnection?.run { if (!composing) setComposingRegion(selectionStart, selectionEnd) setComposingText(text, 1) } } @SuppressLint("InflateParams") override fun onCreate() { Log.d(tag, "Starting WKT9") val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager val languageTag = inputMethodManager.currentInputMethodSubtype?.languageTag ?: "en-US" locale = Locale.forLanguageTag(languageTag) initializeDictionary(locale) super.onCreate() } @SuppressLint("InflateParams") override fun onCreateInputView(): View? { wordsView = layoutInflater.inflate(R.layout.words, null) as Words return wordsView } override fun onCurrentInputMethodSubtypeChanged(newSubtype: InputMethodSubtype?) { super.onCurrentInputMethodSubtypeChanged(newSubtype) newSubtype?.let { locale = Locale.forLanguageTag(it.languageTag) initializeDictionary(locale) inputHandler?.onSwitchLocale(locale) } } override fun onDeleteText(beforeCursor: Int, afterCursor: Int, finishComposing: Boolean) { deleteText(beforeCursor, afterCursor) } override fun onGetTextBeforeCursor(n: Int): CharSequence? { return this.currentInputConnection?.getTextBeforeCursor(n, 0) } override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { if (keyDownStats.keyCode != keyCode) { keyDownStats.keyCode = keyCode keyDownStats.repeats = 0 } else keyDownStats.repeats++ val key = Key.fromKeyCode(keyCode) if (event == null || key == null) return super.onKeyDown(keyCode, event) var consume = key.consume val hasLongDownMapping = key.mappings.hasLongDownMapping(InputMode.Word) val mappings = key.mappings.match( event = if (event.repeatCount > 0) Event.keyDownRepeat else Event.keyDown, inputMode = inputModeManager.currentMode, packageName = currentInputEditorInfo.packageName, fn = event.isFunctionPressed, ctrl = event.isCtrlPressed, repeatCount = event.repeatCount ) mappings?.map { mapping -> if (mapping.command != null) { inputHandler?.onRunCommand(mapping.command, key, event, keyDownStats) } if (mapping.overrideConsume) consume = mapping.consume } if (hasLongDownMapping && event.repeatCount == 0) event.startTracking() return when (consume) { true -> true false -> false else -> super.onKeyDown(keyCode, event) } } override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { if (keyUpStats.keyCode != keyCode) { keyUpStats.keyCode = keyCode keyUpStats.repeats = 0 } else keyUpStats.repeats++ val key = Key.fromKeyCode(keyCode) if (event == null || key == null) return super.onKeyUp(keyCode, event) var consume = key.consume val keyDownMS = event.eventTime - event.downTime val mappings = key.mappings.match( event = if (keyDownMS >= 400L) Event.afterLongDown else Event.afterShortDown, inputMode = inputModeManager.currentMode, packageName = currentInputEditorInfo.packageName, fn = event.isFunctionPressed, ctrl = event.isCtrlPressed, repeatCount = event.repeatCount ) mappings?.map { mapping -> if (mapping.command != null) { inputHandler?.onRunCommand(mapping.command, key, event, keyUpStats) } if (mapping.overrideConsume) consume = mapping.consume } return when (consume) { true -> true false -> false else -> super.onKeyUp(keyCode, event) } } override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean { val key = Key.fromKeyCode(keyCode) if (event == null || key == null) return super.onKeyLongPress(keyCode, event) var consume = key.consume val mappings = key.mappings.match( event = Event.keyLongDown, inputMode = inputModeManager.currentMode, packageName = currentInputEditorInfo.packageName, fn = event.isFunctionPressed, ctrl = event.isCtrlPressed ) mappings?.map { mapping -> if (mapping.command != null) { inputHandler?.onRunCommand(mapping.command, key, event, keyDownStats) } if (mapping.overrideConsume) consume = mapping.consume } return when (consume) { true -> true false -> false else -> super.onKeyLongPress(keyCode, event) } } override fun onShowInputRequested(flags: Int, configChange: Boolean): Boolean { return ( inputModeManager.currentMode != InputMode.Number && inputModeManager.currentMode != InputMode.Idle ) } override fun onStartInput(editorInfo: EditorInfo?, restarting: Boolean) { val mode = inputModeManager.selectModeByEditor(editorInfo) currentInputConnection?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR) switchInputMode(mode) } override fun onStartIntent(intent: Intent) { if (Settings.canDrawOverlays(this)) { startActivity(intent) } } override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) { cursorAnchorInfo?.let { selectionStart = it.selectionStart selectionEnd = it.selectionEnd composing = if (it.composingTextStart == -1) { if (composing) finishComposing() false } else if (it.selectionEnd != (it.composingTextStart + it.composingText.length)) { onCommit() true } else true } super.onUpdateCursorAnchorInfo(cursorAnchorInfo) } override fun onSwitchInputHandler(inputMode: InputMode) { val mode = inputModeManager.switchToMode(inputMode) switchInputMode(mode) } override fun onTriggerKeyEvent(event: KeyEvent) { currentInputConnection?.sendKeyEvent(event) } override fun onUpdateStatusIcon(icon: Int?) { if (icon == null) hideStatusIcon() else showStatusIcon(icon) } override fun onWords(words: List) { wordsView?.words = words } override fun onWordSelected(word: Word) { this.onCompose(word.word) this.inputHandler?.onWordSelected(word) } override fun onNextWord() { wordsView?.next() } override fun onPreviousWord() { wordsView?.previous() } private fun deleteText(beforeCursor: Int, afterCursor: Int) { currentInputConnection?.run { deleteSurroundingText(beforeCursor, afterCursor) } } private fun finishComposing() { wordsView?.clear() inputHandler?.onFinishComposing() } private fun initializeDictionary(locale: Locale) { val t9 = T9(this, locale) t9.initializeWords() } private fun switchInputMode(mode: InputMode) { inputHandler = when(mode) { InputMode.Word -> WordInputHandler(this, this, locale) InputMode.Letter -> LetterInputHandler(this, this, locale) InputMode.Number -> NumberInputHandler(this, this) else -> IdleInputHandler(this, this) } } }