mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Refactor LanguageModel invocation code
This commit is contained in:
parent
0e0876f06c
commit
33be6fb3ed
@ -33,6 +33,7 @@
|
||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
|
||||
<uses-permission android:name="android.permission.WRITE_USER_DICTIONARY"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
|
||||
<!-- A signature-protected permission to ask AOSP Keyboard to close the software keyboard.
|
||||
To use this, add the following line into calling application's AndroidManifest.xml
|
||||
|
@ -107,8 +107,7 @@
|
||||
<!-- Common suggestion strip configuration. -->
|
||||
<integer name="config_suggestions_count_in_strip">3</integer>
|
||||
<fraction name="config_center_suggestion_percentile">36%</fraction>
|
||||
<integer name="config_delay_in_milliseconds_to_update_suggestions">100</integer>
|
||||
<integer name="config_delay_in_milliseconds_to_update_old_suggestions">300</integer>
|
||||
<integer name="config_delay_in_milliseconds_to_update_suggestions">0</integer>
|
||||
|
||||
<!-- Common more suggestions configuraion. -->
|
||||
<dimen name="config_more_suggestions_key_horizontal_padding">12dp</dimen>
|
||||
|
@ -64,7 +64,6 @@ public abstract class Dictionary {
|
||||
// User history dictionary internal to LatinIME.
|
||||
public static final String TYPE_USER_HISTORY = "history";
|
||||
|
||||
public static final String TYPE_GGML = "ggml";
|
||||
public final String mDictType;
|
||||
// The locale for this dictionary. May be null if unknown (phony dictionary for example).
|
||||
public final Locale mLocale;
|
||||
|
@ -45,7 +45,6 @@ import javax.annotation.Nullable;
|
||||
public interface DictionaryFacilitator {
|
||||
|
||||
public static final String[] ALL_DICTIONARY_TYPES = new String[] {
|
||||
Dictionary.TYPE_GGML,
|
||||
Dictionary.TYPE_MAIN,
|
||||
Dictionary.TYPE_CONTACTS,
|
||||
Dictionary.TYPE_USER_HISTORY,
|
||||
|
@ -136,8 +136,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||
@Nullable public final String mAccount;
|
||||
|
||||
@Nullable private Dictionary mMainDict;
|
||||
|
||||
@Nullable private LanguageModel mGGMLDict = null;
|
||||
// Confidence that the most probable language is actually the language the user is
|
||||
// typing in. For now, this is simply the number of times a word from this language
|
||||
// has been committed in a row.
|
||||
@ -185,9 +183,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||
if (Dictionary.TYPE_MAIN.equals(dictType)) {
|
||||
return mMainDict;
|
||||
}
|
||||
if (Dictionary.TYPE_GGML.equals(dictType)) {
|
||||
return mGGMLDict;
|
||||
}
|
||||
return getSubDict(dictType);
|
||||
}
|
||||
|
||||
@ -199,9 +194,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||
if (Dictionary.TYPE_MAIN.equals(dictType)) {
|
||||
return mMainDict != null;
|
||||
}
|
||||
if (Dictionary.TYPE_GGML.equals(dictType)) {
|
||||
return mGGMLDict != null;
|
||||
}
|
||||
if (Dictionary.TYPE_USER_HISTORY.equals(dictType) &&
|
||||
!TextUtils.equals(account, mAccount)) {
|
||||
// If the dictionary type is user history, & if the account doesn't match,
|
||||
@ -358,7 +350,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||
DictionaryGroup newDictionaryGroup =
|
||||
new DictionaryGroup(newLocale, mainDict, account, subDicts);
|
||||
|
||||
newDictionaryGroup.mGGMLDict = new LanguageModel(context, Dictionary.TYPE_GGML, newLocale);
|
||||
// Replace Dictionaries.
|
||||
final DictionaryGroup oldDictionaryGroup;
|
||||
synchronized (mLock) {
|
||||
@ -416,7 +407,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||
synchronized (mLock) {
|
||||
if (locale.equals(dictionaryGroup.mLocale)) {
|
||||
dictionaryGroup.setMainDict(mainDict);
|
||||
dictionaryGroup.mGGMLDict = new LanguageModel(context, Dictionary.TYPE_GGML, locale);
|
||||
} else {
|
||||
// Dictionary facilitator has been reset for another locale.
|
||||
mainDict.close();
|
||||
@ -628,6 +618,13 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||
NgramContext ngramContext, @Nonnull final Keyboard keyboard,
|
||||
SettingsValuesForSuggestion settingsValuesForSuggestion, int sessionId,
|
||||
int inputStyle) {
|
||||
|
||||
if(settingsValuesForSuggestion.mUseTransformerLM) {
|
||||
throw new IllegalStateException("Invalid code path TransformerLM");
|
||||
}
|
||||
|
||||
|
||||
|
||||
long proximityInfoHandle = keyboard.getProximityInfo().getNativeProximityInfo();
|
||||
final SuggestionResults suggestionResults = new SuggestionResults(
|
||||
SuggestedWords.MAX_SUGGESTIONS, ngramContext.isBeginningOfSentenceContext(),
|
||||
@ -635,9 +632,6 @@ public class DictionaryFacilitatorImpl implements DictionaryFacilitator {
|
||||
final float[] weightOfLangModelVsSpatialModel =
|
||||
new float[] { Dictionary.NOT_A_WEIGHT_OF_LANG_MODEL_VS_SPATIAL_MODEL };
|
||||
for (final String dictType : ALL_DICTIONARY_TYPES) {
|
||||
if(settingsValuesForSuggestion.mUseTransformerLM && dictType != Dictionary.TYPE_GGML) continue;
|
||||
else if(!settingsValuesForSuggestion.mUseTransformerLM && dictType == Dictionary.TYPE_GGML) continue;
|
||||
|
||||
final Dictionary dictionary = mDictionaryGroup.getDict(dictType);
|
||||
if (null == dictionary) continue;
|
||||
final float weightForLocale = composedData.mIsBatchMode
|
||||
|
@ -60,9 +60,19 @@ import androidx.savedstate.findViewTreeSavedStateRegistryOwner
|
||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import org.futo.inputmethod.latin.common.Constants
|
||||
import org.futo.inputmethod.latin.common.ComposedData
|
||||
import org.futo.inputmethod.latin.uix.Action
|
||||
import org.futo.inputmethod.latin.uix.ActionBar
|
||||
import org.futo.inputmethod.latin.uix.ActionInputTransaction
|
||||
@ -87,6 +97,11 @@ import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
|
||||
import org.futo.inputmethod.latin.uix.theme.presets.ClassicMaterialDark
|
||||
import org.futo.inputmethod.latin.uix.theme.presets.DynamicSystemTheme
|
||||
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
|
||||
import org.futo.inputmethod.latin.settings.SettingsValues;
|
||||
import org.futo.inputmethod.latin.settings.SettingsValuesForSuggestion
|
||||
import org.futo.inputmethod.latin.xlm.LanguageModel;
|
||||
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator;
|
||||
import org.futo.inputmethod.latin.utils.SuggestionResults
|
||||
|
||||
class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner,
|
||||
LatinIMELegacy.SuggestionStripController, DynamicThemeProviderOwner, KeyboardManagerForAction {
|
||||
@ -127,6 +142,15 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
this as LatinIMELegacy.SuggestionStripController
|
||||
)
|
||||
|
||||
public val languageModelFacilitator = LanguageModelFacilitator(
|
||||
this,
|
||||
latinIMELegacy.mInputLogic,
|
||||
latinIMELegacy.mDictionaryFacilitator,
|
||||
latinIMELegacy.mSettings,
|
||||
latinIMELegacy.mKeyboardSwitcher,
|
||||
lifecycleScope
|
||||
)
|
||||
|
||||
private var activeThemeOption: ThemeOption? = null
|
||||
private var activeColorScheme = DarkColorScheme
|
||||
private var colorSchemeLoaderJob: Job? = null
|
||||
@ -209,6 +233,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
|
||||
latinIMELegacy.onCreate()
|
||||
|
||||
languageModelFacilitator.launchProcessor()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@ -698,4 +724,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun postUpdateSuggestionStrip(inputStyle: Int) {
|
||||
languageModelFacilitator.updateSuggestionStripAsync(inputStyle);
|
||||
}
|
||||
}
|
@ -101,6 +101,7 @@ import org.futo.inputmethod.latin.utils.StatsUtils;
|
||||
import org.futo.inputmethod.latin.utils.StatsUtilsManager;
|
||||
import org.futo.inputmethod.latin.utils.SubtypeLocaleUtils;
|
||||
import org.futo.inputmethod.latin.utils.ViewLayoutUtils;
|
||||
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
@ -157,7 +158,7 @@ public class LatinIMELegacy implements KeyboardActionListener,
|
||||
private static final String SCHEME_PACKAGE = "package";
|
||||
|
||||
final Settings mSettings;
|
||||
private final DictionaryFacilitator mDictionaryFacilitator =
|
||||
final DictionaryFacilitator mDictionaryFacilitator =
|
||||
DictionaryFacilitatorProvider.getDictionaryFacilitator(
|
||||
false /* isNeededForSpellChecking */);
|
||||
final InputLogic mInputLogic;
|
||||
@ -220,7 +221,7 @@ public class LatinIMELegacy implements KeyboardActionListener,
|
||||
public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIMELegacy> {
|
||||
private static final int MSG_UPDATE_SHIFT_STATE = 0;
|
||||
private static final int MSG_PENDING_IMS_CALLBACK = 1;
|
||||
private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
|
||||
private static final int MSG_UPDATE_SUGGESTION_STRIP_LEGACY = 2;
|
||||
private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
|
||||
private static final int MSG_RESUME_SUGGESTIONS = 4;
|
||||
private static final int MSG_REOPEN_DICTIONARIES = 5;
|
||||
@ -266,7 +267,7 @@ public class LatinIMELegacy implements KeyboardActionListener,
|
||||
}
|
||||
final KeyboardSwitcher switcher = latinImeLegacy.mKeyboardSwitcher;
|
||||
switch (msg.what) {
|
||||
case MSG_UPDATE_SUGGESTION_STRIP:
|
||||
case MSG_UPDATE_SUGGESTION_STRIP_LEGACY:
|
||||
cancelUpdateSuggestionStrip();
|
||||
latinImeLegacy.mInputLogic.performUpdateSuggestionStripSync(
|
||||
latinImeLegacy.mSettings.getCurrent(), msg.arg1 /* inputStyle */);
|
||||
@ -332,8 +333,13 @@ public class LatinIMELegacy implements KeyboardActionListener,
|
||||
}
|
||||
|
||||
public void postUpdateSuggestionStrip(final int inputStyle) {
|
||||
sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP, inputStyle,
|
||||
0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions);
|
||||
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
|
||||
if(latinImeLegacy.mSettings.getCurrent().mTransformerPredictionEnabled) {
|
||||
((LatinIME)latinImeLegacy.getInputMethodService()).postUpdateSuggestionStrip(inputStyle);
|
||||
} else {
|
||||
sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP_LEGACY, inputStyle,
|
||||
0 /* ignored */), mDelayInMillisecondsToUpdateSuggestions);
|
||||
}
|
||||
}
|
||||
|
||||
public void postReopenDictionaries() {
|
||||
@ -389,11 +395,16 @@ public class LatinIMELegacy implements KeyboardActionListener,
|
||||
}
|
||||
|
||||
public void cancelUpdateSuggestionStrip() {
|
||||
removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
|
||||
removeMessages(MSG_UPDATE_SUGGESTION_STRIP_LEGACY);
|
||||
}
|
||||
|
||||
public boolean hasPendingUpdateSuggestions() {
|
||||
return hasMessages(MSG_UPDATE_SUGGESTION_STRIP);
|
||||
return hasMessages(MSG_UPDATE_SUGGESTION_STRIP_LEGACY);
|
||||
}
|
||||
|
||||
public LanguageModelFacilitator getLanguageModelFacilitator() {
|
||||
final LatinIMELegacy latinImeLegacy = getOwnerInstance();
|
||||
return ((LatinIME)(latinImeLegacy.mInputMethodService)).getLanguageModelFacilitator();
|
||||
}
|
||||
|
||||
public boolean hasPendingReopenDictionaries() {
|
||||
@ -1562,12 +1573,17 @@ public class LatinIMELegacy implements KeyboardActionListener,
|
||||
// TODO[IL]: Move this out of LatinIME.
|
||||
public void getSuggestedWords(final int inputStyle, final int sequenceNumber,
|
||||
final OnGetSuggestedWordsCallback callback) {
|
||||
SettingsValues settings = mSettings.getCurrent();
|
||||
if(settings.mTransformerPredictionEnabled) {
|
||||
((LatinIME)getInputMethodService()).postUpdateSuggestionStrip(inputStyle);
|
||||
return;
|
||||
}
|
||||
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
|
||||
if (keyboard == null) {
|
||||
callback.onGetSuggestedWords(SuggestedWords.getEmptyInstance());
|
||||
return;
|
||||
}
|
||||
mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard,
|
||||
mInputLogic.getSuggestedWords(settings, keyboard,
|
||||
mKeyboardSwitcher.getKeyboardShiftMode(), inputStyle, sequenceNumber, callback);
|
||||
}
|
||||
|
||||
|
@ -147,13 +147,12 @@ public final class Suggest {
|
||||
return firstSuggestedWordInfo;
|
||||
}
|
||||
|
||||
// Retrieves suggestions for non-batch input (typing, recorrection, predictions...)
|
||||
// and calls the callback function with the suggestions.
|
||||
private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer,
|
||||
final NgramContext ngramContext, final Keyboard keyboard,
|
||||
final SettingsValuesForSuggestion settingsValuesForSuggestion,
|
||||
final int inputStyleIfNotPrediction, final boolean isCorrectionEnabled,
|
||||
final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
|
||||
public static SuggestedWords obtainNonBatchedInputSuggestedWords(
|
||||
final WordComposer wordComposer, final int inputStyleIfNotPrediction,
|
||||
final boolean isCorrectionEnabled, final int sequenceNumber,
|
||||
final Locale locale, final SuggestionResults suggestionResults,
|
||||
final float autoCorrectionThreshold
|
||||
) {
|
||||
final String typedWordString = wordComposer.getTypedWord();
|
||||
final int trailingSingleQuotesCount =
|
||||
StringUtils.getTrailingSingleQuotesCount(typedWordString);
|
||||
@ -161,10 +160,6 @@ public final class Suggest {
|
||||
? typedWordString.substring(0, typedWordString.length() - trailingSingleQuotesCount)
|
||||
: typedWordString;
|
||||
|
||||
final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
|
||||
wordComposer.getComposedDataSnapshot(), ngramContext, keyboard,
|
||||
settingsValuesForSuggestion, SESSION_ID_TYPING, inputStyleIfNotPrediction);
|
||||
final Locale locale = mDictionaryFacilitator.getLocale();
|
||||
final ArrayList<SuggestedWordInfo> suggestionsContainer =
|
||||
getTransformedSuggestedWordInfoList(wordComposer, suggestionResults,
|
||||
trailingSingleQuotesCount, locale);
|
||||
@ -223,7 +218,7 @@ public final class Suggest {
|
||||
// list, "will" would always auto-correct to "Will" which is unwanted. Hence, no
|
||||
// main dict => no auto-correct. Also, it would probably get obnoxious quickly.
|
||||
// TODO: now that we have personalization, we may want to re-evaluate this decision
|
||||
|| !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()
|
||||
//|| !mDictionaryFacilitator.hasAtLeastOneInitializedMainDictionary()
|
||||
// If the first suggestion is a shortcut we never auto-correct to it, regardless
|
||||
// of how strong it is (allowlist entries are not KIND_SHORTCUT but KIND_WHITELIST).
|
||||
// TODO: we may want to have shortcut-only entries auto-correct in the future.
|
||||
@ -235,7 +230,7 @@ public final class Suggest {
|
||||
&& firstOcurrenceOfTypedWordInSuggestions != 0) {
|
||||
hasAutoCorrection = true;
|
||||
} else if (!AutoCorrectionUtils.suggestionExceedsThreshold(
|
||||
firstSuggestion, consideredWord, mAutoCorrectionThreshold)) {
|
||||
firstSuggestion, consideredWord, autoCorrectionThreshold)) {
|
||||
// Score is too low for autocorrect
|
||||
hasAutoCorrection = false;
|
||||
} else {
|
||||
@ -277,11 +272,30 @@ public final class Suggest {
|
||||
|
||||
final boolean isTypedWordValid = firstOcurrenceOfTypedWordInSuggestions > -1
|
||||
|| (!resultsArePredictions && !allowsToBeAutoCorrected);
|
||||
callback.onGetSuggestedWords(new SuggestedWords(suggestionsList,
|
||||
return new SuggestedWords(suggestionsList,
|
||||
suggestionResults.mRawSuggestions, typedWordInfo,
|
||||
isTypedWordValid,
|
||||
hasAutoCorrection /* willAutoCorrect */,
|
||||
false /* isObsoleteSuggestions */, inputStyle, sequenceNumber));
|
||||
false /* isObsoleteSuggestions */, inputStyle, sequenceNumber);
|
||||
}
|
||||
|
||||
|
||||
// Retrieves suggestions for non-batch input (typing, recorrection, predictions...)
|
||||
// and calls the callback function with the suggestions.
|
||||
private void getSuggestedWordsForNonBatchInput(final WordComposer wordComposer,
|
||||
final NgramContext ngramContext, final Keyboard keyboard,
|
||||
final SettingsValuesForSuggestion settingsValuesForSuggestion,
|
||||
final int inputStyleIfNotPrediction, final boolean isCorrectionEnabled,
|
||||
final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
|
||||
final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
|
||||
wordComposer.getComposedDataSnapshot(), ngramContext, keyboard,
|
||||
settingsValuesForSuggestion, SESSION_ID_TYPING, inputStyleIfNotPrediction);
|
||||
final Locale locale = mDictionaryFacilitator.getLocale();
|
||||
|
||||
callback.onGetSuggestedWords(
|
||||
obtainNonBatchedInputSuggestedWords(wordComposer, inputStyleIfNotPrediction,
|
||||
isCorrectionEnabled, sequenceNumber, locale, suggestionResults, mAutoCorrectionThreshold)
|
||||
);
|
||||
}
|
||||
|
||||
// Retrieves suggestions for the batch input
|
||||
|
@ -58,6 +58,7 @@ import org.futo.inputmethod.latin.utils.InputTypeUtils;
|
||||
import org.futo.inputmethod.latin.utils.RecapitalizeStatus;
|
||||
import org.futo.inputmethod.latin.utils.StatsUtils;
|
||||
import org.futo.inputmethod.latin.utils.TextRange;
|
||||
import org.futo.inputmethod.latin.xlm.LanguageModelFacilitator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
@ -74,7 +75,7 @@ public final class InputLogic {
|
||||
|
||||
// TODO : Remove this member when we can.
|
||||
final LatinIMELegacy mLatinIMELegacy;
|
||||
private final SuggestionStripViewAccessor mSuggestionStripViewAccessor;
|
||||
public final SuggestionStripViewAccessor mSuggestionStripViewAccessor;
|
||||
|
||||
// Never null.
|
||||
private InputLogicHandler mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
|
||||
@ -88,8 +89,8 @@ public final class InputLogic {
|
||||
private final DictionaryFacilitator mDictionaryFacilitator;
|
||||
|
||||
public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
|
||||
// This has package visibility so it can be accessed from InputLogicHandler.
|
||||
/* package */ final WordComposer mWordComposer;
|
||||
|
||||
public final WordComposer mWordComposer;
|
||||
public final RichInputConnection mConnection;
|
||||
private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
|
||||
|
||||
@ -1443,62 +1444,90 @@ public final class InputLogic {
|
||||
ngramContext, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
|
||||
}
|
||||
|
||||
private void ensureSuggestionStripCompleted(final SettingsValues settingsValues,
|
||||
final String separator, final LatinIMELegacy.UIHandler handler) {
|
||||
if(settingsValues.mTransformerPredictionEnabled) {
|
||||
LanguageModelFacilitator facilitator = handler.getLanguageModelFacilitator();
|
||||
if(facilitator.hasPendingUpdate()) {
|
||||
facilitator.blockUntilComplete();
|
||||
}
|
||||
} else {
|
||||
if (handler.hasPendingUpdateSuggestions()) {
|
||||
handler.cancelUpdateSuggestionStrip();
|
||||
// To know the input style here, we should retrieve the in-flight "update suggestions"
|
||||
// message and read its arg1 member here. However, the Handler class does not let
|
||||
// us retrieve this message, so we can't do that. But in fact, we notice that
|
||||
// we only ever come here when the input style was typing. In the case of batch
|
||||
// input, we update the suggestions synchronously when the tail batch comes. Likewise
|
||||
// for application-specified completions. As for recorrections, we never auto-correct,
|
||||
// so we don't come here either. Hence, the input style is necessarily
|
||||
// INPUT_STYLE_TYPING.
|
||||
performUpdateSuggestionStripSync(settingsValues, SuggestedWords.INPUT_STYLE_TYPING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
|
||||
final int inputStyle) {
|
||||
long startTimeMillis = 0;
|
||||
if (DebugFlags.DEBUG_ENABLED) {
|
||||
startTimeMillis = System.currentTimeMillis();
|
||||
Log.d(TAG, "performUpdateSuggestionStripSync()");
|
||||
}
|
||||
// Check if we have a suggestion engine attached.
|
||||
if (!settingsValues.needsToLookupSuggestions()) {
|
||||
if (mWordComposer.isComposingWord()) {
|
||||
Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
|
||||
+ "requested!");
|
||||
if(settingsValues.mTransformerPredictionEnabled) {
|
||||
throw new IllegalStateException("called performUpdateSuggestionStripSync during TransformerLM");
|
||||
} else {
|
||||
long startTimeMillis = 0;
|
||||
if (DebugFlags.DEBUG_ENABLED) {
|
||||
startTimeMillis = System.currentTimeMillis();
|
||||
Log.d(TAG, "performUpdateSuggestionStripSync()");
|
||||
}
|
||||
// Check if we have a suggestion engine attached.
|
||||
if (!settingsValues.needsToLookupSuggestions()) {
|
||||
if (mWordComposer.isComposingWord()) {
|
||||
Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
|
||||
+ "requested!");
|
||||
}
|
||||
// Clear the suggestions strip.
|
||||
mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.getEmptyInstance());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mWordComposer.isComposingWord() && !settingsValues.mBigramPredictionEnabled) {
|
||||
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
|
||||
return;
|
||||
}
|
||||
// Clear the suggestions strip.
|
||||
mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.getEmptyInstance());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mWordComposer.isComposingWord() && !settingsValues.mBigramPredictionEnabled) {
|
||||
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
|
||||
return;
|
||||
}
|
||||
|
||||
final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>("Suggest");
|
||||
mInputLogicHandler.getSuggestedWords(inputStyle, SuggestedWords.NOT_A_SEQUENCE_NUMBER,
|
||||
new OnGetSuggestedWordsCallback() {
|
||||
@Override
|
||||
public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
|
||||
final String typedWordString = mWordComposer.getTypedWord();
|
||||
final SuggestedWordInfo typedWordInfo = new SuggestedWordInfo(
|
||||
typedWordString, "" /* prevWordsContext */,
|
||||
SuggestedWordInfo.MAX_SCORE,
|
||||
SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
|
||||
SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
|
||||
SuggestedWordInfo.NOT_A_CONFIDENCE);
|
||||
// Show new suggestions if we have at least one. Otherwise keep the old
|
||||
// suggestions with the new typed word. Exception: if the length of the
|
||||
// typed word is <= 1 (after a deletion typically) we clear old suggestions.
|
||||
if (suggestedWords.size() > 1 || typedWordString.length() <= 1) {
|
||||
holder.set(suggestedWords);
|
||||
} else {
|
||||
holder.set(retrieveOlderSuggestions(typedWordInfo, mSuggestedWords));
|
||||
final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>("Suggest");
|
||||
mInputLogicHandler.getSuggestedWords(inputStyle, SuggestedWords.NOT_A_SEQUENCE_NUMBER,
|
||||
new OnGetSuggestedWordsCallback() {
|
||||
@Override
|
||||
public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
|
||||
final String typedWordString = mWordComposer.getTypedWord();
|
||||
final SuggestedWordInfo typedWordInfo = new SuggestedWordInfo(
|
||||
typedWordString, "" /* prevWordsContext */,
|
||||
SuggestedWordInfo.MAX_SCORE,
|
||||
SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
|
||||
SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
|
||||
SuggestedWordInfo.NOT_A_CONFIDENCE);
|
||||
// Show new suggestions if we have at least one. Otherwise keep the old
|
||||
// suggestions with the new typed word. Exception: if the length of the
|
||||
// typed word is <= 1 (after a deletion typically) we clear old suggestions.
|
||||
if (suggestedWords.size() > 1 || typedWordString.length() <= 1) {
|
||||
holder.set(suggestedWords);
|
||||
} else {
|
||||
holder.set(retrieveOlderSuggestions(typedWordInfo, mSuggestedWords));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
// This line may cause the current thread to wait.
|
||||
final SuggestedWords suggestedWords = holder.get(null,
|
||||
Constants.GET_SUGGESTED_WORDS_TIMEOUT);
|
||||
if (suggestedWords != null) {
|
||||
mSuggestionStripViewAccessor.showSuggestionStrip(suggestedWords);
|
||||
}
|
||||
if (DebugFlags.DEBUG_ENABLED) {
|
||||
long runTimeMillis = System.currentTimeMillis() - startTimeMillis;
|
||||
Log.d(TAG, "performUpdateSuggestionStripSync() : " + runTimeMillis + " ms to finish");
|
||||
// This line may cause the current thread to wait.
|
||||
final SuggestedWords suggestedWords = holder.get(null,
|
||||
Constants.GET_SUGGESTED_WORDS_TIMEOUT);
|
||||
if (suggestedWords != null) {
|
||||
mSuggestionStripViewAccessor.showSuggestionStrip(suggestedWords);
|
||||
}
|
||||
if (DebugFlags.DEBUG_ENABLED) {
|
||||
long runTimeMillis = System.currentTimeMillis() - startTimeMillis;
|
||||
Log.d(TAG, "performUpdateSuggestionStripSync() : " + runTimeMillis + " ms to finish");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2094,18 +2123,7 @@ public final class InputLogic {
|
||||
private void commitCurrentAutoCorrection(final SettingsValues settingsValues,
|
||||
final String separator, final LatinIMELegacy.UIHandler handler) {
|
||||
// Complete any pending suggestions query first
|
||||
if (handler.hasPendingUpdateSuggestions()) {
|
||||
handler.cancelUpdateSuggestionStrip();
|
||||
// To know the input style here, we should retrieve the in-flight "update suggestions"
|
||||
// message and read its arg1 member here. However, the Handler class does not let
|
||||
// us retrieve this message, so we can't do that. But in fact, we notice that
|
||||
// we only ever come here when the input style was typing. In the case of batch
|
||||
// input, we update the suggestions synchronously when the tail batch comes. Likewise
|
||||
// for application-specified completions. As for recorrections, we never auto-correct,
|
||||
// so we don't come here either. Hence, the input style is necessarily
|
||||
// INPUT_STYLE_TYPING.
|
||||
performUpdateSuggestionStripSync(settingsValues, SuggestedWords.INPUT_STYLE_TYPING);
|
||||
}
|
||||
ensureSuggestionStripCompleted(settingsValues, separator, handler);
|
||||
final SuggestedWordInfo autoCorrectionOrNull = mWordComposer.getAutoCorrectionOrNull();
|
||||
final String typedWord = mWordComposer.getTypedWord();
|
||||
final String stringToCommit = (autoCorrectionOrNull != null)
|
||||
|
@ -55,7 +55,6 @@ public class SettingsValues {
|
||||
|
||||
// From resources:
|
||||
public final SpacingAndPunctuations mSpacingAndPunctuations;
|
||||
public final int mDelayInMillisecondsToUpdateOldSuggestions;
|
||||
public final long mDoubleSpacePeriodTimeout;
|
||||
// From configuration:
|
||||
public final Locale mLocale;
|
||||
@ -124,8 +123,6 @@ public class SettingsValues {
|
||||
@Nonnull final InputAttributes inputAttributes) {
|
||||
mLocale = res.getConfiguration().locale;
|
||||
// Get the resources
|
||||
mDelayInMillisecondsToUpdateOldSuggestions =
|
||||
res.getInteger(R.integer.config_delay_in_milliseconds_to_update_old_suggestions);
|
||||
mSpacingAndPunctuations = new SpacingAndPunctuations(res);
|
||||
|
||||
// Store the input attributes
|
||||
@ -381,8 +378,6 @@ public class SettingsValues {
|
||||
final StringBuilder sb = new StringBuilder("Current settings :");
|
||||
sb.append("\n mSpacingAndPunctuations = ");
|
||||
sb.append("" + mSpacingAndPunctuations.dump());
|
||||
sb.append("\n mDelayInMillisecondsToUpdateOldSuggestions = ");
|
||||
sb.append("" + mDelayInMillisecondsToUpdateOldSuggestions);
|
||||
sb.append("\n mAutoCap = ");
|
||||
sb.append("" + mAutoCap);
|
||||
sb.append("\n mVibrateOn = ");
|
||||
|
@ -95,16 +95,16 @@ public class LanguageModel extends Dictionary {
|
||||
@Override public void run() {
|
||||
if(mNativeState != 0) return;
|
||||
|
||||
String modelPath = getPathToModelResource(context, R.raw.ml4_f16, R.raw.ml3_tokenizer, true);
|
||||
String modelPath = getPathToModelResource(context, R.raw.ml4_1_f16, R.raw.ml3_tokenizer, true);
|
||||
mNativeState = openNative(modelPath);
|
||||
|
||||
if(mNativeState == 0){
|
||||
modelPath = getPathToModelResource(context, R.raw.ml4_f16, R.raw.ml3_tokenizer, true);
|
||||
modelPath = getPathToModelResource(context, R.raw.ml4_1_f16, R.raw.ml3_tokenizer, true);
|
||||
mNativeState = openNative(modelPath);
|
||||
}
|
||||
|
||||
if(mNativeState == 0){
|
||||
throw new RuntimeException("Failed to load R.raw.ml4_f16, R.raw.ml3_tokenizer model");
|
||||
throw new RuntimeException("Failed to load R.raw.ml4_1_f16, R.raw.ml3_tokenizer model");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,246 @@
|
||||
package org.futo.inputmethod.latin.xlm;
|
||||
|
||||
import android.content.ComponentCallbacks2
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.CompletionInfo
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InlineSuggestion
|
||||
import android.view.inputmethod.InlineSuggestionsRequest
|
||||
import android.view.inputmethod.InlineSuggestionsResponse
|
||||
import android.view.inputmethod.InputMethodSubtype
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.findViewTreeViewModelStoreOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.setViewTreeViewModelStoreOwner
|
||||
import androidx.savedstate.SavedStateRegistry
|
||||
import androidx.savedstate.SavedStateRegistryController
|
||||
import androidx.savedstate.SavedStateRegistryOwner
|
||||
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
|
||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import org.futo.inputmethod.latin.common.Constants
|
||||
import org.futo.inputmethod.latin.common.ComposedData
|
||||
import org.futo.inputmethod.latin.uix.Action
|
||||
import org.futo.inputmethod.latin.uix.ActionBar
|
||||
import org.futo.inputmethod.latin.uix.ActionInputTransaction
|
||||
import org.futo.inputmethod.latin.uix.ActionWindow
|
||||
import org.futo.inputmethod.latin.uix.BasicThemeProvider
|
||||
import org.futo.inputmethod.latin.uix.DynamicThemeProvider
|
||||
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner
|
||||
import org.futo.inputmethod.latin.uix.KeyboardManagerForAction
|
||||
import org.futo.inputmethod.latin.uix.PersistentActionState
|
||||
import org.futo.inputmethod.latin.uix.THEME_KEY
|
||||
import org.futo.inputmethod.latin.uix.actions.VoiceInputAction
|
||||
import org.futo.inputmethod.latin.uix.createInlineSuggestionsRequest
|
||||
import org.futo.inputmethod.latin.uix.deferGetSetting
|
||||
import org.futo.inputmethod.latin.uix.deferSetSetting
|
||||
import org.futo.inputmethod.latin.uix.differsFrom
|
||||
import org.futo.inputmethod.latin.uix.inflateInlineSuggestion
|
||||
import org.futo.inputmethod.latin.uix.theme.DarkColorScheme
|
||||
import org.futo.inputmethod.latin.uix.theme.ThemeOption
|
||||
import org.futo.inputmethod.latin.uix.theme.ThemeOptions
|
||||
import org.futo.inputmethod.latin.uix.theme.Typography
|
||||
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
|
||||
import org.futo.inputmethod.latin.uix.theme.presets.ClassicMaterialDark
|
||||
import org.futo.inputmethod.latin.uix.theme.presets.DynamicSystemTheme
|
||||
import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
|
||||
import org.futo.inputmethod.latin.settings.SettingsValues;
|
||||
import org.futo.inputmethod.latin.settings.SettingsValuesForSuggestion
|
||||
import org.futo.inputmethod.latin.settings.Settings
|
||||
import org.futo.inputmethod.latin.xlm.LanguageModel;
|
||||
import org.futo.inputmethod.latin.utils.SuggestionResults
|
||||
import org.futo.inputmethod.latin.NgramContext
|
||||
import org.futo.inputmethod.latin.LatinIMELegacy
|
||||
import org.futo.inputmethod.latin.inputlogic.InputLogic
|
||||
import org.futo.inputmethod.latin.DictionaryFacilitator
|
||||
import org.futo.inputmethod.latin.Suggest
|
||||
import org.futo.inputmethod.latin.SuggestedWords
|
||||
import org.futo.inputmethod.keyboard.KeyboardSwitcher
|
||||
|
||||
public class LanguageModelFacilitator(
|
||||
val context: Context,
|
||||
val inputLogic: InputLogic,
|
||||
val dictionaryFacilitator: DictionaryFacilitator,
|
||||
val settings: Settings,
|
||||
val keyboardSwitcher: KeyboardSwitcher,
|
||||
val lifecycleScope: LifecycleCoroutineScope
|
||||
) {
|
||||
private var languageModel: LanguageModel? = null
|
||||
data class PredictionInputValues(
|
||||
val composedData: ComposedData,
|
||||
val ngramContext: NgramContext,
|
||||
val inputStyle: Int,
|
||||
val sequenceId: Int
|
||||
)
|
||||
private val sharedFlow = MutableSharedFlow<PredictionInputValues>(replay = 0, extraBufferCapacity = 1)
|
||||
|
||||
private var currentSequenceId = 0
|
||||
private val sequenceIdFinishedFlow = MutableSharedFlow<Int>(replay = 4, extraBufferCapacity = 4)
|
||||
|
||||
private val computationSemaphore = Semaphore(1)
|
||||
public fun hasPendingUpdate(): Boolean =
|
||||
computationSemaphore.availablePermits == 0
|
||||
|
||||
public fun blockUntilComplete() {
|
||||
runBlocking {
|
||||
computationSemaphore.acquire()
|
||||
computationSemaphore.release()
|
||||
try {
|
||||
sequenceIdFinishedFlow.first { it >= currentSequenceId }
|
||||
} catch(ignored: Exception) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun processUpdateSuggestionStrip(values: PredictionInputValues) {
|
||||
computationSemaphore.acquire()
|
||||
try {
|
||||
val job = Job()
|
||||
CoroutineScope(Dispatchers.Default + job).launch {
|
||||
delay(200)
|
||||
inputLogic.mSuggestionStripViewAccessor.setNeutralSuggestionStrip()
|
||||
}
|
||||
|
||||
val locale = dictionaryFacilitator.locale
|
||||
if(languageModel == null) {
|
||||
languageModel = LanguageModel(context, "lm", locale)
|
||||
}
|
||||
|
||||
val settingsValues = settings.current
|
||||
|
||||
val keyboard = keyboardSwitcher.getKeyboard()
|
||||
val settingsForPrediction = SettingsValuesForSuggestion(
|
||||
settingsValues.mBlockPotentiallyOffensive,
|
||||
settingsValues.mTransformerPredictionEnabled
|
||||
)
|
||||
val proximityInfoHandle = keyboard.getProximityInfo().getNativeProximityInfo()
|
||||
|
||||
val suggestionResults = SuggestionResults(
|
||||
3, values.ngramContext.isBeginningOfSentenceContext(), false)
|
||||
|
||||
val lmSuggestions = languageModel!!.getSuggestions(
|
||||
values.composedData,
|
||||
values.ngramContext,
|
||||
proximityInfoHandle,
|
||||
settingsForPrediction,
|
||||
-1,
|
||||
0.0f,
|
||||
floatArrayOf())
|
||||
|
||||
if(lmSuggestions == null) {
|
||||
job.cancel()
|
||||
inputLogic.mSuggestionStripViewAccessor.setNeutralSuggestionStrip()
|
||||
return
|
||||
}
|
||||
|
||||
suggestionResults.addAll(lmSuggestions)
|
||||
if(suggestionResults.mRawSuggestions != null) {
|
||||
suggestionResults.mRawSuggestions.addAll(lmSuggestions)
|
||||
}
|
||||
|
||||
val wordComposer = inputLogic.mWordComposer
|
||||
val suggestedWords = Suggest.obtainNonBatchedInputSuggestedWords(
|
||||
wordComposer, values.inputStyle, true, -1, locale, suggestionResults, settingsValues.mAutoCorrectionThreshold)
|
||||
|
||||
job.cancel()
|
||||
inputLogic.mSuggestionStripViewAccessor.showSuggestionStrip(suggestedWords)
|
||||
sequenceIdFinishedFlow.emit(values.sequenceId)
|
||||
} finally {
|
||||
computationSemaphore.release()
|
||||
}
|
||||
}
|
||||
|
||||
public fun launchProcessor() = lifecycleScope.launch {
|
||||
println("LatinIME: Starting processor")
|
||||
withContext(Dispatchers.Default) {
|
||||
sharedFlow.conflate().collect { value ->
|
||||
println("LatinIME: Collecting")
|
||||
processUpdateSuggestionStrip(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun updateSuggestionStripAsync(inputStyle: Int) {
|
||||
val settingsValues = settings.current
|
||||
if (!settingsValues.needsToLookupSuggestions()) {
|
||||
inputLogic.mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.getEmptyInstance())
|
||||
return
|
||||
}
|
||||
|
||||
if(!settingsValues.mTransformerPredictionEnabled) {
|
||||
// TODO: Call old path
|
||||
return
|
||||
}
|
||||
|
||||
val wordComposer = inputLogic.mWordComposer
|
||||
val ngramContext = inputLogic.getNgramContextFromNthPreviousWordForSuggestion(settingsValues.mSpacingAndPunctuations, 2)
|
||||
|
||||
val values = PredictionInputValues(
|
||||
wordComposer.getComposedDataSnapshot(),
|
||||
ngramContext,
|
||||
inputStyle,
|
||||
++currentSequenceId
|
||||
)
|
||||
|
||||
lifecycleScope.launch {
|
||||
println("LatinIME: Emitting values")
|
||||
sharedFlow.emit(values)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user