aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/inputlogic
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin/inputlogic')
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java329
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java57
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java40
3 files changed, 275 insertions, 151 deletions
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index fdab7f25f..a67f46108 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -39,21 +39,20 @@ import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.keyboard.TextDecorator;
import com.android.inputmethod.keyboard.TextDecoratorUiOperator;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryFacilitator;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LastComposedWord;
import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.NgramContext;
import com.android.inputmethod.latin.RichInputConnection;
import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.define.DebugFlags;
-import com.android.inputmethod.latin.define.ProductionFlags;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
@@ -61,6 +60,7 @@ import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
+import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
@@ -68,6 +68,8 @@ import java.util.ArrayList;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
+
/**
* This class manages the input logic.
*/
@@ -75,7 +77,7 @@ public final class InputLogic {
private static final String TAG = InputLogic.class.getSimpleName();
// TODO : Remove this member when we can.
- private final LatinIME mLatinIME;
+ final LatinIME mLatinIME;
private final SuggestionStripViewAccessor mSuggestionStripViewAccessor;
// Never null.
@@ -85,7 +87,7 @@ public final class InputLogic {
// Current space state of the input method. This can be any of the above constants.
private int mSpaceState;
// Never null
- public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
+ public SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance();
public final Suggest mSuggest;
private final DictionaryFacilitator mDictionaryFacilitator;
@@ -144,13 +146,20 @@ public final class InputLogic {
*/
public void startInput(final String combiningSpec, final SettingsValues settingsValues) {
mEnteredText = null;
+ if (!mWordComposer.getTypedWord().isEmpty()) {
+ // For messaging apps that offer send button, the IME does not get the opportunity
+ // to capture the last word. This block should capture those uncommitted words.
+ // The timestamp at which it is captured is not accurate but close enough.
+ StatsUtils.onWordCommitUserTyped(
+ mWordComposer.getTypedWord(), mWordComposer.isBatchMode());
+ }
mWordComposer.restartCombining(combiningSpec);
resetComposingState(true /* alsoResetLastComposedWord */);
mDeleteCount = 0;
mSpaceState = SpaceState.NONE;
mRecapitalizeStatus.disable(); // Do not perform recapitalize until the cursor is moved once
mCurrentlyPressedHardwareKeys.clear();
- mSuggestedWords = SuggestedWords.EMPTY;
+ mSuggestedWords = SuggestedWords.getEmptyInstance();
// In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying
// so we try using some heuristics to find out about these and fix them.
mConnection.tryFixLyingCursorPosition();
@@ -161,14 +170,11 @@ public final class InputLogic {
mInputLogicHandler.reset();
}
- if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK) {
- // AcceptTypedWord feature relies on CursorAnchorInfo.
- if (settingsValues.mShouldShowUiToAcceptTypedWord) {
- mConnection.requestCursorUpdates(true /* enableMonitor */,
- true /* requestImmediateCallback */);
- }
- mTextDecorator.reset();
+ if (settingsValues.mShouldShowLxxSuggestionUi) {
+ mConnection.requestCursorUpdates(true /* enableMonitor */,
+ true /* requestImmediateCallback */);
}
+ mTextDecorator.reset();
}
/**
@@ -204,6 +210,8 @@ public final class InputLogic {
public void finishInput() {
if (mWordComposer.isComposingWord()) {
mConnection.finishComposingText();
+ StatsUtils.onWordCommitUserTyped(
+ mWordComposer.getTypedWord(), mWordComposer.isBatchMode());
}
resetComposingState(true /* alsoResetLastComposedWord */);
mInputLogicHandler.reset();
@@ -250,6 +258,7 @@ public final class InputLogic {
promotePhantomSpace(settingsValues);
}
mConnection.commitText(text, 1);
+ StatsUtils.onWordCommitUserTyped(mEnteredText, mWordComposer.isBatchMode());
mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.NONE;
@@ -298,6 +307,7 @@ public final class InputLogic {
currentKeyboardScriptId, handler);
}
+ mDictionaryFacilitator.switchMostProbableLanguage(suggestionInfo.mSourceDict.mLocale);
final Event event = Event.createSuggestionPickedEvent(suggestionInfo);
final InputTransaction inputTransaction = new InputTransaction(settingsValues,
event, SystemClock.uptimeMillis(), mSpaceState, keyboardShiftState);
@@ -321,7 +331,7 @@ public final class InputLogic {
// however need to reset the suggestion strip right away, because we know we can't take
// the risk of calling commitCompletion twice because we don't know how the app will react.
if (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_APP_DEFINED)) {
- mSuggestedWords = SuggestedWords.EMPTY;
+ mSuggestedWords = SuggestedWords.getEmptyInstance();
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
resetComposingState(true /* alsoResetLastComposedWord */);
@@ -341,12 +351,17 @@ public final class InputLogic {
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
if (shouldShowAddToDictionaryHint) {
- mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
+ mSuggestionStripViewAccessor.suggestAddingToDictionary(suggestion,
+ true /* isFromSuggestionStrip */);
} else {
// If we're not showing the "Touch again to save", then update the suggestion strip.
// That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
}
+
+ StatsUtils.onPickSuggestionManually(mSuggestedWords, suggestionInfo);
+ StatsUtils.onWordCommitSuggestionPickedManually(
+ suggestionInfo.mWord, mWordComposer.isBatchMode());
return inputTransaction;
}
@@ -422,8 +437,7 @@ public final class InputLogic {
// removed.
mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
// We moved the cursor. If we are touching a word, we need to resume suggestion.
- mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */,
- true /* shouldDelay */);
+ mLatinIME.mHandler.postResumeSuggestions(true /* shouldDelay */);
// Stop the last recapitalization, if started.
mRecapitalizeStatus.stop();
return true;
@@ -442,8 +456,8 @@ public final class InputLogic {
* {@link com.android.inputmethod.keyboard.KeyboardSwitcher#getKeyboardShiftMode()}
* @return the complete transaction object
*/
- public InputTransaction onCodeInput(final SettingsValues settingsValues, final Event event,
- final int keyboardShiftMode,
+ public InputTransaction onCodeInput(final SettingsValues settingsValues,
+ @Nonnull final Event event, final int keyboardShiftMode,
// TODO: remove these arguments
final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {
final Event processedEvent = mWordComposer.processEvent(event);
@@ -495,7 +509,7 @@ public final class InputLogic {
final KeyboardSwitcher keyboardSwitcher, final LatinIME.UIHandler handler) {
mInputLogicHandler.onStartBatchInput();
handler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
+ SuggestedWords.getEmptyInstance(), false /* dismissGestureFloatingPreviewText */);
handler.cancelUpdateSuggestionStrip();
++mAutoCommitSequenceNumber;
mConnection.beginBatchEdit();
@@ -572,6 +586,7 @@ public final class InputLogic {
batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
promotePhantomSpace(settingsValues);
mConnection.commitText(commitParts[0], 0);
+ StatsUtils.onWordCommitUserTyped(commitParts[0], mWordComposer.isBatchMode());
mSpaceState = SpaceState.PHANTOM;
keyboardSwitcher.requestUpdatingShiftState(
getCurrentAutoCapsState(settingsValues), getCurrentRecapitalizeState());
@@ -593,23 +608,26 @@ public final class InputLogic {
public void onCancelBatchInput(final LatinIME.UIHandler handler) {
mInputLogicHandler.onCancelBatchInput();
handler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
+ SuggestedWords.getEmptyInstance(), true /* dismissGestureFloatingPreviewText */);
}
// TODO: on the long term, this method should become private, but it will be difficult.
// Especially, how do we deal with InputMethodService.onDisplayCompletions?
- public void setSuggestedWords(final SuggestedWords suggestedWords,
- final SettingsValues settingsValues, final LatinIME.UIHandler handler) {
- if (SuggestedWords.EMPTY != suggestedWords) {
- final String autoCorrection;
+ public void setSuggestedWords(final SuggestedWords suggestedWords) {
+ if (!suggestedWords.isEmpty()) {
+ final SuggestedWordInfo suggestedWordInfo;
if (suggestedWords.mWillAutoCorrect) {
- autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
+ suggestedWordInfo = suggestedWords.getInfo(SuggestedWords.INDEX_OF_AUTO_CORRECTION);
} else {
// We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)
// because it may differ from mWordComposer.mTypedWord.
- autoCorrection = suggestedWords.mTypedWord;
+ suggestedWordInfo = new SuggestedWordInfo(suggestedWords.mTypedWord,
+ SuggestedWordInfo.MAX_SCORE,
+ SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */);
}
- mWordComposer.setAutoCorrection(autoCorrection);
+ mWordComposer.setAutoCorrection(suggestedWordInfo);
}
mSuggestedWords = suggestedWords;
final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
@@ -683,7 +701,7 @@ public final class InputLogic {
break;
case Constants.CODE_CAPSLOCK:
// Note: Changing keyboard to shift lock state is handled in
- // {@link KeyboardSwitcher#onCodeInput(int)}.
+ // {@link KeyboardSwitcher#onEvent(Event)}.
break;
case Constants.CODE_SYMBOL_SHIFT:
// Note: Calling back to the keyboard on the symbol Shift key is handled in
@@ -711,11 +729,11 @@ public final class InputLogic {
break;
case Constants.CODE_EMOJI:
// Note: Switching emoji keyboard is being handled in
- // {@link KeyboardState#onCodeInput(int,int)}.
+ // {@link KeyboardState#onEvent(Event,int)}.
break;
case Constants.CODE_ALPHA_FROM_EMOJI:
// Note: Switching back from Emoji keyboard to the main keyboard is being
- // handled in {@link KeyboardState#onCodeInput(int,int)}.
+ // handled in {@link KeyboardState#onEvent(Event,int)}.
break;
case Constants.CODE_SHIFT_ENTER:
// TODO: remove this object
@@ -1043,8 +1061,10 @@ public final class InputLogic {
if (!TextUtils.isEmpty(rejectedSuggestion)) {
mDictionaryFacilitator.removeWordFromPersonalizedDicts(rejectedSuggestion);
}
+ StatsUtils.onBackspaceWordDelete(rejectedSuggestion.length());
} else {
mWordComposer.applyProcessedEvent(event);
+ StatsUtils.onBackspacePressed(1);
}
if (mWordComposer.isComposingWord()) {
setComposingTextInternal(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
@@ -1054,7 +1074,10 @@ public final class InputLogic {
inputTransaction.setRequiresUpdateSuggestions();
} else {
if (mLastComposedWord.canRevertCommit()) {
+ final String lastComposedWord = mLastComposedWord.mTypedWord;
revertCommit(inputTransaction, inputTransaction.mSettingsValues);
+ StatsUtils.onRevertAutoCorrect();
+ StatsUtils.onWordCommitUserTyped(lastComposedWord, mWordComposer.isBatchMode());
return;
}
if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) {
@@ -1062,6 +1085,7 @@ public final class InputLogic {
// This is triggered on backspace after a key that inputs multiple characters,
// like the smiley key or the .com key.
mConnection.deleteSurroundingText(mEnteredText.length(), 0);
+ StatsUtils.onDeleteMultiCharInput(mEnteredText.length());
mEnteredText = null;
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
// In addition we know that spaceState is false, and that we should not be
@@ -1070,16 +1094,19 @@ public final class InputLogic {
}
if (SpaceState.DOUBLE == inputTransaction.mSpaceState) {
cancelDoubleSpacePeriodCountdown();
- if (mConnection.revertDoubleSpacePeriod()) {
+ if (mConnection.revertDoubleSpacePeriod(
+ inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
// No need to reset mSpaceState, it has already be done (that's why we
// receive it as a parameter)
inputTransaction.setRequiresUpdateSuggestions();
mWordComposer.setCapitalizedModeAtStartComposingTime(
WordComposer.CAPS_MODE_OFF);
+ StatsUtils.onRevertDoubleSpacePeriod();
return;
}
} else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
if (mConnection.revertSwapPunctuation()) {
+ StatsUtils.onRevertSwapPunctuation();
// Likewise
return;
}
@@ -1094,25 +1121,31 @@ public final class InputLogic {
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
mConnection.getExpectedSelectionEnd());
mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ StatsUtils.onBackspaceSelectedText(numCharsDeleted);
} else {
// There is no selection, just delete one character.
- if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) {
- // This should never happen.
- Log.e(TAG, "Backspace when we don't know the selection position");
- }
- if (inputTransaction.mSettingsValues.isBeforeJellyBean() ||
- inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()) {
- // There are two possible reasons to send a key event: either the field has
+ if (inputTransaction.mSettingsValues.isBeforeJellyBean()
+ || inputTransaction.mSettingsValues.mInputAttributes.isTypeNull()
+ || Constants.NOT_A_CURSOR_POSITION
+ == mConnection.getExpectedSelectionEnd()) {
+ // There are three possible reasons to send a key event: either the field has
// type TYPE_NULL, in which case the keyboard should send events, or we are
- // running in backward compatibility mode. Before Jelly bean, the keyboard
- // would simulate a hardware keyboard event on pressing enter or delete. This
- // is bad for many reasons (there are race conditions with commits) but some
- // applications are relying on this behavior so we continue to support it for
- // older apps, so we retain this behavior if the app has target SDK < JellyBean.
+ // running in backward compatibility mode, or we don't know the cursor position.
+ // Before Jelly bean, the keyboard would simulate a hardware keyboard event on
+ // pressing enter or delete. This is bad for many reasons (there are race
+ // conditions with commits) but some applications are relying on this behavior
+ // so we continue to support it for older apps, so we retain this behavior if
+ // the app has target SDK < JellyBean.
+ // As for the case where we don't know the cursor position, it can happen
+ // because of bugs in the framework. But the framework should know, so the next
+ // best thing is to leave it to whatever it thinks is best.
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ int totalDeletedLength = 1;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL);
+ totalDeletedLength++;
}
+ StatsUtils.onBackspacePressed(totalDeletedLength);
} else {
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursor == Constants.NOT_A_CODE) {
@@ -1123,11 +1156,13 @@ public final class InputLogic {
// catch it and have their broken interface react. If you need the keyboard
// to do this, you're doing it wrong -- please fix your app.
mConnection.deleteSurroundingText(1, 0);
+ // TODO: Add a new StatsUtils method onBackspaceWhenNoText()
return;
}
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDelete, 0);
+ int totalDeletedLength = lengthToDelete;
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
@@ -1135,8 +1170,10 @@ public final class InputLogic {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
codePointBeforeCursorToDeleteAgain) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
+ totalDeletedLength += lengthToDeleteAgain;
}
}
+ StatsUtils.onBackspacePressed(totalDeletedLength);
}
}
if (inputTransaction.mSettingsValues
@@ -1146,7 +1183,7 @@ public final class InputLogic {
&& !mConnection.isCursorFollowedByWordCharacter(
inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
- true /* shouldIncludeResumedWordInSuggestions */, currentKeyboardScriptId);
+ currentKeyboardScriptId);
}
}
}
@@ -1253,7 +1290,9 @@ public final class InputLogic {
if (null == lastTwo) return false;
final int length = lastTwo.length();
if (length < 2) return false;
- if (lastTwo.charAt(length - 1) != Constants.CODE_SPACE) return false;
+ if (lastTwo.charAt(length - 1) != Constants.CODE_SPACE) {
+ return false;
+ }
// We know there is a space in pos -1, and we have at least two chars. If we have only two
// chars, isSurrogatePairs can't return true as charAt(1) is a space, so this is fine.
final int firstCodePoint =
@@ -1336,7 +1375,7 @@ public final class InputLogic {
}
private void performAdditionToUserHistoryDictionary(final SettingsValues settingsValues,
- final String suggestion, final PrevWordsInfo prevWordsInfo) {
+ final String suggestion, @Nonnull final NgramContext ngramContext) {
// If correction is not enabled, we don't add words to the user history dictionary.
// That's to avoid unintended additions in some sensitive fields, or fields that
// expect to receive non-words.
@@ -1348,7 +1387,7 @@ public final class InputLogic {
final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
System.currentTimeMillis());
mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized,
- prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
+ ngramContext, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
}
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues,
@@ -1360,7 +1399,7 @@ public final class InputLogic {
+ "requested!");
}
// Clear the suggestions strip.
- mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.EMPTY);
+ mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.getEmptyInstance());
return;
}
@@ -1400,12 +1439,10 @@ public final class InputLogic {
* do nothing.
*
* @param settingsValues the current values of the settings.
- * @param shouldIncludeResumedWordInSuggestions whether to include the word on which we resume
* suggestions in the suggestion list.
*/
// TODO: make this private.
public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues,
- final boolean shouldIncludeResumedWordInSuggestions,
// TODO: remove this argument, put it into settingsValues
final int currentKeyboardScriptId) {
// HACK: We may want to special-case some apps that exhibit bad behavior in case of
@@ -1453,13 +1490,11 @@ public final class InputLogic {
if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return;
final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
final String typedWord = range.mWord.toString();
- if (shouldIncludeResumedWordInSuggestions) {
- suggestions.add(new SuggestedWordInfo(typedWord,
- SuggestedWords.MAX_SUGGESTIONS + 1,
- SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
- SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
- SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
- }
+ suggestions.add(new SuggestedWordInfo(typedWord,
+ SuggestedWords.MAX_SUGGESTIONS + 1,
+ SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
+ SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
+ SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */));
if (!isResumableWord(settingsValues, typedWord)) {
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
return;
@@ -1479,12 +1514,6 @@ public final class InputLogic {
}
}
final int[] codePoints = StringUtils.toCodePointArray(typedWord);
- // We want the previous word for suggestion. If we have chars in the word
- // before the cursor, then we want the word before that, hence 2; otherwise,
- // we want the word immediately before the cursor, hence 1.
- final PrevWordsInfo prevWordsInfo = getPrevWordsInfoFromNthPreviousWordForSuggestion(
- settingsValues.mSpacingAndPunctuations,
- 0 == numberOfCharsInWordBeforeCursor ? 1 : 2);
mWordComposer.setComposingWord(codePoints,
mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
mWordComposer.setCursorPositionWithinWord(
@@ -1492,33 +1521,15 @@ public final class InputLogic {
mConnection.maybeMoveTheCursorAroundAndRestoreToWorkaroundABug();
mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor());
- if (suggestions.size() <= (shouldIncludeResumedWordInSuggestions ? 1 : 0)) {
+ if (suggestions.size() <= 1) {
// 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, new OnGetSuggestedWordsCallback() {
@Override
- public void onGetSuggestedWords(
- final SuggestedWords suggestedWordsIncludingTypedWord) {
- final SuggestedWords suggestedWords;
- if (suggestedWordsIncludingTypedWord.size() > 1
- && !shouldIncludeResumedWordInSuggestions) {
- // We were able to compute new suggestions for this word.
- // Remove the typed word, since we don't want to display it in this
- // case. The #getSuggestedWordsExcludingTypedWordForRecorrection()
- // method sets willAutoCorrect to false.
- suggestedWords = suggestedWordsIncludingTypedWord
- .getSuggestedWordsExcludingTypedWordForRecorrection();
- } else {
- // No saved suggestions, and we were unable to compute any good one
- // either. Rather than displaying an empty suggestion strip, we'll
- // display the original word alone in the middle.
- // Since there is only one word, willAutoCorrect is false.
- suggestedWords = suggestedWordsIncludingTypedWord;
- }
- mIsAutoCorrectionIndicatorOn = false;
- mLatinIME.mHandler.showSuggestionStrip(suggestedWords);
+ public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
+ doShowSuggestionsAndClearAutoCorrectionIndicator(suggestedWords);
}});
} else {
// We found suggestion spans in the word. We'll create the SuggestedWords out of
@@ -1529,11 +1540,15 @@ public final class InputLogic {
null /* rawSuggestions */, typedWord, false /* typedWordValid */,
false /* willAutoCorrect */, false /* isObsoleteSuggestions */,
SuggestedWords.INPUT_STYLE_RECORRECTION, SuggestedWords.NOT_A_SEQUENCE_NUMBER);
- mIsAutoCorrectionIndicatorOn = false;
- mLatinIME.mHandler.showSuggestionStrip(suggestedWords);
+ doShowSuggestionsAndClearAutoCorrectionIndicator(suggestedWords);
}
}
+ void doShowSuggestionsAndClearAutoCorrectionIndicator(final SuggestedWords suggestedWords) {
+ mIsAutoCorrectionIndicatorOn = false;
+ mLatinIME.mHandler.showSuggestionStrip(suggestedWords);
+ }
+
/**
* Reverts a previous commit with auto-correction.
*
@@ -1551,6 +1566,10 @@ public final class InputLogic {
final String committedWordString = committedWord.toString();
final int cancelLength = committedWord.length();
final String separatorString = mLastComposedWord.mSeparatorString;
+ // If our separator is a space, we won't actually commit it,
+ // but set the space state to PHANTOM so that a space will be inserted
+ // on the next keypress
+ final boolean usePhantomSpace = separatorString.equals(Constants.STRING_SPACE);
// We want java chars, not codepoints for the following.
final int separatorLength = separatorString.length();
// TODO: should we check our saved separator against the actual contents of the text view?
@@ -1571,7 +1590,8 @@ public final class InputLogic {
if (!TextUtils.isEmpty(committedWord)) {
mDictionaryFacilitator.removeWordFromPersonalizedDicts(committedWordString);
}
- final String stringToCommit = originallyTypedWord + separatorString;
+ final String stringToCommit = originallyTypedWord +
+ (usePhantomSpace ? "" : separatorString);
final SpannableString textToCommit = new SpannableString(stringToCommit);
if (committedWord instanceof SpannableString) {
final SpannableString committedWordWithSuggestionSpans = (SpannableString)committedWord;
@@ -1604,8 +1624,10 @@ public final class InputLogic {
}
}
// Add the suggestion list to the list of suggestions.
- textToCommit.setSpan(new SuggestionSpan(inputTransaction.mSettingsValues.mLocale,
- suggestions.toArray(new String[suggestions.size()]), 0 /* flags */),
+ textToCommit.setSpan(new SuggestionSpan(mLatinIME /* context */,
+ inputTransaction.mSettingsValues.mLocale,
+ suggestions.toArray(new String[suggestions.size()]), 0 /* flags */,
+ null /* notificationTargetClass */),
0 /* start */, lastCharIndex /* end */, 0 /* flags */);
}
@@ -1623,6 +1645,9 @@ public final class InputLogic {
} else {
mConnection.commitText(textToCommit, 1);
}
+ if (usePhantomSpace) {
+ mSpaceState = SpaceState.PHANTOM;
+ }
} else {
// For languages without spaces, we revert the typed string but the cursor is flush
// with the typed word, so we need to resume suggestions right away.
@@ -1650,7 +1675,8 @@ public final class InputLogic {
mConnection.getExpectedSelectionStart(),
mConnection.getExpectedSelectionEnd());
}
- mSuggestionStripViewAccessor.showAddToDictionaryHint(originallyTypedWordString);
+ mSuggestionStripViewAccessor.suggestAddingToDictionary(originallyTypedWordString,
+ false /* isFromSuggestionStrip */);
} else {
// We have a separator between the word and the cursor: we should show predictions.
inputTransaction.setRequiresUpdateSuggestions();
@@ -1720,26 +1746,26 @@ public final class InputLogic {
}
/**
- * Get information fo previous words from the nth previous word before the cursor as context
+ * Get n-gram context from the nth previous word before the cursor as context
* for the suggestion process.
* @param spacingAndPunctuations the current spacing and punctuations settings.
* @param nthPreviousWord reverse index of the word to get (1-indexed)
* @return the information of previous words
*/
// TODO: Make this private
- public PrevWordsInfo getPrevWordsInfoFromNthPreviousWordForSuggestion(
+ public NgramContext getNgramContextFromNthPreviousWordForSuggestion(
final SpacingAndPunctuations spacingAndPunctuations, final int nthPreviousWord) {
if (spacingAndPunctuations.mCurrentLanguageHasSpaces) {
// If we are typing in a language with spaces we can just look up the previous
// word information from textview.
- return mConnection.getPrevWordsInfoFromNthPreviousWord(
+ return mConnection.getNgramContextFromNthPreviousWord(
spacingAndPunctuations, nthPreviousWord);
- } else {
- return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ?
- PrevWordsInfo.BEGINNING_OF_SENTENCE :
- new PrevWordsInfo(new PrevWordsInfo.WordInfo(
- mLastComposedWord.mCommittedWord.toString()));
}
+ if (LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord) {
+ return NgramContext.BEGINNING_OF_SENTENCE;
+ }
+ return new NgramContext(new NgramContext.WordInfo(
+ mLastComposedWord.mCommittedWord.toString()));
}
/**
@@ -1792,9 +1818,8 @@ public final class InputLogic {
// If no code point, #getCodePointBeforeCursor returns NOT_A_CODE_POINT.
if (Constants.CODE_PERIOD == codePointBeforeCursor) {
return text.substring(1);
- } else {
- return text;
}
+ return text;
}
/**
@@ -1850,11 +1875,10 @@ public final class InputLogic {
* @param previousSuggestedWords The previously suggested words.
* @return Obsolete suggestions with the newly typed word.
*/
- private SuggestedWords retrieveOlderSuggestions(final String typedWord,
+ static SuggestedWords retrieveOlderSuggestions(final String typedWord,
final SuggestedWords previousSuggestedWords) {
- final SuggestedWords oldSuggestedWords =
- previousSuggestedWords.isPunctuationSuggestions() ? SuggestedWords.EMPTY
- : previousSuggestedWords;
+ final SuggestedWords oldSuggestedWords = previousSuggestedWords.isPunctuationSuggestions()
+ ? SuggestedWords.getEmptyInstance() : previousSuggestedWords;
final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords);
return new SuggestedWords(typedWordAndPreviousSuggestions, null /* rawSuggestions */,
@@ -1976,6 +2000,8 @@ public final class InputLogic {
final int indexOfLastSpace = batchInputText.lastIndexOf(Constants.CODE_SPACE) + 1;
if (0 != indexOfLastSpace) {
mConnection.commitText(batchInputText.substring(0, indexOfLastSpace), 1);
+ StatsUtils.onWordCommitUserTyped(
+ batchInputText.substring(0, indexOfLastSpace), mWordComposer.isBatchMode());
final SuggestedWords suggestedWordsForLastWordOfPhraseGesture =
suggestedWords.getSuggestedWordsForLastWordOfPhraseGesture();
mLatinIME.showSuggestionStrip(suggestedWordsForLastWordOfPhraseGesture);
@@ -2014,8 +2040,10 @@ public final class InputLogic {
if (!mWordComposer.isComposingWord()) return;
final String typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
+ final boolean isBatchMode = mWordComposer.isBatchMode();
commitChosenWord(settingsValues, typedWord,
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString);
+ StatsUtils.onWordCommitUserTyped(typedWord, isBatchMode);
}
}
@@ -2052,18 +2080,23 @@ public final class InputLogic {
// INPUT_STYLE_TYPING.
performUpdateSuggestionStripSync(settingsValues, SuggestedWords.INPUT_STYLE_TYPING);
}
- final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
+ final SuggestedWordInfo autoCorrectionOrNull = mWordComposer.getAutoCorrectionOrNull();
final String typedWord = mWordComposer.getTypedWord();
- final String autoCorrection = (typedAutoCorrection != null)
- ? typedAutoCorrection : typedWord;
- if (autoCorrection != null) {
+ final String stringToCommit = (autoCorrectionOrNull != null)
+ ? autoCorrectionOrNull.mWord : typedWord;
+ if (stringToCommit != null) {
if (TextUtils.isEmpty(typedWord)) {
throw new RuntimeException("We have an auto-correction but the typed word "
+ "is empty? Impossible! I must commit suicide.");
}
- commitChosenWord(settingsValues, autoCorrection,
+ final boolean isBatchMode = mWordComposer.isBatchMode();
+ commitChosenWord(settingsValues, stringToCommit,
LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator);
- if (!typedWord.equals(autoCorrection)) {
+ if (null != autoCorrectionOrNull) {
+ mDictionaryFacilitator.switchMostProbableLanguage(
+ autoCorrectionOrNull.mSourceDict.mLocale);
+ }
+ if (!typedWord.equals(stringToCommit)) {
// This will make the correction flash for a short while as a visual clue
// to the user that auto-correction happened. It has no other effect; in particular
// note that this won't affect the text inside the text field AT ALL: it only makes
@@ -2071,8 +2104,14 @@ public final class InputLogic {
// of the auto-correction flash. At this moment, the "typedWord" argument is
// ignored by TextView.
mConnection.commitCorrection(new CorrectionInfo(
- mConnection.getExpectedSelectionEnd() - autoCorrection.length(),
- typedWord, autoCorrection));
+ mConnection.getExpectedSelectionEnd() - stringToCommit.length(),
+ typedWord, stringToCommit));
+ StatsUtils.onAutoCorrection(typedWord, stringToCommit, isBatchMode,
+ null == autoCorrectionOrNull
+ ? null : autoCorrectionOrNull.mSourceDict.mDictType);
+ StatsUtils.onWordCommitAutoCorrect(stringToCommit, isBatchMode);
+ } else {
+ StatsUtils.onWordCommitUserTyped(stringToCommit, isBatchMode);
}
}
}
@@ -2091,20 +2130,20 @@ public final class InputLogic {
final CharSequence chosenWordWithSuggestions =
SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
suggestedWords);
- // When we are composing word, get previous words information from the 2nd previous word
- // because the 1st previous word is the word to be committed. Otherwise get previous words
- // information from the 1st previous word.
- final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord(
+ // When we are composing word, get n-gram context from the 2nd previous word because the
+ // 1st previous word is the word to be committed. Otherwise get n-gram context from the 1st
+ // previous word.
+ final NgramContext ngramContext = mConnection.getNgramContextFromNthPreviousWord(
settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1);
mConnection.commitText(chosenWordWithSuggestions, 1);
// Add the word to the user history dictionary
- performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo);
+ performAdditionToUserHistoryDictionary(settingsValues, chosenWord, ngramContext);
// TODO: figure out here if this is an auto-correct or if the best word is actually
// what user typed. Note: currently this is done much later in
// LastComposedWord#didCommitTypedWord by string equality of the remembered
// strings.
mLastComposedWord = mWordComposer.commitWord(commitType,
- chosenWordWithSuggestions, separatorString, prevWordsInfo);
+ chosenWordWithSuggestions, separatorString, ngramContext);
}
/**
@@ -2137,10 +2176,7 @@ public final class InputLogic {
}
mConnection.tryFixLyingCursorPosition();
if (tryResumeSuggestions) {
- // This is triggered when starting input anew, so we want to include the resumed
- // word in suggestions.
- handler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */,
- true /* shouldDelay */);
+ handler.postResumeSuggestions(true /* shouldDelay */);
}
return true;
}
@@ -2151,7 +2187,7 @@ public final class InputLogic {
mWordComposer.adviseCapitalizedModeBeforeFetchingSuggestions(
getActualCapsMode(settingsValues, keyboardShiftMode));
mSuggest.getSuggestedWords(mWordComposer,
- getPrevWordsInfoFromNthPreviousWordForSuggestion(
+ getNgramContextFromNthPreviousWordForSuggestion(
settingsValues.mSpacingAndPunctuations,
// Get the word on which we should search the bigrams. If we are composing
// a word, it's whatever is *before* the half-committed word in the buffer,
@@ -2216,6 +2252,47 @@ public final class InputLogic {
mConnection.setComposingText(composingTextToBeSet, newCursorPosition);
}
+ /**
+ * Gets an object allowing private IME commands to be sent to the
+ * underlying editor.
+ * @return An object for sending private commands to the underlying editor.
+ */
+ public PrivateCommandPerformer getPrivateCommandPerformer() {
+ return mConnection;
+ }
+
+ /**
+ * Gets the expected index of the first char of the composing span within the editor's text.
+ * Returns a negative value in case there appears to be no valid composing span.
+ *
+ * @see #getComposingLength()
+ * @see RichInputConnection#hasSelection()
+ * @see RichInputConnection#isCursorPositionKnown()
+ * @see RichInputConnection#getExpectedSelectionStart()
+ * @see RichInputConnection#getExpectedSelectionEnd()
+ * @return The expected index in Java chars of the first char of the composing span.
+ */
+ // TODO: try and see if we can get rid of this method. Ideally the users of this class should
+ // never need to know this.
+ public int getComposingStart() {
+ if (!mConnection.isCursorPositionKnown() || mConnection.hasSelection()) {
+ return -1;
+ }
+ return mConnection.getExpectedSelectionStart() - mWordComposer.size();
+ }
+
+ /**
+ * Gets the expected length in Java chars of the composing span.
+ * May be 0 if there is no valid composing span.
+ * @see #getComposingStart()
+ * @return The expected length of the composing span.
+ */
+ // TODO: try and see if we can get rid of this method. Ideally the users of this class should
+ // never need to know this.
+ public int getComposingLength() {
+ return mWordComposer.size();
+ }
+
//////////////////////////////////////////////////////////////////////////////////////////////
// Following methods are tentatively placed in this class for the integration with
// TextDecorator.
@@ -2226,7 +2303,7 @@ public final class InputLogic {
* Sets the UI operator for {@link TextDecorator}.
* @param uiOperator the UI operator which should be associated with {@link TextDecorator}.
*/
- public void setTextDecoratorUi(final TextDecoratorUiOperator uiOperator) {
+ public void setTextDecoratorUi(@Nonnull final TextDecoratorUiOperator uiOperator) {
mTextDecorator.setUiOperator(uiOperator);
}
@@ -2267,7 +2344,7 @@ public final class InputLogic {
// We cannot help in this case because we are heavily relying on this new API.
return false;
}
- if (!settingsValues.mShouldShowUiToAcceptTypedWord) {
+ if (!settingsValues.mShouldShowLxxSuggestionUi) {
return false;
}
if (TextUtils.isEmpty(lastComposedWord.mTypedWord)) {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
index c6f83d0b9..5f391dd9b 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogicHandler.java
@@ -23,7 +23,6 @@ import android.os.Message;
import com.android.inputmethod.compat.LooperCompatUtils;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
@@ -62,7 +61,7 @@ class InputLogicHandler implements Handler.Callback {
final OnGetSuggestedWordsCallback callback) {}
};
- private InputLogicHandler() {
+ InputLogicHandler() {
mNonUIThreadHandler = null;
mLatinIME = null;
mInputLogic = null;
@@ -134,30 +133,38 @@ class InputLogicHandler implements Handler.Callback {
return;
}
mInputLogic.mWordComposer.setBatchInputPointers(batchPointers);
+ final OnGetSuggestedWordsCallback callback = new OnGetSuggestedWordsCallback() {
+ @Override
+ public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
+ showGestureSuggestionsWithPreviewVisuals(suggestedWords, isTailBatchInput);
+ }
+ };
getSuggestedWords(isTailBatchInput ? SuggestedWords.INPUT_STYLE_TAIL_BATCH
- : SuggestedWords.INPUT_STYLE_UPDATE_BATCH, sequenceNumber,
- new OnGetSuggestedWordsCallback() {
- @Override
- public void onGetSuggestedWords(SuggestedWords suggestedWords) {
- // We're now inside the callback. This always runs on the Non-UI thread,
- // no matter what thread updateBatchInput was originally called on.
- if (suggestedWords.isEmpty()) {
- // Use old suggestions if we don't have any new ones.
- // Previous suggestions are found in InputLogic#mSuggestedWords.
- // Since these are the most recent ones and we just recomputed
- // new ones to update them, then the previous ones are there.
- suggestedWords = mInputLogic.mSuggestedWords;
- }
- mLatinIME.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWords,
- isTailBatchInput /* dismissGestureFloatingPreviewText */);
- if (isTailBatchInput) {
- mInBatchInput = false;
- // The following call schedules onEndBatchInputInternal
- // to be called on the UI thread.
- mLatinIME.mHandler.showTailBatchInputResult(suggestedWords);
- }
- }
- });
+ : SuggestedWords.INPUT_STYLE_UPDATE_BATCH, sequenceNumber, callback);
+ }
+ }
+
+ 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,
+ // no matter what thread updateBatchInput was originally called on.
+ if (suggestedWordsForBatchInput.isEmpty()) {
+ // Use old suggestions if we don't have any new ones.
+ // Previous suggestions are found in InputLogic#mSuggestedWords.
+ // Since these are the most recent ones and we just recomputed
+ // new ones to update them, then the previous ones are there.
+ suggestedWordsToShowSuggestions = mInputLogic.mSuggestedWords;
+ } else {
+ suggestedWordsToShowSuggestions = suggestedWordsForBatchInput;
+ }
+ mLatinIME.mHandler.showGesturePreviewAndSuggestionStrip(suggestedWordsToShowSuggestions,
+ isTailBatchInput /* dismissGestureFloatingPreviewText */);
+ if (isTailBatchInput) {
+ mInBatchInput = false;
+ // The following call schedules onEndBatchInputInternal
+ // to be called on the UI thread.
+ mLatinIME.mHandler.showTailBatchInputResult(suggestedWordsToShowSuggestions);
}
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java b/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java
new file mode 100644
index 000000000..42eaa9c82
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/inputlogic/PrivateCommandPerformer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.inputlogic;
+
+import android.os.Bundle;
+
+/**
+ * Provides an interface matching
+ * {@link android.view.inputmethod.InputConnection#performPrivateCommand(String,Bundle)}.
+ */
+public interface PrivateCommandPerformer {
+ /**
+ * API to send private commands from an input method to its connected
+ * editor. This can be used to provide domain-specific features that are
+ * only known between certain input methods and their clients.
+ *
+ * @param action Name of the command to be performed. This must be a scoped
+ * name, i.e. prefixed with a package name you own, so that
+ * different developers will not create conflicting commands.
+ * @param data Any data to include with the command.
+ * @return true if the command was sent (regardless of whether the
+ * associated editor understood it), false if the input connection is no
+ * longer valid.
+ */
+ boolean performPrivateCommand(String action, Bundle data);
+}