Refactor LanguageModel invocation code

This commit is contained in:
Aleksandras Kostarevas 2023-11-13 16:44:52 +02:00
parent 0e0876f06c
commit 33be6fb3ed
12 changed files with 422 additions and 111 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = ");

View File

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

View File

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