simplify the way of setting suggestions

remove the excursion to latinIME
have Suggest return suggestions instead of calling a callback
now the relevant code can be mostly found in inputLogic (except for handling batch input)
This commit is contained in:
Helium314 2025-06-07 20:26:06 +02:00
parent 0787a79de4
commit 867438fdc0
4 changed files with 63 additions and 133 deletions

View file

@ -60,7 +60,6 @@ import helium314.keyboard.keyboard.KeyboardId;
import helium314.keyboard.keyboard.KeyboardLayoutSet;
import helium314.keyboard.keyboard.KeyboardSwitcher;
import helium314.keyboard.keyboard.MainKeyboardView;
import helium314.keyboard.latin.Suggest.OnGetSuggestedWordsCallback;
import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo;
import helium314.keyboard.latin.common.ColorType;
import helium314.keyboard.latin.common.Constants;
@ -129,6 +128,9 @@ public class LatinIME extends InputMethodService implements
public final KeyboardActionListener mKeyboardActionListener;
private int mOriginalNavBarColor = 0;
private int mOriginalNavBarFlags = 0;
// UIHandler is needed when creating InputLogic
public final UIHandler mHandler = new UIHandler(this);
private final DictionaryFacilitator mDictionaryFacilitator =
DictionaryFacilitatorProvider.getDictionaryFacilitator(false);
final InputLogic mInputLogic = new InputLogic(this, this, mDictionaryFacilitator);
@ -187,8 +189,6 @@ public class LatinIME extends InputMethodService implements
private final ClipboardHistoryManager mClipboardHistoryManager = new ClipboardHistoryManager(this);
public final UIHandler mHandler = new UIHandler(this);
public static final class UIHandler extends LeakGuardHandlerWrapper<LatinIME> {
private static final int MSG_UPDATE_SHIFT_STATE = 0;
private static final int MSG_PENDING_IMS_CALLBACK = 1;
@ -796,14 +796,6 @@ public class LatinIME extends InputMethodService implements
deallocateMemory();
}
public void recycle() {
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
unregisterReceiver(mRingerModeChangeReceiver);
unregisterReceiver(mRestartAfterDeviceUnlockReceiver);
mInputLogic.recycle();
}
private boolean isImeSuppressedByHardwareKeyboard() {
final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
return !onEvaluateInputViewShown() && switcher.isImeSuppressedByHardwareKeyboard(
@ -1683,18 +1675,6 @@ public class LatinIME extends InputMethodService implements
}
}
// TODO[IL]: Move this out of LatinIME.
public void getSuggestedWords(final int inputStyle, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
if (keyboard == null) {
callback.onGetSuggestedWords(SuggestedWords.getEmptyInstance());
return;
}
mInputLogic.getSuggestedWords(mSettings.getCurrent(), keyboard,
mKeyboardSwitcher.getKeyboardShiftMode(), inputStyle, sequenceNumber, callback);
}
@Override
public void showSuggestionStrip(final SuggestedWords suggestedWords) {
if (suggestedWords.isEmpty()) {

View file

@ -45,19 +45,20 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
mAutoCorrectionThreshold = threshold
}
// todo: remove when InputLogic is ready
interface OnGetSuggestedWordsCallback {
fun onGetSuggestedWords(suggestedWords: SuggestedWords?)
}
fun getSuggestedWords(wordComposer: WordComposer, ngramContext: NgramContext, keyboard: Keyboard,
settingsValuesForSuggestion: SettingsValuesForSuggestion, isCorrectionEnabled: Boolean,
inputStyle: Int, sequenceNumber: Int, callback: OnGetSuggestedWordsCallback) {
if (wordComposer.isBatchMode) {
inputStyle: Int, sequenceNumber: Int): SuggestedWords {
return if (wordComposer.isBatchMode) {
getSuggestedWordsForBatchInput(wordComposer, ngramContext, keyboard, settingsValuesForSuggestion,
inputStyle, sequenceNumber, callback)
inputStyle, sequenceNumber)
} else {
getSuggestedWordsForNonBatchInput(wordComposer, ngramContext, keyboard, settingsValuesForSuggestion,
inputStyle, isCorrectionEnabled, sequenceNumber, callback)
inputStyle, isCorrectionEnabled, sequenceNumber)
}
}
@ -65,7 +66,7 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
// and calls the callback function with the suggestions.
private fun getSuggestedWordsForNonBatchInput(wordComposer: WordComposer, ngramContext: NgramContext, keyboard: Keyboard,
settingsValuesForSuggestion: SettingsValuesForSuggestion, inputStyleIfNotPrediction: Int,
isCorrectionEnabled: Boolean, sequenceNumber: Int, callback: OnGetSuggestedWordsCallback) {
isCorrectionEnabled: Boolean, sequenceNumber: Int): SuggestedWords {
val typedWordString = wordComposer.typedWord
val resultsArePredictions = !wordComposer.isComposingWord
val suggestionResults = if (typedWordString.isEmpty())
@ -131,8 +132,8 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
}
}
val isTypedWordValid = firstOccurrenceOfTypedWordInSuggestions > -1 || (!resultsArePredictions && !allowsToBeAutoCorrected)
callback.onGetSuggestedWords(SuggestedWords(suggestionsList, suggestionResults.mRawSuggestions,
typedWordInfo, isTypedWordValid, hasAutoCorrection, false, inputStyle, sequenceNumber))
return SuggestedWords(suggestionsList, suggestionResults.mRawSuggestions,
typedWordInfo, isTypedWordValid, hasAutoCorrection, false, inputStyle, sequenceNumber)
}
// returns [allowsToBeAutoCorrected, hasAutoCorrection]
@ -252,9 +253,8 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
wordComposer: WordComposer,
ngramContext: NgramContext, keyboard: Keyboard,
settingsValuesForSuggestion: SettingsValuesForSuggestion,
inputStyle: Int, sequenceNumber: Int,
callback: OnGetSuggestedWordsCallback
) {
inputStyle: Int, sequenceNumber: Int
): SuggestedWords {
val suggestionResults = mDictionaryFacilitator.getSuggestionResults(
wordComposer.composedDataSnapshot, ngramContext, keyboard,
settingsValuesForSuggestion, SESSION_ID_GESTURE, inputStyle
@ -312,10 +312,8 @@ class Suggest(private val mDictionaryFacilitator: DictionaryFacilitator) {
} else {
suggestionsContainer
}
callback.onGetSuggestedWords(
SuggestedWords(suggestionsList, suggestionResults.mRawSuggestions, pseudoTypedWordInfo, true,
return SuggestedWords(suggestionsList, suggestionResults.mRawSuggestions, pseudoTypedWordInfo, true,
false, false, inputStyle, sequenceNumber)
)
}
/** reduces score of the first suggestion if next one is close and has more than a single letter */

View file

@ -44,6 +44,7 @@ import helium314.keyboard.latin.common.StringUtils;
import helium314.keyboard.latin.common.StringUtilsKt;
import helium314.keyboard.latin.common.SuggestionSpanUtilsKt;
import helium314.keyboard.latin.define.DebugFlags;
import helium314.keyboard.latin.settings.Settings;
import helium314.keyboard.latin.settings.SettingsValues;
import helium314.keyboard.latin.settings.SpacingAndPunctuations;
import helium314.keyboard.latin.suggestions.SuggestionStripViewAccessor;
@ -71,7 +72,7 @@ public final class InputLogic {
final LatinIME mLatinIME;
private final SuggestionStripViewAccessor mSuggestionStripViewAccessor;
@NonNull private InputLogicHandler mInputLogicHandler;
@NonNull private final InputLogicHandler mInputLogicHandler;
// TODO : make all these fields private as soon as possible.
// Current space state of the input method. This can be any of the above constants.
@ -119,7 +120,7 @@ public final class InputLogic {
mSuggestionStripViewAccessor = suggestionStripViewAccessor;
mWordComposer = new WordComposer();
mConnection = new RichInputConnection(latinIME);
mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
mInputLogicHandler = new InputLogicHandler(mLatinIME.mHandler, this);
mSuggest = new Suggest(dictionaryFacilitator);
mDictionaryFacilitator = dictionaryFacilitator;
}
@ -153,11 +154,7 @@ public final class InputLogic {
// editorInfo.initialSelStart is not the actual cursor position, so we try using some heuristics to find the correct position.
mConnection.tryFixIncorrectCursorPosition();
cancelDoubleSpacePeriodCountdown();
if (InputLogicHandler.NULL_HANDLER == mInputLogicHandler) {
mInputLogicHandler = new InputLogicHandler(mLatinIME, this);
} else {
mInputLogicHandler.reset();
}
mConnection.requestCursorUpdates(true, true);
}
@ -200,17 +197,6 @@ public final class InputLogic {
mInputLogicHandler.reset();
}
// Normally this class just gets out of scope after the process ends, but in unit tests, we
// create several instances of LatinIME in the same process, which results in several
// instances of InputLogic. This cleans up the associated handler so that tests don't leak
// handlers.
public void recycle() {
final InputLogicHandler inputLogicHandler = mInputLogicHandler;
mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
inputLogicHandler.destroy();
mDictionaryFacilitator.closeDictionaries();
}
/**
* React to a string input.
* <p>
@ -1680,15 +1666,14 @@ public final class InputLogic {
}
final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>("Suggest");
mInputLogicHandler.getSuggestedWords(inputStyle, SuggestedWords.NOT_A_SEQUENCE_NUMBER,
mInputLogicHandler.getSuggestedWords(() -> getSuggestedWords(
inputStyle, SuggestedWords.NOT_A_SEQUENCE_NUMBER,
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);
typedWordString, "", SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
Dictionary.DICTIONARY_USER_TYPED, SuggestedWordInfo.NOT_AN_INDEX, 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.
@ -1698,8 +1683,7 @@ public final class InputLogic {
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);
@ -1809,8 +1793,8 @@ public final class InputLogic {
// If there weren't any suggestion spans on this word, suggestions#size() will be 1
// if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we
// have no useful suggestions, so we will try to compute some for it instead.
mInputLogicHandler.getSuggestedWords(Suggest.SESSION_ID_TYPING,
SuggestedWords.NOT_A_SEQUENCE_NUMBER, this::doShowSuggestionsAndClearAutoCorrectionIndicator);
mInputLogicHandler.getSuggestedWords(() -> getSuggestedWords(Suggest.SESSION_ID_TYPING,
SuggestedWords.NOT_A_SEQUENCE_NUMBER, this::doShowSuggestionsAndClearAutoCorrectionIndicator));
} else {
// We found suggestion spans in the word. We'll create the SuggestedWords out of
// them, and make willAutoCorrect false. We make typedWordValid false, because the
@ -2434,12 +2418,17 @@ public final class InputLogic {
return true;
}
public void getSuggestedWords(final SettingsValues settingsValues,
final Keyboard keyboard, final int keyboardShiftMode, final int inputStyle,
final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
// we used to provide keyboard, settingsValues and keyboardShiftMode, but every time read it from current instance anyway
public void getSuggestedWords(final int inputStyle, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) {
final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard();
if (keyboard == null) {
callback.onGetSuggestedWords(SuggestedWords.getEmptyInstance());
return;
}
final SettingsValues settingsValues = Settings.getValues();
mWordComposer.adviseCapitalizedModeBeforeFetchingSuggestions(
getActualCapsMode(settingsValues, keyboardShiftMode));
mSuggest.getSuggestedWords(mWordComposer,
getActualCapsMode(settingsValues, KeyboardSwitcher.getInstance().getKeyboardShiftMode()));
final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer,
getNgramContextFromNthPreviousWordForSuggestion(
settingsValues.mSpacingAndPunctuations,
// Get the word on which we should search the bigrams. If we are composing
@ -2449,7 +2438,8 @@ public final class InputLogic {
keyboard,
settingsValues.mSettingsValuesForSuggestion,
settingsValues.mAutoCorrectEnabled,
inputStyle, sequenceNumber, callback);
inputStyle, sequenceNumber);
callback.onGetSuggestedWords(suggestedWords);
}
/**

View file

@ -11,7 +11,6 @@ import android.os.HandlerThread;
import android.os.Message;
import helium314.keyboard.latin.LatinIME;
import helium314.keyboard.latin.Suggest.OnGetSuggestedWordsCallback;
import helium314.keyboard.latin.SuggestedWords;
import helium314.keyboard.latin.common.InputPointers;
@ -20,48 +19,19 @@ import helium314.keyboard.latin.common.InputPointers;
*/
class InputLogicHandler implements Handler.Callback {
final Handler mNonUIThreadHandler;
// TODO: remove this reference.
final LatinIME mLatinIME;
final LatinIME.UIHandler mLatinIMEHandler;
final InputLogic mInputLogic;
private final Object mLock = new Object();
private boolean mInBatchInput; // synchronized using {@link #mLock}.
private static final int MSG_GET_SUGGESTED_WORDS = 1;
// A handler that never does anything. This is used for cases where events come before anything
// is initialized, though probably only the monkey can actually do this.
public static final InputLogicHandler NULL_HANDLER = new InputLogicHandler() {
@Override
public void reset() {}
@Override
public boolean handleMessage(final Message msg) { return true; }
@Override
public void onStartBatchInput() {}
@Override
public void onUpdateBatchInput(final InputPointers batchPointers,
final int sequenceNumber) {}
@Override
public void onCancelBatchInput() {}
@Override
public void updateTailBatchInput(final InputPointers batchPointers,
final int sequenceNumber) {}
@Override
public void getSuggestedWords(final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {}
};
InputLogicHandler() {
mNonUIThreadHandler = null;
mLatinIME = null;
mInputLogic = null;
}
public InputLogicHandler(final LatinIME latinIME, final InputLogic inputLogic) {
public InputLogicHandler(final LatinIME.UIHandler latinIMEHandler, final InputLogic inputLogic) {
final HandlerThread handlerThread = new HandlerThread(
InputLogicHandler.class.getSimpleName());
handlerThread.start();
mNonUIThreadHandler = new Handler(handlerThread.getLooper(), this);
mLatinIME = latinIME;
mLatinIMEHandler = latinIMEHandler;
mInputLogic = inputLogic;
}
@ -69,12 +39,6 @@ class InputLogicHandler implements Handler.Callback {
mNonUIThreadHandler.removeCallbacksAndMessages(null);
}
// In unit tests, we create several instances of LatinIME, which results in several instances
// of InputLogicHandler. To avoid these handlers lingering, we call this.
public void destroy() {
mNonUIThreadHandler.getLooper().quitSafely();
}
/**
* Handle a message.
* @see android.os.Handler.Callback#handleMessage(android.os.Message)
@ -83,7 +47,7 @@ class InputLogicHandler implements Handler.Callback {
@Override
public boolean handleMessage(final Message msg) {
if (msg.what == MSG_GET_SUGGESTED_WORDS)
mLatinIME.getSuggestedWords(msg.arg1, msg.arg2, (OnGetSuggestedWordsCallback) msg.obj);
((Runnable)msg.obj).run();
return true;
}
@ -118,13 +82,14 @@ class InputLogicHandler implements Handler.Callback {
return;
}
mInputLogic.mWordComposer.setBatchInputPointers(batchPointers);
final OnGetSuggestedWordsCallback callback = suggestedWords -> showGestureSuggestionsWithPreviewVisuals(suggestedWords, isTailBatchInput);
getSuggestedWords(isTailBatchInput ? SuggestedWords.INPUT_STYLE_TAIL_BATCH
: SuggestedWords.INPUT_STYLE_UPDATE_BATCH, sequenceNumber, callback);
getSuggestedWords(() -> mInputLogic.getSuggestedWords(
isTailBatchInput ? SuggestedWords.INPUT_STYLE_TAIL_BATCH : SuggestedWords.INPUT_STYLE_UPDATE_BATCH, sequenceNumber,
suggestedWords -> showGestureSuggestionsWithPreviewVisuals(suggestedWords, isTailBatchInput))
);
}
}
void showGestureSuggestionsWithPreviewVisuals(final SuggestedWords suggestedWordsForBatchInput,
private void showGestureSuggestionsWithPreviewVisuals(final SuggestedWords suggestedWordsForBatchInput,
final boolean isTailBatchInput) {
final SuggestedWords suggestedWordsToShowSuggestions;
// We're now inside the callback. This always runs on the Non-UI thread,
@ -138,13 +103,12 @@ class InputLogicHandler implements Handler.Callback {
} else {
suggestedWordsToShowSuggestions = suggestedWordsForBatchInput;
}
mLatinIME.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWordsToShowSuggestions,
isTailBatchInput /* dismissGestureFloatingPreviewText */);
mLatinIMEHandler.showGesturePreviewAndSuggestionStrip(suggestedWordsToShowSuggestions, isTailBatchInput);
if (isTailBatchInput) {
mInBatchInput = false;
// The following call schedules onEndBatchInputInternal
// to be called on the UI thread.
mLatinIME.mHandler.showTailBatchInputResult(suggestedWordsToShowSuggestions);
mLatinIMEHandler.showTailBatchInputResult(suggestedWordsToShowSuggestions);
}
}
@ -193,9 +157,7 @@ class InputLogicHandler implements Handler.Callback {
updateBatchInput(batchPointers, sequenceNumber, true);
}
public void getSuggestedWords(final int inputStyle, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
mNonUIThreadHandler.obtainMessage(
MSG_GET_SUGGESTED_WORDS, inputStyle, sequenceNumber, callback).sendToTarget();
public void getSuggestedWords(final Runnable callback) {
mNonUIThreadHandler.obtainMessage(MSG_GET_SUGGESTED_WORDS, callback).sendToTarget();
}
}