Skip to content
Open
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 @@ -19,8 +19,10 @@
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion;
import helium314.keyboard.latin.utils.SuggestionResults;

import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
Expand Down Expand Up @@ -151,4 +153,14 @@ void unlearnFromUserHistory(final String word,
void dumpDictionaryForDebug(final String dictName);

@NonNull List<DictionaryStats> getDictionaryStats(final Context context);

/**
* Returns all words with frequencies from the primary main dictionary, for gesture typing
* precomputation. Iterates the binary dictionary directly; can be slow on first call.
* The default returns an empty map; DictionaryFacilitatorImpl overrides this.
*/
@NonNull
default Map<String, Integer> getAllMainDictionaryWordsWithFrequency() {
return Collections.emptyMap();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,9 @@ class DictionaryFacilitatorImpl : DictionaryFacilitator {
putWordIntoValidSpellingWordCache("unlearnFromUserHistory", word.lowercase(Locale.getDefault()))
}

override fun getAllMainDictionaryWordsWithFrequency(): Map<String, Int> =
dictionaryGroups[0].getDict(Dictionary.TYPE_MAIN)?.getAllWordsWithFrequency() ?: emptyMap()

// TODO: Revise the way to fusion suggestion results.
override fun getSuggestionResults(
composedData: ComposedData, ngramContext: NgramContext, keyboard: Keyboard,
Expand Down
34 changes: 29 additions & 5 deletions app/src/main/java/helium314/keyboard/latin/Suggest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import helium314.keyboard.latin.define.DebugFlags
import helium314.keyboard.latin.define.DecoderSpecificConstants.SHOULD_AUTO_CORRECT_USING_NON_WHITE_LISTED_SUGGESTION
import helium314.keyboard.latin.define.DecoderSpecificConstants.SHOULD_REMOVE_PREVIOUSLY_REJECTED_SUGGESTION
import helium314.keyboard.latin.dictionary.Dictionary
import helium314.keyboard.latin.gesture.SwipeGestureEngine
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion
import helium314.keyboard.latin.suggestions.SuggestionStripView
Expand All @@ -35,8 +36,19 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
private val mPlausibilityThreshold = 0f
private val nextWordSuggestionsCache = HashMap<NgramContext, SuggestionResults>()

// Precomputed gesture word index, keyed by a fingerprint of the key positions.
// Rebuilt only when key centres actually change (language/layout switch), not on shift-state
// or action-button changes, and not on text-field focus changes.
@Volatile private var gestureIndex: SwipeGestureEngine.GestureIndex? = null
@Volatile private var gestureIndexFingerprint: Int = 0

// cache cleared whenever LatinIME.loadSettings is called, notably on changing layout and switching input fields
fun clearNextWordSuggestionsCache() = nextWordSuggestionsCache.clear()
fun clearNextWordSuggestionsCache() {
nextWordSuggestionsCache.clear()
// Do not clear gestureIndex here: loadSettings fires on every text-field focus, so
// clearing here would cause a 1-2s rebuild on every new field. The index is invalidated
// in getSuggestedWordsForBatchInput when the layout fingerprint changes.
}

/**
* Set the normalized-score threshold for a suggestion to be considered strong enough that we
Expand Down Expand Up @@ -265,10 +277,22 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
settingsValuesForSuggestion: SettingsValuesForSuggestion,
inputStyle: Int, sequenceNumber: Int
): SuggestedWords {
val suggestionResults = mDictionaryFacilitator.getSuggestionResults(
wordComposer.composedDataSnapshot, ngramContext, keyboard,
settingsValuesForSuggestion, SESSION_ID_GESTURE, inputStyle
)
val pointers = wordComposer.composedDataSnapshot.mInputPointers
// Build the precomputed gesture index lazily (once per keyboard layout).
// Keyed by a fingerprint of key positions so it survives text-field focus and shift-state
// changes, but is rebuilt when the user switches languages or physical layout.
// The build iterates the binary dict via JNI and runs on InputLogicHandler's background thread.
val fingerprint = SwipeGestureEngine.layoutFingerprint(keyboard)
var index = gestureIndex
if (index == null || index.byFirst.isEmpty() || gestureIndexFingerprint != fingerprint) {
val words = mDictionaryFacilitator.getAllMainDictionaryWordsWithFrequency()
index = SwipeGestureEngine.buildIndex(words, keyboard)
if (index.byFirst.isNotEmpty()) {
gestureIndex = index
gestureIndexFingerprint = fingerprint
}
}
val suggestionResults = SwipeGestureEngine.rankByIndex(index, pointers, keyboard, SuggestedWords.MAX_SUGGESTIONS)
replaceSingleLetterFirstSuggestion(suggestionResults)

// For transforming words that don't come from a dictionary, because it's our best bet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
package helium314.keyboard.latin.dictionary;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;

import helium314.keyboard.latin.NgramContext;
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo;
Expand Down Expand Up @@ -98,6 +100,16 @@ public boolean isValidWord(final String word) {
*/
abstract public boolean isInDictionary(final String word);

/**
* Returns all words stored in this dictionary.
* The default implementation returns an empty list; override in concrete dictionaries
* that support full enumeration (e.g. ReadOnlyBinaryDictionary).
*/
@androidx.annotation.NonNull
public Map<String, Integer> getAllWordsWithFrequency() {
return Collections.emptyMap();
}

/**
* Get the frequency of the word.
* @param word the word to get the frequency of.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
* Class for a collection of dictionaries that behave like one dictionary.
Expand Down Expand Up @@ -89,6 +91,14 @@ public int getMaxFrequencyOfExactMatches(final String word) {
return maxFreq;
}

@Override
@androidx.annotation.NonNull
public Map<String, Integer> getAllWordsWithFrequency() {
Map<String, Integer> result = new HashMap<>();
for (Dictionary dict : mDictionaries) result.putAll(dict.getAllWordsWithFrequency());
return result;
}

@Override
public boolean isInitialized() {
return !mDictionaries.isEmpty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
import helium314.keyboard.latin.settings.SettingsValuesForSuggestion;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
Expand Down Expand Up @@ -109,6 +111,30 @@ public int getMaxFrequencyOfExactMatches(final String word) {
return NOT_A_PROBABILITY;
}

@Override
@androidx.annotation.NonNull
public Map<String, Integer> getAllWordsWithFrequency() {
Map<String, Integer> words = new HashMap<>();
if (!mLock.readLock().tryLock()) return words;
try {
int token = 0;
do {
BinaryDictionary.GetNextWordPropertyResult result =
mBinaryDictionary.getNextWordProperty(token);
if (result.mWordProperty == null) break;
if (!result.mWordProperty.mIsNotAWord && !result.mWordProperty.mIsPossiblyOffensive) {
String word = result.mWordProperty.mWord;
if (word != null && !word.isEmpty())
words.put(word, result.mWordProperty.mProbabilityInfo.mProbability);
}
token = result.mNextToken;
} while (token != 0);
} finally {
mLock.readLock().unlock();
}
return words;
}

@Override
public WordProperty getWordProperty(String word, boolean isBeginningOfSentence) {
if (mLock.readLock().tryLock()) {
Expand Down
Loading