Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1717,6 +1717,7 @@ open class UrikInputMethodService :
if (inputState.isTerminalField) viewModel.disableAutoCapForTerminalField()
inputState.currentInputAction = c.currentInputAction
inputState.isUrlOrEmailField = c.isUrlOrEmailField
inputState.isSuggestionsDisabled = c.isSuggestionsDisabled
}

override fun onFinishInput() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.urik.keyboard.service

import android.text.InputType
import android.view.inputmethod.EditorInfo
import com.urik.keyboard.model.KeyboardKey
import com.urik.keyboard.utils.ActionDetector
Expand All @@ -11,7 +12,8 @@ data class FieldClassification(
val isRawKeyEventField: Boolean,
val isTerminalField: Boolean,
val isUrlOrEmailField: Boolean,
val currentInputAction: KeyboardKey.ActionType
val currentInputAction: KeyboardKey.ActionType,
val isSuggestionsDisabled: Boolean = false
)

object InputFieldClassifier {
Expand All @@ -26,7 +28,29 @@ object InputFieldClassifier {
isUrlOrEmailField = variation == EditorInfo.TYPE_TEXT_VARIATION_URI ||
variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS ||
variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS,
currentInputAction = ActionDetector.detectAction(info)
currentInputAction = ActionDetector.detectAction(info),
isSuggestionsDisabled = detectSuggestionsDisabled(inputType)
)
}

private fun detectSuggestionsDisabled(inputType: Int): Boolean {
val inputClass = inputType and InputType.TYPE_MASK_CLASS

if (inputClass == InputType.TYPE_CLASS_PHONE) return true

if (inputClass == InputType.TYPE_CLASS_NUMBER) {
val variation = inputType and InputType.TYPE_MASK_VARIATION
return variation != InputType.TYPE_NUMBER_VARIATION_PASSWORD
}

if (inputClass == InputType.TYPE_CLASS_TEXT) {
val flags = inputType and InputType.TYPE_MASK_FLAGS
val variation = inputType and InputType.TYPE_MASK_VARIATION
if (flags and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS != 0) return true
if (flags and InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE != 0) return true
if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) return true
}

return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ class InputStateManager(
var isUrlOrEmailField: Boolean = false
internal set

@Volatile
var isSuggestionsDisabled: Boolean = false
internal set

@Volatile
var currentInputAction: com.urik.keyboard.model.KeyboardKey.ActionType =
com.urik.keyboard.model.KeyboardKey.ActionType.ENTER
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ class LetterInputHandler(
return
}

if (inputState.isSuggestionsDisabled) {
outputBridge.sendCharacter(char)
return
}

if (inputState.displayBuffer.isNotEmpty() && inputState.wordState.isFromSwipe) {
outputBridge.beginBatchEdit()
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ class NonLetterInputHandler(
outputBridge.sendCharacter(char)
return
}

if (inputState.isSuggestionsDisabled) {
outputBridge.sendCharacter(char)
return
}
serviceScope.launch {
try {
inputState.lastSpaceTime = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ class SpaceInputHandler(
return@launch
}

if (inputState.isSuggestionsDisabled) {
outputBridge.sendSpace()
return@launch
}

if (suggestionPipeline.isJapaneseLayout && inputState.displayBuffer.isNotEmpty()) {
onJapaneseSpaceNextCandidate()
return@launch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class SuggestionPipeline(
}

fun requestSuggestions(buffer: String, inputMethod: InputMethod) {
if (state.isSuggestionsDisabled) return
state.showDegradedIndicator(spellCheckManager.isDegradedMode)
if (isJapaneseLayout) {
requestJapaneseSuggestions(buffer)
Expand Down Expand Up @@ -87,6 +88,7 @@ class SuggestionPipeline(
}

fun coordinateStateTransition(newWordState: WordState) {
if (state.isSuggestionsDisabled) return
state.wordState = newWordState

if (newWordState.suggestions.isNotEmpty()) {
Expand Down Expand Up @@ -126,7 +128,11 @@ class SuggestionPipeline(
}

fun showBigramPredictions() {
if (state.requiresDirectCommit || !host.showSuggestions() || state.lastCommittedWord.isBlank()) {
if (state.requiresDirectCommit ||
state.isSuggestionsDisabled ||
!host.showSuggestions() ||
state.lastCommittedWord.isBlank()
) {
return
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,96 @@ class InputFieldClassifierTest {

assertTrue(result.isTerminalField)
}

@Test
fun `classify null info isSuggestionsDisabled false`() {
assertFalse(InputFieldClassifier.classify(null).isSuggestionsDisabled)
}

@Test
fun `classify TYPE_TEXT_FLAG_NO_SUGGESTIONS isSuggestionsDisabled true`() {
val info = createEditorInfo(inputFlags = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS)
assertTrue(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}

@Test
fun `classify TYPE_TEXT_FLAG_AUTO_COMPLETE isSuggestionsDisabled true`() {
val info = createEditorInfo(inputFlags = InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE)
assertTrue(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}

@Test
fun `classify TYPE_TEXT_VARIATION_FILTER isSuggestionsDisabled true`() {
val info = createEditorInfo(inputVariation = InputType.TYPE_TEXT_VARIATION_FILTER)
assertTrue(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}

@Test
fun `classify TYPE_CLASS_NUMBER non-password isSuggestionsDisabled true`() {
val info = createEditorInfo(
inputClass = InputType.TYPE_CLASS_NUMBER,
inputVariation = InputType.TYPE_NUMBER_VARIATION_NORMAL
)
assertTrue(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}

@Test
fun `classify TYPE_CLASS_NUMBER password isSuggestionsDisabled false`() {
val info = createEditorInfo(
inputClass = InputType.TYPE_CLASS_NUMBER,
inputVariation = InputType.TYPE_NUMBER_VARIATION_PASSWORD
)
assertFalse(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}

@Test
fun `classify TYPE_CLASS_PHONE isSuggestionsDisabled true`() {
val info = createEditorInfo(inputClass = InputType.TYPE_CLASS_PHONE, inputVariation = 0)
assertTrue(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}

@Test
fun `classify normal text field isSuggestionsDisabled false`() {
val info = createEditorInfo(
inputClass = InputType.TYPE_CLASS_TEXT,
inputVariation = InputType.TYPE_TEXT_VARIATION_NORMAL
)
assertFalse(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}

@Test
fun `classify password variation isSuggestionsDisabled false`() {
val info = createEditorInfo(
inputClass = InputType.TYPE_CLASS_TEXT,
inputVariation = InputType.TYPE_TEXT_VARIATION_PASSWORD
)
assertFalse(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}

@Test
fun `classify web password variation isSuggestionsDisabled false`() {
val info = createEditorInfo(
inputClass = InputType.TYPE_CLASS_TEXT,
inputVariation = InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD
)
assertFalse(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}

@Test
fun `classify email address variation isSuggestionsDisabled false`() {
val info = createEditorInfo(
inputClass = InputType.TYPE_CLASS_TEXT,
inputVariation = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
)
assertFalse(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}

@Test
fun `classify uri variation isSuggestionsDisabled false`() {
val info = createEditorInfo(
inputClass = InputType.TYPE_CLASS_TEXT,
inputVariation = InputType.TYPE_TEXT_VARIATION_URI
)
assertFalse(InputFieldClassifier.classify(info).isSuggestionsDisabled)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,4 +255,9 @@ class InputStateManagerTest {

assertFalse(stateManager.isKnownCursorTrustworthy())
}

@Test
fun `isSuggestionsDisabled initialises false`() {
assertFalse(stateManager.isSuggestionsDisabled)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,12 @@ class LetterInputHandlerTest {
handler.handle("a")
assertEquals("a", realInputState.displayBuffer)
}

@Test
fun `handle isSuggestionsDisabled sends character directly without composing`() {
realInputState.isSuggestionsDisabled = true
handler.handle("a")
verify(mockOutputBridge).sendCharacter("a")
assertEquals("", realInputState.displayBuffer)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,12 @@ class NonLetterInputHandlerTest {
testDispatcher.scheduler.advanceUntilIdle()
assert(getCurrentSettingsCalls.size >= 1)
}

@Test
fun `handle isSuggestionsDisabled sends character directly without suggestions`() {
realInputState.isSuggestionsDisabled = true
handler.handle(".")
verify(mockOutputBridge).sendCharacter(".")
verify(mockSuggestionPipeline, org.mockito.Mockito.never()).showBigramPredictions()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,23 @@ class SpaceInputHandlerTest {
testDispatcher.scheduler.advanceUntilIdle()
org.mockito.Mockito.verify(mockOutputBridge).commitText(" ", 1)
}

@Test
fun `handle isSuggestionsDisabled sends space directly`() {
realInputState.isSuggestionsDisabled = true
handler.handle()
testDispatcher.scheduler.advanceUntilIdle()
org.mockito.Mockito.verify(mockOutputBridge).sendSpace()
}

@Test
fun `handle isSuggestionsDisabled double space does not produce period`() {
realInputState.isSuggestionsDisabled = true
handler.handle()
testDispatcher.scheduler.advanceUntilIdle()
handler.handle()
testDispatcher.scheduler.advanceUntilIdle()
org.mockito.Mockito.verify(mockOutputBridge, org.mockito.Mockito.times(2)).sendSpace()
org.mockito.Mockito.verify(mockOutputBridge, org.mockito.Mockito.never()).commitText(". ", 1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -456,4 +456,40 @@ class SuggestionPipelineTest {
assertEquals(1, suggestions.count { it == "か" })
assert(suggestions.contains("カ")) { "katakana カ must be present" }
}

@Test
fun `requestSuggestions isSuggestionsDisabled emits no suggestions`() = runTest(testDispatcher) {
inputState.isSuggestionsDisabled = true
pipeline.requestSuggestions("hello", InputMethod.TYPED)
testDispatcher.scheduler.advanceUntilIdle()

verify(mockTextInputProcessor, never()).processWordInput(any(), any())
assertEquals(emptyList<String>(), capturedSuggestions)
}

@Test
fun `coordinateStateTransition isSuggestionsDisabled does not update suggestions`() {
inputState.isSuggestionsDisabled = true
val wordState = WordState(
buffer = "hello",
suggestions = listOf(SpellingSuggestion("hello", 0.9, 0, "dict", preserveCase = false))
)

pipeline.coordinateStateTransition(wordState)

assertEquals(emptyList<String>(), capturedSuggestions)
}

@Test
fun `showBigramPredictions isSuggestionsDisabled emits no predictions`() = runTest(testDispatcher) {
inputState.isSuggestionsDisabled = true
inputState.lastCommittedWord = "hello"
whenever(mockWordFrequencyRepository.getBigramPredictions(any(), any(), any()))
.thenReturn(setOf("world"))

pipeline.showBigramPredictions()
testDispatcher.scheduler.advanceUntilIdle()

assertEquals(emptyList<String>(), capturedSuggestions)
}
}