0
0
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:
Patrick Goldinger 2022-06-05 19:50:51 +02:00 committed by GitHub
commit 3d6cacc753
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 78 additions and 58 deletions

View File

@ -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() {

View File

@ -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 {

View File

@ -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()

View File

@ -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
}
}

View File

@ -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()) {

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
}