mirror of
https://github.com/florisboard/florisboard.git
synced 2024-09-20 03:52:18 +02:00
Merge pull request #1894 from florisboard/finetune-performance-and-decrease-cpu-usage
Improve input feedback controller performance
This commit is contained in:
commit
3d6cacc753
@ -325,7 +325,6 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
activeState.imeUiMode = ImeUiMode.TEXT
|
||||
activeState.isSelectionMode = editorInfo.initialSelection.isSelectionMode
|
||||
editorInstance.handleStartInputView(editorInfo)
|
||||
keyboardManager.updateCapsState()
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,7 +345,6 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
newSelection = EditorRange.normalized(newSelStart, newSelEnd),
|
||||
composing = EditorRange.normalized(candidatesStart, candidatesEnd),
|
||||
)
|
||||
keyboardManager.updateCapsState()
|
||||
}
|
||||
}
|
||||
|
||||
@ -373,6 +371,7 @@ class FlorisImeService : LifecycleInputMethodService() {
|
||||
}
|
||||
isWindowShown = true
|
||||
themeManager.updateActiveTheme()
|
||||
inputFeedbackController.updateSystemPrefsState()
|
||||
}
|
||||
|
||||
override fun onWindowHidden() {
|
||||
|
@ -20,6 +20,7 @@ import android.content.Context
|
||||
import android.icu.text.BreakIterator
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.os.SystemClock
|
||||
import android.text.TextUtils
|
||||
import android.view.InputDevice
|
||||
import android.view.KeyCharacterMap
|
||||
import android.view.KeyEvent
|
||||
@ -27,6 +28,7 @@ import android.view.inputmethod.InputConnection
|
||||
import dev.patrickgold.florisboard.FlorisImeService
|
||||
import dev.patrickgold.florisboard.ime.nlp.BreakIteratorGroup
|
||||
import dev.patrickgold.florisboard.ime.text.composing.Composer
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
import dev.patrickgold.florisboard.lib.kotlin.guardedByLock
|
||||
import dev.patrickgold.florisboard.subtypeManager
|
||||
@ -49,6 +51,7 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
private const val CursorUpdateNone: Int = 0
|
||||
}
|
||||
|
||||
private val keyboardManager by context.keyboardManager()
|
||||
private val subtypeManager by context.subtypeManager()
|
||||
private val scope = MainScope()
|
||||
protected val breakIterators = BreakIteratorGroup()
|
||||
@ -98,11 +101,9 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
if (ic == null || selection.isNotValid || editorInfo.isRawInputEditor) {
|
||||
activeCursorCapsMode = InputAttributes.CapsMode.NONE
|
||||
activeContent = EditorContent.Unspecified
|
||||
keyboardManager.reevaluateInputShiftState()
|
||||
return
|
||||
}
|
||||
// We query the input connection instead of using the initialCapsMode because some apps just don't want to use
|
||||
// EditorInfo's initial fields correctly.
|
||||
activeCursorCapsMode = ic.getCursorCapsMode()
|
||||
|
||||
// Get Text
|
||||
val textBeforeSelection = editorInfo.getInitialTextBeforeCursor(NumCharsBeforeCursor)
|
||||
@ -113,15 +114,17 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
?: ic.getSelectedText(0) ?: ""
|
||||
|
||||
scope.launch {
|
||||
activeContent = generateContent(
|
||||
val content = generateContent(
|
||||
editorInfo,
|
||||
selection,
|
||||
textBeforeSelection,
|
||||
textAfterSelection,
|
||||
selectedText,
|
||||
).also { content ->
|
||||
ic.setComposingRegion(content.composing)
|
||||
}
|
||||
)
|
||||
activeCursorCapsMode = content.cursorCapsMode()
|
||||
activeContent = content
|
||||
keyboardManager.reevaluateInputShiftState()
|
||||
ic.setComposingRegion(content.composing)
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,9 +142,9 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
if (ic == null || newSelection.isNotValid || editorInfo.isRawInputEditor) {
|
||||
activeCursorCapsMode = InputAttributes.CapsMode.NONE
|
||||
activeContent = EditorContent.Unspecified
|
||||
keyboardManager.reevaluateInputShiftState()
|
||||
return
|
||||
}
|
||||
activeCursorCapsMode = ic.getCursorCapsMode()
|
||||
|
||||
val expected = runBlocking {
|
||||
expectedContentQueue.popUntilOrNull {
|
||||
@ -150,7 +153,9 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
}
|
||||
}
|
||||
if (expected != null) {
|
||||
activeCursorCapsMode = expected.cursorCapsMode()
|
||||
activeContent = expected
|
||||
keyboardManager.reevaluateInputShiftState()
|
||||
return
|
||||
}
|
||||
|
||||
@ -161,16 +166,18 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
val selectedText = if (newSelection.isSelectionMode) ic.getSelectedText(0) ?: "" else ""
|
||||
|
||||
scope.launch {
|
||||
activeContent = generateContent(
|
||||
val content = generateContent(
|
||||
editorInfo,
|
||||
newSelection,
|
||||
textBeforeSelection,
|
||||
textAfterSelection,
|
||||
selectedText,
|
||||
).also { content ->
|
||||
if (content.composing != composing) {
|
||||
ic.setComposingRegion(content.composing)
|
||||
}
|
||||
)
|
||||
activeCursorCapsMode = content.cursorCapsMode()
|
||||
activeContent = content
|
||||
keyboardManager.reevaluateInputShiftState()
|
||||
if (content.composing != composing) {
|
||||
ic.setComposingRegion(content.composing)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -191,12 +198,6 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
runBlocking { expectedContentQueue.clear() }
|
||||
}
|
||||
|
||||
private fun InputConnection.getCursorCapsMode(): InputAttributes.CapsMode {
|
||||
return InputAttributes.CapsMode.fromFlags(
|
||||
this.getCursorCapsMode(activeInfo.inputAttributes.raw)
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun generateContent(
|
||||
editorInfo: FlorisEditorInfo,
|
||||
selection: EditorRange,
|
||||
@ -243,6 +244,17 @@ abstract class AbstractEditorInstance(context: Context) {
|
||||
return generateContent(editorInfo, selection, textBeforeSelection, textAfterSelection, selectedText)
|
||||
}
|
||||
|
||||
private fun EditorContent.cursorCapsMode(): InputAttributes.CapsMode {
|
||||
return when {
|
||||
localSelection.isNotValid -> InputAttributes.CapsMode.NONE
|
||||
else -> {
|
||||
InputAttributes.CapsMode.fromFlags(
|
||||
TextUtils.getCapsMode(text, localSelection.start, activeInfo.inputAttributes.raw)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun determineComposer(composerName: ExtensionComponentName): Composer
|
||||
|
||||
protected open fun shouldDetermineComposingRegion(editorInfo: FlorisEditorInfo): Boolean {
|
||||
|
@ -23,7 +23,6 @@ import android.content.Intent
|
||||
import android.view.KeyEvent
|
||||
import androidx.core.view.inputmethod.InputConnectionCompat
|
||||
import androidx.core.view.inputmethod.InputContentInfoCompat
|
||||
import dev.patrickgold.florisboard.BuildConfig
|
||||
import dev.patrickgold.florisboard.FlorisImeService
|
||||
import dev.patrickgold.florisboard.app.florisPreferenceModel
|
||||
import dev.patrickgold.florisboard.appContext
|
||||
@ -39,11 +38,8 @@ import dev.patrickgold.florisboard.ime.text.key.KeyVariation
|
||||
import dev.patrickgold.florisboard.keyboardManager
|
||||
import dev.patrickgold.florisboard.lib.android.AndroidVersion
|
||||
import dev.patrickgold.florisboard.lib.android.showShortToast
|
||||
import dev.patrickgold.florisboard.lib.devtools.flogDebug
|
||||
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
|
||||
import dev.patrickgold.florisboard.nlpManager
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
@ -57,7 +53,6 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
|
||||
private val clipboardManager by context.clipboardManager()
|
||||
private val keyboardManager by context.keyboardManager()
|
||||
private val nlpManager by context.nlpManager()
|
||||
private val scope = MainScope()
|
||||
|
||||
private val activeState get() = keyboardManager.activeState
|
||||
val phantomSpace = PhantomSpaceState()
|
||||
@ -65,16 +60,6 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
|
||||
|
||||
private fun currentInputConnection() = FlorisImeService.currentInputConnection()
|
||||
|
||||
init {
|
||||
if (BuildConfig.DEBUG) {
|
||||
scope.launch {
|
||||
activeContentFlow.collect { editorContent ->
|
||||
flogDebug { editorContent.toString() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleStartInputView(editorInfo: FlorisEditorInfo) {
|
||||
phantomSpace.setInactive()
|
||||
massSelection.reset()
|
||||
|
@ -137,15 +137,15 @@ value class InputAttributes private constructor(val raw: Int) {
|
||||
enum class CapsMode(private val value: Int) {
|
||||
NONE(0),
|
||||
ALL(1),
|
||||
SENTENCES(2),
|
||||
WORDS(3);
|
||||
WORDS(2),
|
||||
SENTENCES(3);
|
||||
|
||||
companion object {
|
||||
fun fromFlags(flags: Int): CapsMode {
|
||||
return when {
|
||||
flags and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS != 0 -> ALL
|
||||
flags and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES != 0 -> SENTENCES
|
||||
flags and InputType.TYPE_TEXT_FLAG_CAP_WORDS != 0 -> WORDS
|
||||
flags and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES != 0 -> SENTENCES
|
||||
else -> NONE
|
||||
}
|
||||
}
|
||||
@ -156,8 +156,8 @@ value class InputAttributes private constructor(val raw: Int) {
|
||||
fun toFlags(): Int {
|
||||
return when (this) {
|
||||
ALL -> InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
||||
SENTENCES -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
WORDS -> InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
||||
SENTENCES -> InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ class InputFeedbackController private constructor(private val ims: InputMethodSe
|
||||
this.systemServiceOrNull(VibratorManager::class)?.defaultVibrator
|
||||
} else {
|
||||
this.systemServiceOrNull(Vibrator::class)
|
||||
}
|
||||
}?.takeIf { it.hasVibrator() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,6 +92,14 @@ class InputFeedbackController private constructor(private val ims: InputMethodSe
|
||||
private val contentResolver = ims.contentResolver
|
||||
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
private var systemAudioEnabled: Boolean = false
|
||||
private var systemHapticEnabled: Boolean = false
|
||||
|
||||
fun updateSystemPrefsState() {
|
||||
systemAudioEnabled = systemPref(Settings.System.SOUND_EFFECTS_ENABLED)
|
||||
systemHapticEnabled = systemPref(Settings.System.HAPTIC_FEEDBACK_ENABLED)
|
||||
}
|
||||
|
||||
fun keyPress(data: KeyData = TextKeyData.UNSPECIFIED) {
|
||||
if (prefs.inputFeedback.audioFeatKeyPress.get()) performAudioFeedback(data, 1.0)
|
||||
if (prefs.inputFeedback.hapticFeatKeyPress.get()) performHapticFeedback(data, 1.0)
|
||||
@ -125,10 +133,7 @@ class InputFeedbackController private constructor(private val ims: InputMethodSe
|
||||
private fun performAudioFeedback(data: KeyData, factor: Double) {
|
||||
if (audioManager == null) return
|
||||
if (!prefs.inputFeedback.audioEnabled.get()) return
|
||||
|
||||
if (!prefs.inputFeedback.audioIgnoreSystemSettings.get()) {
|
||||
if (!systemPref(Settings.System.SOUND_EFFECTS_ENABLED)) return
|
||||
}
|
||||
if (!prefs.inputFeedback.audioIgnoreSystemSettings.get() && !systemAudioEnabled) return
|
||||
|
||||
scope.launch {
|
||||
val volume = (prefs.inputFeedback.audioVolume.get() * factor) / 100.0
|
||||
@ -146,12 +151,9 @@ class InputFeedbackController private constructor(private val ims: InputMethodSe
|
||||
}
|
||||
|
||||
private fun performHapticFeedback(data: KeyData, factor: Double) {
|
||||
if (vibrator == null || !vibrator.hasVibrator()) return
|
||||
if (vibrator == null) return
|
||||
if (!prefs.inputFeedback.hapticEnabled.get()) return
|
||||
|
||||
if (!prefs.inputFeedback.hapticIgnoreSystemSettings.get()) {
|
||||
if (!systemPref(Settings.System.HAPTIC_FEEDBACK_ENABLED)) return
|
||||
}
|
||||
if (!prefs.inputFeedback.hapticIgnoreSystemSettings.get() && !systemHapticEnabled) return
|
||||
|
||||
scope.launch {
|
||||
if (!prefs.inputFeedback.hapticUseVibrator.get()) {
|
||||
|
@ -25,6 +25,7 @@ import dev.patrickgold.florisboard.ime.editor.ImeOptions
|
||||
import dev.patrickgold.florisboard.ime.input.InputShiftState
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyCode
|
||||
import dev.patrickgold.florisboard.ime.text.key.KeyType
|
||||
import dev.patrickgold.florisboard.lib.FlorisLocale
|
||||
|
||||
interface ComputingEvaluator {
|
||||
fun activeEditorInfo(): FlorisEditorInfo
|
||||
@ -70,6 +71,27 @@ object DefaultComputingEvaluator : ComputingEvaluator {
|
||||
override fun slotData(data: KeyData): KeyData? = null
|
||||
}
|
||||
|
||||
private var cachedDisplayNameState = Triple(FlorisLocale.ROOT, DisplayLanguageNamesIn.SYSTEM_LOCALE, "")
|
||||
|
||||
/**
|
||||
* Compute language name with a cache to prevent repetitive calling of `locale.displayName()`, which invokes the
|
||||
* underlying `LocaleNative.getLanguageName()` method and in turn uses the rather slow ICU data table to look up the
|
||||
* language name. This only caches the last display name, but that's more than enough, as a one-time re-computation when
|
||||
* the subtype changes does not hurt, the repetitive computation for the same language hurts.
|
||||
*/
|
||||
private fun computeLanguageDisplayName(locale: FlorisLocale, displayLanguageNamesIn: DisplayLanguageNamesIn): String {
|
||||
val (cachedLocale, cachedDisplayLanguageNamesIn, cachedDisplayName) = cachedDisplayNameState
|
||||
if (cachedLocale == locale && cachedDisplayLanguageNamesIn == displayLanguageNamesIn) {
|
||||
return cachedDisplayName
|
||||
}
|
||||
val displayName = when (displayLanguageNamesIn) {
|
||||
DisplayLanguageNamesIn.SYSTEM_LOCALE -> locale.displayName()
|
||||
DisplayLanguageNamesIn.NATIVE_LOCALE -> locale.displayName(locale)
|
||||
}
|
||||
cachedDisplayNameState = Triple(locale, displayLanguageNamesIn, displayName)
|
||||
return displayName
|
||||
}
|
||||
|
||||
fun ComputingEvaluator.computeLabel(data: KeyData): String? {
|
||||
val evaluator = this
|
||||
return if (data.type == KeyType.CHARACTER && data.code != KeyCode.SPACE && data.code != KeyCode.CJK_SPACE
|
||||
@ -83,10 +105,7 @@ fun ComputingEvaluator.computeLabel(data: KeyData): String? {
|
||||
KeyCode.SPACE, KeyCode.CJK_SPACE -> {
|
||||
when (evaluator.keyboard().mode) {
|
||||
KeyboardMode.CHARACTERS -> evaluator.activeSubtype().primaryLocale.let { locale ->
|
||||
when (displayLanguageNamesIn()) {
|
||||
DisplayLanguageNamesIn.SYSTEM_LOCALE -> locale.displayName()
|
||||
DisplayLanguageNamesIn.NATIVE_LOCALE -> locale.displayName(locale)
|
||||
}
|
||||
computeLanguageDisplayName(locale, evaluator.displayLanguageNamesIn())
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
@ -147,8 +147,8 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
updateRenderInfo()
|
||||
}
|
||||
subtypeManager.activeSubtype.observeForever { newSubtype ->
|
||||
reevaluateInputShiftState()
|
||||
updateRenderInfo()
|
||||
updateCapsState()
|
||||
if (prefs.glide.enabled.get()) {
|
||||
glideTypingManager.setWordData(newSubtype)
|
||||
}
|
||||
@ -234,7 +234,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
|
||||
return activeState.observeAsNonNullState(neverEqualPolicy())
|
||||
}
|
||||
|
||||
fun updateCapsState() {
|
||||
fun reevaluateInputShiftState() {
|
||||
if (activeState.inputShiftState != InputShiftState.CAPS_LOCK && !inputEventDispatcher.isPressed(KeyCode.SHIFT)) {
|
||||
val shift = prefs.correction.autoCapitalization.get()
|
||||
&& subtypeManager.activeSubtype().primaryLocale.supportsCapitalization
|
||||
|
@ -18,6 +18,8 @@
|
||||
|
||||
package dev.patrickgold.florisboard.ime.keyboard
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.arch.core.executor.ArchTaskExecutor
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.lifecycle.LiveData
|
||||
import dev.patrickgold.florisboard.ime.ImeUiMode
|
||||
@ -117,11 +119,12 @@ class KeyboardState private constructor(initValue: ULong) : LiveData<KeyboardSta
|
||||
/**
|
||||
* Dispatches the new state to all observers if [batchEditCount] is [BATCH_ZERO] (= no active batch edits).
|
||||
*/
|
||||
@SuppressLint("RestrictedApi")
|
||||
private fun dispatchState() {
|
||||
if (batchEditCount.get() == BATCH_ZERO) {
|
||||
try {
|
||||
if (ArchTaskExecutor.getInstance().isMainThread) {
|
||||
super.setValue(this)
|
||||
} catch (e: Exception) {
|
||||
} else {
|
||||
super.postValue(this)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user