aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java2
-rw-r--r--java/src/com/android/inputmethod/latin/CapsModeUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/CompletionInfoUtils.java43
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java2
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java5
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java2
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java7
-rw-r--r--java/src/com/android/inputmethod/latin/InputTypeUtils.java1
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java264
-rw-r--r--java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java28
-rw-r--r--java/src/com/android/inputmethod/latin/RecapitalizeStatus.java189
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java38
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java8
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsActivity.java5
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsFragment.java13
-rw-r--r--java/src/com/android/inputmethod/latin/StringUtils.java140
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeLocale.java4
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java21
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java18
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java34
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java48
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java8
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java9
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java8
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java10
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java117
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java18
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java8
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java6
29 files changed, 864 insertions, 194 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 562e1d0b7..42f713697 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -422,7 +422,7 @@ public final class BinaryDictionaryFileDumper {
private static void reinitializeClientRecordInDictionaryContentProvider(final Context context,
final ContentProviderClient client, final String clientId) throws RemoteException {
- final String metadataFileUri = context.getString(R.string.dictionary_pack_metadata_uri);
+ final String metadataFileUri = MetadataFileUriGetter.getMetadataUri(context);
if (TextUtils.isEmpty(metadataFileUri)) return;
// Tell the content provider to reset all information about this client id
final Uri metadataContentUri = getProviderUriBuilder(clientId)
diff --git a/java/src/com/android/inputmethod/latin/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/CapsModeUtils.java
index 1012cd519..4b8d1ac11 100644
--- a/java/src/com/android/inputmethod/latin/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/CapsModeUtils.java
@@ -41,7 +41,7 @@ public final class CapsModeUtils {
if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) {
return s.toUpperCase(locale);
} else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) {
- return StringUtils.toTitleCase(s, locale);
+ return StringUtils.capitalizeFirstCodePoint(s, locale);
} else {
return s;
}
diff --git a/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java b/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java
new file mode 100644
index 000000000..792a446c9
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.text.TextUtils;
+import android.view.inputmethod.CompletionInfo;
+
+import java.util.Arrays;
+
+/**
+ * Utilities to do various stuff with CompletionInfo.
+ */
+public class CompletionInfoUtils {
+ private CompletionInfoUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static CompletionInfo[] removeNulls(final CompletionInfo[] src) {
+ int j = 0;
+ final CompletionInfo[] dst = new CompletionInfo[src.length];
+ for (int i = 0; i < src.length; ++i) {
+ if (null != src[i] && !TextUtils.isEmpty(src[i].getText())) {
+ dst[j] = src[i];
+ ++j;
+ }
+ }
+ return Arrays.copyOfRange(dst, 0, j);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 50e50233e..86bb25562 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -160,6 +160,8 @@ public final class Constants {
public static final int CODE_DOUBLE_QUOTE = '"';
public static final int CODE_QUESTION_MARK = '?';
public static final int CODE_EXCLAMATION_MARK = '!';
+ public static final int CODE_SLASH = '/';
+ public static final int CODE_COMMERCIAL_AT = '@';
// TODO: Check how this should work for right-to-left languages. It seems to stand
// that for rtl languages, a closing parenthesis is a left parenthesis. Is this
// managed by the font? Or is it a different char?
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 8b5a76a17..75c2cf2c8 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -173,7 +173,8 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
// capitalization of i.
final int wordLen = StringUtils.codePointCount(word);
if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
- super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS);
+ super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS,
+ false /* isNotAWord */);
if (!TextUtils.isEmpty(prevWord)) {
if (mUseFirstLastBigrams) {
super.setBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM);
@@ -251,7 +252,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
}
private static boolean isValidName(final String name) {
- if (name != null && -1 == name.indexOf('@')) {
+ if (name != null && -1 == name.indexOf(Constants.CODE_COMMERCIAL_AT)) {
return true;
}
return false;
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index ff3d83fad..9691fa231 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -37,6 +37,8 @@ public abstract class Dictionary {
public static final String TYPE_USER = "user";
// User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history";
+ // Spawned by resuming suggestions. Comes from a span that was in the TextView.
+ public static final String TYPE_RESUMED = "resumed";
protected final String mDictType;
public Dictionary(final String dictType) {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 97dc6a8ac..4b1975a00 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -176,14 +176,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
// TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries,
// considering performance regression.
- protected void addWord(final String word, final String shortcutTarget, final int frequency) {
+ protected void addWord(final String word, final String shortcutTarget, final int frequency,
+ final boolean isNotAWord) {
if (shortcutTarget == null) {
- mFusionDictionary.add(word, frequency, null, false /* isNotAWord */);
+ mFusionDictionary.add(word, frequency, null, isNotAWord);
} else {
// TODO: Do this in the subclass, with this class taking an arraylist.
final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
- mFusionDictionary.add(word, frequency, shortcutTargets, false /* isNotAWord */);
+ mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord);
}
}
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index ecb20144b..46194f6e4 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -33,7 +33,6 @@ public final class InputTypeUtils implements InputType {
private static final int[] SUPPRESSING_AUTO_SPACES_FIELD_VARIATION = {
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS,
InputType.TYPE_TEXT_VARIATION_PASSWORD,
- InputType.TYPE_TEXT_VARIATION_URI,
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD };
public static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 56b1c786e..efa51569f 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -44,7 +44,9 @@ import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
+import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@@ -72,6 +74,7 @@ import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.RichInputConnection.Range;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.Utils.Stats;
import com.android.inputmethod.latin.define.ProductionFlag;
@@ -158,6 +161,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mPositionalInfoForUserDictPendingAddition = null;
private final WordComposer mWordComposer = new WordComposer();
private final RichInputConnection mConnection = new RichInputConnection(this);
+ private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
// Keep track of the last selection range to decide if we need to show word alternatives
private static final int NOT_A_CURSOR_POSITION = -1;
@@ -197,6 +201,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private static final int MSG_PENDING_IMS_CALLBACK = 1;
private static final int MSG_UPDATE_SUGGESTION_STRIP = 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 ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
@@ -234,6 +239,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords)msg.obj,
msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
break;
+ case MSG_RESUME_SUGGESTIONS:
+ latinIme.restartSuggestionsOnWordTouchedByCursor();
+ break;
}
}
@@ -241,6 +249,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions);
}
+ public void postResumeSuggestions() {
+ sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
+ }
+
public void cancelUpdateSuggestionStrip() {
removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
}
@@ -427,6 +439,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mHandler.onCreate();
DEBUG = LatinImeLogger.sDBG;
+ // TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}.
loadSettings();
initSuggest();
@@ -464,6 +477,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final InputAttributes inputAttributes =
new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode());
mSettings.loadSettings(locale, inputAttributes);
+ // May need to reset the contacts dictionary depending on the user settings.
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
}
@@ -730,8 +744,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
resetComposingState(true /* alsoResetLastComposedWord */);
mDeleteCount = 0;
mSpaceState = SPACE_STATE_NONE;
+ mRecapitalizeStatus.deactivate();
mCurrentlyPressedHardwareKeys.clear();
+ // Note: the following does a round-trip IPC on the main thread: be careful
+ final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
+ if (null != mSuggest && null != currentLocale && !currentLocale.equals(mSuggest.mLocale)) {
+ initSuggest();
+ }
if (mSuggestionStripView != null) {
// This will set the punctuation suggestions if next word suggestion is off;
// otherwise it will clear the suggestion strip.
@@ -784,8 +804,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// to the user dictionary.
if (null != mPositionalInfoForUserDictPendingAddition
&& mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
- mConnection, editorInfo, mLastSelectionEnd,
- mSubtypeSwitcher.getCurrentSubtypeLocale())) {
+ mConnection, editorInfo, mLastSelectionEnd, currentLocale)) {
mPositionalInfoForUserDictPendingAddition = null;
}
// If tryReplaceWithActualWord returns false, we don't know what word was
@@ -910,13 +929,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
resetEntireInputState(newSelStart);
}
+ // We moved the cursor. If we are touching a word, we need to resume suggestion.
+ mHandler.postResumeSuggestions();
+ // Reset the last recapitalization.
+ mRecapitalizeStatus.deactivate();
mKeyboardSwitcher.updateShiftState();
}
mExpectingUpdateSelection = false;
- // TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not
- // here. It would probably be too expensive to call directly here but we may want to post a
- // message to delay it. The point would be to unify behavior between backspace to the
- // end of a word and manually put the pointer at the end of the word.
// Make a note of the cursor position
mLastSelectionStart = newSelStart;
@@ -983,7 +1002,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
}
if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) return;
- mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
if (applicationSpecifiedCompletions == null) {
clearSuggestionStrip();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -991,6 +1009,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
return;
}
+ mApplicationSpecifiedCompletions =
+ CompletionInfoUtils.removeNulls(applicationSpecifiedCompletions);
final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
SuggestedWords.getFromApplicationSpecifiedCompletions(
@@ -1166,6 +1186,15 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
SPACE_STATE_PHANTOM == mSpaceState);
}
+ public int getCurrentRecapitalizeState() {
+ if (!mRecapitalizeStatus.isActive()
+ || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
+ // Not recapitalizing at the moment
+ return RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
+ }
+ return mRecapitalizeStatus.getCurrentMode();
+ }
+
// Factor in auto-caps and manual caps and compute the current caps mode.
private int getActualCapsMode() {
final int keyboardShiftMode = mKeyboardSwitcher.getKeyboardShiftMode();
@@ -1243,10 +1272,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
} else {
wordToEdit = word;
}
- mPositionalInfoForUserDictPendingAddition =
- new PositionalInfoForUserDictPendingAddition(
- wordToEdit, mLastSelectionEnd, getCurrentInputEditorInfo(),
- mLastComposedWord.mCapitalizedMode);
mUserDictionary.addWordToUserDictionary(wordToEdit);
}
@@ -1380,8 +1405,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
LatinImeLogger.logOnDelete(x, y);
break;
case Constants.CODE_SHIFT:
+ // Note: calling back to the keyboard on Shift key is handled in onPressKey()
+ // and onReleaseKey().
+ final Keyboard currentKeyboard = switcher.getKeyboard();
+ if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
+ // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
+ // alphabetic shift and shift while in symbol layout.
+ handleRecapitalize();
+ }
+ break;
case Constants.CODE_SWITCH_ALPHA_SYMBOL:
- // Shift and symbol key is handled in onPressKey() and onReleaseKey().
+ // Note: calling back to the keyboard on symbol key is handled in onPressKey()
+ // and onReleaseKey().
break;
case Constants.CODE_SETTINGS:
onSettingsKeyPressed();
@@ -1459,7 +1494,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
"", mWordComposer.getTypedWord(), " ", mWordComposer);
}
}
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the character at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ } else {
+ commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ }
}
final int keyX, keyY;
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
@@ -1515,8 +1556,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
final int wordComposerSize = mWordComposer.size();
// Since isComposingWord() is true, the size is at least 1.
- final int lastChar = mWordComposer.getCodeAt(wordComposerSize - 1);
- if (wordComposerSize <= 1) {
+ final int lastChar = mWordComposer.getCodeBeforeCursor();
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the batch input at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ } else if (wordComposerSize <= 1) {
// We auto-correct the previous (typed, not gestured) string iff it's one character
// long. The reason for this is, even in the middle of gesture typing, you'll still
// tap one-letter words and you want them auto-corrected (typically, "i" in English
@@ -1540,7 +1585,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
} else {
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
- if (mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+ if (Character.isLetter(codePointBeforeCursor)
+ || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
mSpaceState = SPACE_STATE_PHANTOM;
}
}
@@ -1551,7 +1597,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private static final class BatchInputUpdater implements Handler.Callback {
private final Handler mHandler;
private LatinIME mLatinIme;
- private boolean mInBatchInput; // synchronized using "this".
+ private final Object mLock = new Object();
+ private boolean mInBatchInput; // synchronized using {@link #mLock}.
private BatchInputUpdater() {
final HandlerThread handlerThread = new HandlerThread(
@@ -1582,21 +1629,25 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
// Run in the UI thread.
- public synchronized void onStartBatchInput(final LatinIME latinIme) {
- mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
- mLatinIme = latinIme;
- mInBatchInput = true;
+ public void onStartBatchInput(final LatinIME latinIme) {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
+ mLatinIme = latinIme;
+ mInBatchInput = true;
+ }
}
// Run in the Handler thread.
- private synchronized void updateBatchInput(final InputPointers batchPointers) {
- if (!mInBatchInput) {
- // Batch input has ended or canceled while the message was being delivered.
- return;
+ private void updateBatchInput(final InputPointers batchPointers) {
+ synchronized (mLock) {
+ if (!mInBatchInput) {
+ // Batch input has ended or canceled while the message was being delivered.
+ return;
+ }
+ final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
+ mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ suggestedWords, false /* dismissGestureFloatingPreviewText */);
}
- final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- suggestedWords, false /* dismissGestureFloatingPreviewText */);
}
// Run in the UI thread.
@@ -1609,19 +1660,23 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
.sendToTarget();
}
- public synchronized void onCancelBatchInput() {
- mInBatchInput = false;
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
+ public void onCancelBatchInput() {
+ synchronized (mLock) {
+ mInBatchInput = false;
+ mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
+ }
}
// Run in the UI thread.
- public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers) {
- mInBatchInput = false;
- final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- suggestedWords, true /* dismissGestureFloatingPreviewText */);
- return suggestedWords;
+ public SuggestedWords onEndBatchInput(final InputPointers batchPointers) {
+ synchronized (mLock) {
+ mInBatchInput = false;
+ final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
+ mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ suggestedWords, true /* dismissGestureFloatingPreviewText */);
+ return suggestedWords;
+ }
}
// {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to
@@ -1717,6 +1772,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// during key repeat.
mHandler.postUpdateShiftState();
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can remove the character at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ // When we exit this if-clause, mWordComposer.isComposingWord() will return false.
+ }
if (mWordComposer.isComposingWord()) {
final int length = mWordComposer.size();
if (length > 0) {
@@ -1727,7 +1788,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
ResearchLogger.getInstance().uncommitCurrentLogUnit(
word, false /* dumpCurrentLogUnit */);
}
+ final String rejectedSuggestion = mWordComposer.getTypedWord();
mWordComposer.reset();
+ mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
} else {
mWordComposer.deleteLast();
}
@@ -1848,6 +1911,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
promotePhantomSpace();
}
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the character at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ isComposingWord = false;
+ }
// NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several
// dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
// thread here.
@@ -1902,6 +1971,38 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
}
+ private void handleRecapitalize() {
+ if (mLastSelectionStart == mLastSelectionEnd) return; // No selection
+ // If we have a recapitalize in progress, use it; otherwise, create a new one.
+ if (!mRecapitalizeStatus.isActive()
+ || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
+ mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd,
+ mConnection.getSelectedText(0 /* flags, 0 for no styles */).toString(),
+ mSettings.getCurrentLocale(), mSettings.getWordSeparators());
+ // We trim leading and trailing whitespace.
+ mRecapitalizeStatus.trim();
+ // Trimming the object may have changed the length of the string, and we need to
+ // reposition the selection handles accordingly. As this result in an IPC call,
+ // only do it if it's actually necessary, in other words if the recapitalize status
+ // is not set at the same place as before.
+ if (!mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
+ mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
+ mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
+ mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
+ }
+ }
+ mRecapitalizeStatus.rotate();
+ final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
+ mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
+ mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
+ mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
+ mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
+ mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
+ // Match the keyboard to the new state.
+ mKeyboardSwitcher.updateShiftState();
+ }
+
// Returns true if we did an autocorrection, false otherwise.
private boolean handleSeparator(final int primaryCode, final int x, final int y,
final int spaceState) {
@@ -1909,7 +2010,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
}
boolean didAutoCorrect = false;
- // Handle separator
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the separator at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ }
if (mWordComposer.isComposingWord()) {
if (mSettings.getCurrent().mCorrectionEnabled) {
// TODO: maybe cache Strings in an <String> sparse array or something
@@ -2321,6 +2426,73 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
/**
+ * Check if the cursor is touching a word. If so, restart suggestions on this word, else
+ * do nothing.
+ */
+ private void restartSuggestionsOnWordTouchedByCursor() {
+ // If the cursor is not touching a word, or if there is a selection, return right away.
+ if (mLastSelectionStart != mLastSelectionEnd) return;
+ if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
+ final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
+ 0 /* additionalPrecedingWordsCount */);
+ if (null == range) return; // Happens if we don't have an input connection at all
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+ final String typedWord = range.mWord.toString();
+ if (range.mWord instanceof SpannableString) {
+ final SpannableString spannableString = (SpannableString)range.mWord;
+ int i = 0;
+ for (Object object : spannableString.getSpans(0, spannableString.length(),
+ SuggestionSpan.class)) {
+ SuggestionSpan span = (SuggestionSpan)object;
+ for (String s : span.getSuggestions()) {
+ ++i;
+ if (!TextUtils.equals(s, typedWord)) {
+ suggestions.add(new SuggestedWordInfo(s,
+ SuggestionStripView.MAX_SUGGESTIONS - i,
+ SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED));
+ }
+ }
+ }
+ }
+ mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard());
+ mWordComposer.setCursorPositionWithinWord(range.mCharsBefore);
+ mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore,
+ mLastSelectionEnd + range.mCharsAfter);
+ final SuggestedWords suggestedWords;
+ if (suggestions.isEmpty()) {
+ // We come here if there weren't any suggestion spans on this word. We will try to
+ // compute suggestions for it instead.
+ final SuggestedWords suggestedWordsIncludingTypedWord =
+ getSuggestedWords(Suggest.SESSION_TYPING);
+ if (suggestedWordsIncludingTypedWord.size() > 1) {
+ // 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 #getSuggestedWordsExcludingTypedWord() method sets willAutoCorrect to false.
+ suggestedWords =
+ suggestedWordsIncludingTypedWord.getSuggestedWordsExcludingTypedWord();
+ } 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;
+ }
+ } else {
+ // We found suggestion spans in the word. We'll create the SuggestedWords out of
+ // them, and make willAutoCorrect false.
+ suggestedWords = new SuggestedWords(suggestions,
+ true /* typedWordValid */, false /* willAutoCorrect */,
+ false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */,
+ false /* isPrediction */);
+ }
+
+ // Note that it's very important here that suggestedWords.mWillAutoCorrect is false.
+ // We never want to auto-correct on a resumed suggestion. Please refer to the three
+ // places above where suggestedWords is affected.
+ showSuggestionStrip(suggestedWords, typedWord);
+ }
+
+ /**
* Check if the cursor is actually at the end of a word. If so, restart suggestions on this
* word, else do nothing.
*/
@@ -2328,17 +2500,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final CharSequence word =
mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent());
if (null != word) {
- restartSuggestionsOnWordBeforeCursor(word);
+ final String wordString = word.toString();
+ restartSuggestionsOnWordBeforeCursor(wordString);
// TODO: Handle the case where the user manually moves the cursor and then backs up over
// a separator. In that case, the current log unit should not be uncommitted.
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().uncommitCurrentLogUnit(word.toString(),
+ ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString,
true /* dumpCurrentLogUnit */);
}
}
}
- private void restartSuggestionsOnWordBeforeCursor(final CharSequence word) {
+ private void restartSuggestionsOnWordBeforeCursor(final String word) {
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
final int length = word.length();
mConnection.deleteSurroundingText(length, 0);
@@ -2392,7 +2565,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// This essentially inserts a space, and that's it.
public void promotePhantomSpace() {
- if (mSettings.getCurrent().shouldInsertSpacesAutomatically()) {
+ if (mSettings.getCurrent().shouldInsertSpacesAutomatically()
+ && !mConnection.textBeforeCursorLooksLikeURL()) {
sendKeyCodePoint(Constants.CODE_SPACE);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_promotePhantomSpace();
@@ -2409,8 +2583,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// Outside LatinIME, only used by the {@link InputTestsBase} test suite.
@UsedForTesting
void loadKeyboard() {
- // When the device locale is changed in SetupWizard etc., this method may get called via
- // onConfigurationChanged before SoftInputWindow is shown.
+ // TODO: Why are we calling {@link #loadSettings()} and {@link #initSuggest()} in a
+ // different order than in {@link #onStartInputView}?
initSuggest();
loadSettings();
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
diff --git a/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java b/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java
new file mode 100644
index 000000000..e6dc6db8f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.content.Context;
+
+/**
+ * Helper class to get the metadata URI.
+ */
+public class MetadataFileUriGetter {
+ public static String getMetadataUri(Context context) {
+ return context.getString(R.string.dictionary_pack_metadata_uri);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
new file mode 100644
index 000000000..8a704ab42
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2013 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;
+
+import com.android.inputmethod.latin.StringUtils;
+
+import java.util.Locale;
+
+/**
+ * The status of the current recapitalize process.
+ */
+public class RecapitalizeStatus {
+ public static final int NOT_A_RECAPITALIZE_MODE = -1;
+ public static final int CAPS_MODE_ORIGINAL_MIXED_CASE = 0;
+ public static final int CAPS_MODE_ALL_LOWER = 1;
+ public static final int CAPS_MODE_FIRST_WORD_UPPER = 2;
+ public static final int CAPS_MODE_ALL_UPPER = 3;
+ // When adding a new mode, don't forget to update the CAPS_MODE_LAST constant.
+ public static final int CAPS_MODE_LAST = CAPS_MODE_ALL_UPPER;
+
+ private static final int[] ROTATION_STYLE = {
+ CAPS_MODE_ORIGINAL_MIXED_CASE,
+ CAPS_MODE_ALL_LOWER,
+ CAPS_MODE_FIRST_WORD_UPPER,
+ CAPS_MODE_ALL_UPPER
+ };
+
+ private static final int getStringMode(final String string, final String separators) {
+ if (StringUtils.isIdenticalAfterUpcase(string)) {
+ return CAPS_MODE_ALL_UPPER;
+ } else if (StringUtils.isIdenticalAfterDowncase(string)) {
+ return CAPS_MODE_ALL_LOWER;
+ } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, separators)) {
+ return CAPS_MODE_FIRST_WORD_UPPER;
+ } else {
+ return CAPS_MODE_ORIGINAL_MIXED_CASE;
+ }
+ }
+
+ /**
+ * We store the location of the cursor and the string that was there before the recapitalize
+ * action was done, and the location of the cursor and the string that was there after.
+ */
+ private int mCursorStartBefore;
+ private String mStringBefore;
+ private int mCursorStartAfter;
+ private int mCursorEndAfter;
+ private int mRotationStyleCurrentIndex;
+ private boolean mSkipOriginalMixedCaseMode;
+ private Locale mLocale;
+ private String mSeparators;
+ private String mStringAfter;
+ private boolean mIsActive;
+
+ public RecapitalizeStatus() {
+ // By default, initialize with dummy values that won't match any real recapitalize.
+ initialize(-1, -1, "", Locale.getDefault(), "");
+ deactivate();
+ }
+
+ public void initialize(final int cursorStart, final int cursorEnd, final String string,
+ final Locale locale, final String separators) {
+ mCursorStartBefore = cursorStart;
+ mStringBefore = string;
+ mCursorStartAfter = cursorStart;
+ mCursorEndAfter = cursorEnd;
+ mStringAfter = string;
+ final int initialMode = getStringMode(mStringBefore, separators);
+ mLocale = locale;
+ mSeparators = separators;
+ if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) {
+ mRotationStyleCurrentIndex = 0;
+ mSkipOriginalMixedCaseMode = false;
+ } else {
+ // Find the current mode in the array.
+ int currentMode;
+ for (currentMode = ROTATION_STYLE.length - 1; currentMode > 0; --currentMode) {
+ if (ROTATION_STYLE[currentMode] == initialMode) {
+ break;
+ }
+ }
+ mRotationStyleCurrentIndex = currentMode;
+ mSkipOriginalMixedCaseMode = true;
+ }
+ mIsActive = true;
+ }
+
+ public void deactivate() {
+ mIsActive = false;
+ }
+
+ public boolean isActive() {
+ return mIsActive;
+ }
+
+ public boolean isSetAt(final int cursorStart, final int cursorEnd) {
+ return cursorStart == mCursorStartAfter && cursorEnd == mCursorEndAfter;
+ }
+
+ /**
+ * Rotate through the different possible capitalization modes.
+ */
+ public void rotate() {
+ final String oldResult = mStringAfter;
+ int count = 0; // Protection against infinite loop.
+ do {
+ mRotationStyleCurrentIndex = (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length;
+ if (CAPS_MODE_ORIGINAL_MIXED_CASE == ROTATION_STYLE[mRotationStyleCurrentIndex]
+ && mSkipOriginalMixedCaseMode) {
+ mRotationStyleCurrentIndex =
+ (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length;
+ }
+ ++count;
+ switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) {
+ case CAPS_MODE_ORIGINAL_MIXED_CASE:
+ mStringAfter = mStringBefore;
+ break;
+ case CAPS_MODE_ALL_LOWER:
+ mStringAfter = mStringBefore.toLowerCase(mLocale);
+ break;
+ case CAPS_MODE_FIRST_WORD_UPPER:
+ mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators,
+ mLocale);
+ break;
+ case CAPS_MODE_ALL_UPPER:
+ mStringAfter = mStringBefore.toUpperCase(mLocale);
+ break;
+ default:
+ mStringAfter = mStringBefore;
+ }
+ } while (mStringAfter.equals(oldResult) && count < ROTATION_STYLE.length + 1);
+ mCursorEndAfter = mCursorStartAfter + mStringAfter.length();
+ }
+
+ /**
+ * Remove leading/trailing whitespace from the considered string.
+ */
+ public void trim() {
+ final int len = mStringBefore.length();
+ int nonWhitespaceStart = 0;
+ for (; nonWhitespaceStart < len;
+ nonWhitespaceStart = mStringBefore.offsetByCodePoints(nonWhitespaceStart, 1)) {
+ final int codePoint = mStringBefore.codePointAt(nonWhitespaceStart);
+ if (!Character.isWhitespace(codePoint)) break;
+ }
+ int nonWhitespaceEnd = len;
+ for (; nonWhitespaceEnd > 0;
+ nonWhitespaceEnd = mStringBefore.offsetByCodePoints(nonWhitespaceEnd, -1)) {
+ final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd);
+ if (!Character.isWhitespace(codePoint)) break;
+ }
+ if (0 != nonWhitespaceStart || len != nonWhitespaceEnd) {
+ mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd;
+ mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart;
+ mStringAfter = mStringBefore =
+ mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd);
+ }
+ }
+
+ public String getRecapitalizedString() {
+ return mStringAfter;
+ }
+
+ public int getNewCursorStart() {
+ return mCursorStartAfter;
+ }
+
+ public int getNewCursorEnd() {
+ return mCursorEndAfter;
+ }
+
+ public int getCurrentMode() {
+ return ROTATION_STYLE[mRotationStyleCurrentIndex];
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 16744d1f0..8ed7ab264 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin;
import android.inputmethodservice.InputMethodService;
+import android.text.SpannableString;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -181,6 +182,11 @@ public final class RichInputConnection {
}
}
+ public CharSequence getSelectedText(final int flags) {
+ if (null == mIC) return null;
+ return mIC.getSelectedText(flags);
+ }
+
/**
* Gets the caps modes we should be in after this specific string.
*
@@ -392,7 +398,9 @@ public final class RichInputConnection {
public void commitCompletion(final CompletionInfo completionInfo) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
- final CharSequence text = completionInfo.getText();
+ CharSequence text = completionInfo.getText();
+ // text should never be null, but just in case, it's better to insert nothing than to crash
+ if (null == text) text = "";
mCommittedTextBeforeComposingText.append(text);
mCurrentCursorPosition += text.length() - mComposingText.length();
mComposingText.setLength(0);
@@ -442,9 +450,9 @@ public final class RichInputConnection {
public final int mCharsAfter;
/** The actual characters that make up a word */
- public final String mWord;
+ public final CharSequence mWord;
- public Range(int charsBefore, int charsAfter, String word) {
+ public Range(int charsBefore, int charsAfter, CharSequence word) {
if (charsBefore < 0 || charsAfter < 0) {
throw new IndexOutOfBoundsException();
}
@@ -498,7 +506,7 @@ public final class RichInputConnection {
* separator. For example, if the field contains "he|llo world", where |
* represents the cursor, then "hello " will be returned.
*/
- public String getWordAtCursor(String separators) {
+ public CharSequence getWordAtCursor(String separators) {
// getWordRangeAtCursor returns null if the connection is null
Range r = getWordRangeAtCursor(separators, 0);
return (r == null) ? null : r.mWord;
@@ -517,8 +525,10 @@ public final class RichInputConnection {
if (mIC == null || sep == null) {
return null;
}
- final CharSequence before = mIC.getTextBeforeCursor(1000, 0);
- final CharSequence after = mIC.getTextAfterCursor(1000, 0);
+ final CharSequence before = mIC.getTextBeforeCursor(1000,
+ InputConnection.GET_TEXT_WITH_STYLES);
+ final CharSequence after = mIC.getTextAfterCursor(1000,
+ InputConnection.GET_TEXT_WITH_STYLES);
if (before == null || after == null) {
return null;
}
@@ -560,8 +570,9 @@ public final class RichInputConnection {
}
}
- final String word = before.toString().substring(startIndexInBefore, before.length())
- + after.toString().substring(0, endIndexInAfter);
+ final SpannableString word = new SpannableString(TextUtils.concat(
+ before.subSequence(startIndexInBefore, before.length()),
+ after.subSequence(0, endIndexInAfter)));
return new Range(before.length() - startIndexInBefore, endIndexInAfter, word);
}
@@ -709,4 +720,15 @@ public final class RichInputConnection {
// position and the expected position, then it must be a belated update.
return (newSelStart - oldSelStart) * (mCurrentCursorPosition - newSelStart) >= 0;
}
+
+ /**
+ * Looks at the text just before the cursor to find out if it looks like a URL.
+ *
+ * The weakest point here is, if we don't have enough text bufferized, we may fail to realize
+ * we are in URL situation, but other places in this class have the same limitation and it
+ * does not matter too much in the practice.
+ */
+ public boolean textBeforeCursorLooksLikeURL() {
+ return StringUtils.lastPartLooksLikeURL(mCommittedTextBeforeComposingText);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 8fbe843cf..72e08700a 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -134,6 +134,14 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return mSettingsValues.mIsInternal;
}
+ public String getWordSeparators() {
+ return mSettingsValues.mWordSeparators;
+ }
+
+ public Locale getCurrentLocale() {
+ return mCurrentLocale;
+ }
+
// Accessed from the settings interface, hence public
public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
final Resources res) {
diff --git a/java/src/com/android/inputmethod/latin/SettingsActivity.java b/java/src/com/android/inputmethod/latin/SettingsActivity.java
index 99b572e06..37ac2e35c 100644
--- a/java/src/com/android/inputmethod/latin/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/SettingsActivity.java
@@ -25,7 +25,10 @@ public final class SettingsActivity extends PreferenceActivity {
@Override
public Intent getIntent() {
final Intent intent = super.getIntent();
- intent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
+ final String fragment = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
+ if (fragment == null) {
+ intent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
+ }
intent.putExtra(EXTRA_NO_HEADERS, true);
return intent;
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 5405a5eb7..a96c997c8 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -16,7 +16,6 @@
package com.android.inputmethod.latin;
-import android.app.Activity;
import android.app.backup.BackupManager;
import android.content.Context;
import android.content.Intent;
@@ -115,12 +114,8 @@ public final class SettingsFragment extends InputMethodSettingsFragment
if (FeedbackUtils.isFeedbackFormSupported()) {
feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
- public boolean onPreferenceClick(Preference arg0) {
- final Activity activity = getActivity();
- FeedbackUtils.showFeedbackForm(activity);
- if (!activity.isFinishing()) {
- activity.finish();
- }
+ public boolean onPreferenceClick(final Preference pref) {
+ FeedbackUtils.showFeedbackForm(getActivity());
return true;
}
});
@@ -232,10 +227,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
} else if (key.equals(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY)) {
setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST,
Settings.readShowsLanguageSwitchKey(prefs));
- } else if (key.equals(Settings.PREF_GESTURE_INPUT)) {
- final boolean gestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
- setPreferenceEnabled(Settings.PREF_GESTURE_PREVIEW_TRAIL, gestureInputEnabled);
- setPreferenceEnabled(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, gestureInputEnabled);
} else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
}
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 59ad28fc9..d5ee58a63 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -106,10 +106,19 @@ public final class StringUtils {
}
}
- public static String toTitleCase(final String s, final Locale locale) {
+ public static String capitalizeFirstCodePoint(final String s, final Locale locale) {
if (s.length() <= 1) {
- // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
- return s;
+ return s.toUpperCase(locale);
+ }
+ // Please refer to the comment below in
+ // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings
+ final int cutoff = s.offsetByCodePoints(0, 1);
+ return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff);
+ }
+
+ public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) {
+ if (s.length() <= 1) {
+ return s.toUpperCase(locale);
}
// TODO: fix the bugs below
// - This does not work for Greek, because it returns upper case instead of title case.
@@ -213,4 +222,129 @@ public final class StringUtils {
if (1 == capsCount) return CAPITALIZE_FIRST;
return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
}
+
+ public static boolean isIdenticalAfterUpcase(final String text) {
+ final int len = text.length();
+ for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+ final int codePoint = text.codePointAt(i);
+ if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isIdenticalAfterDowncase(final String text) {
+ final int len = text.length();
+ for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+ final int codePoint = text.codePointAt(i);
+ if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
+ final String separators) {
+ boolean needCapsNext = true;
+ final int len = text.length();
+ for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+ final int codePoint = text.codePointAt(i);
+ if (Character.isLetter(codePoint)) {
+ if ((needCapsNext && !Character.isUpperCase(codePoint))
+ || (!needCapsNext && !Character.isLowerCase(codePoint))) {
+ return false;
+ }
+ }
+ // We need a capital letter next if this is a separator.
+ needCapsNext = (-1 != separators.indexOf(codePoint));
+ }
+ return true;
+ }
+
+ // TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph
+ // which should be capitalized together in *some* cases.
+ public static String capitalizeEachWord(final String text, final String separators,
+ final Locale locale) {
+ final StringBuilder builder = new StringBuilder();
+ boolean needCapsNext = true;
+ final int len = text.length();
+ for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+ final String nextChar = text.substring(i, text.offsetByCodePoints(i, 1));
+ if (needCapsNext) {
+ builder.append(nextChar.toUpperCase(locale));
+ } else {
+ builder.append(nextChar.toLowerCase(locale));
+ }
+ // We need a capital letter next if this is a separator.
+ needCapsNext = (-1 != separators.indexOf(nextChar.codePointAt(0)));
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Approximates whether the text before the cursor looks like a URL.
+ *
+ * This is not foolproof, but it should work well in the practice.
+ * Essentially it walks backward from the cursor until it finds something that's not a letter,
+ * digit, or common URL symbol like underscore. If it hasn't found a period yet, then it
+ * does not look like a URL.
+ * If the text:
+ * - starts with www and contains a period
+ * - starts with a slash preceded by either a slash, whitespace, or start-of-string
+ * Then it looks like a URL and we return true. Otherwise, we return false.
+ *
+ * Note: this method is called quite often, and should be fast.
+ *
+ * TODO: This will return that "abc./def" and ".abc/def" look like URLs to keep down the
+ * code complexity, but ideally it should not. It's acceptable for now.
+ */
+ public static boolean lastPartLooksLikeURL(final CharSequence text) {
+ int i = text.length();
+ if (0 == i) return false;
+ int wCount = 0;
+ int slashCount = 0;
+ boolean hasSlash = false;
+ boolean hasPeriod = false;
+ int codePoint = 0;
+ while (i > 0) {
+ codePoint = Character.codePointBefore(text, i);
+ if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') {
+ // Handwavy heuristic to see if that's a URL character. Anything between period
+ // and z. This includes all lower- and upper-case ascii letters, period,
+ // underscore, arrobase, question mark, equal sign. It excludes spaces, exclamation
+ // marks, double quotes...
+ // Anything that's not a URL-like character causes us to break from here and
+ // evaluate normally.
+ break;
+ }
+ if (Constants.CODE_PERIOD == codePoint) {
+ hasPeriod = true;
+ }
+ if (Constants.CODE_SLASH == codePoint) {
+ hasSlash = true;
+ if (2 == ++slashCount) {
+ return true;
+ }
+ } else {
+ slashCount = 0;
+ }
+ if ('w' == codePoint) {
+ ++wCount;
+ } else {
+ wCount = 0;
+ }
+ i = Character.offsetByCodePoints(text, i, -1);
+ }
+ // End of the text run.
+ // If it starts with www and includes a period, then it looks like a URL.
+ if (wCount >= 3 && hasPeriod) return true;
+ // If it starts with a slash, and the code point before is whitespace, it looks like an URL.
+ if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) return true;
+ // If it has both a period and a slash, it looks like an URL.
+ if (hasPeriod && hasSlash) return true;
+ // Otherwise, it doesn't look like an URL.
+ return false;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 5e28cc2d0..4d88ecc0c 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -183,7 +183,7 @@ public final class SubtypeLocale {
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
displayName = locale.getDisplayName(displayLocale);
}
- return StringUtils.toTitleCase(displayName, displayLocale);
+ return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale);
}
// InputMethodSubtype's display name in its locale.
@@ -243,7 +243,7 @@ public final class SubtypeLocale {
}
}
};
- return StringUtils.toTitleCase(
+ return StringUtils.capitalizeFirstCodePoint(
getSubtypeName.runInLocale(sResources, displayLocale), displayLocale);
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 975664dca..59d0207f6 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -47,6 +47,9 @@ public final class Suggest {
// TODO: rename this to CORRECTION_ON
public static final int CORRECTION_FULL = 1;
+ // Close to -2**31
+ private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
+
public interface SuggestInitializationListener {
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
}
@@ -65,7 +68,7 @@ public final class Suggest {
private float mAutoCorrectionThreshold;
// Locale used for upper- and title-casing words
- private final Locale mLocale;
+ public final Locale mLocale;
public Suggest(final Context context, final Locale locale,
final SuggestInitializationListener listener) {
@@ -334,7 +337,21 @@ public final class Suggest {
}
}
+ if (suggestionsContainer.size() > 1 && TextUtils.equals(suggestionsContainer.get(0).mWord,
+ wordComposer.getRejectedBatchModeSuggestion())) {
+ final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
+ suggestionsContainer.add(1, rejected);
+ }
SuggestedWordInfo.removeDups(suggestionsContainer);
+
+ // For some reason some suggestions with MIN_VALUE are making their way here.
+ // TODO: Find a more robust way to detect distractors.
+ for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
+ if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
+ suggestionsContainer.remove(i);
+ }
+ }
+
// In the batch input mode, the most relevant suggested word should act as a "typed word"
// (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
return new SuggestedWords(suggestionsContainer,
@@ -394,7 +411,7 @@ public final class Suggest {
if (isAllUpperCase) {
sb.append(wordInfo.mWord.toUpperCase(locale));
} else if (isFirstCharCapitalized) {
- sb.append(StringUtils.toTitleCase(wordInfo.mWord, locale));
+ sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
} else {
sb.append(wordInfo.mWord);
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 3d6fe2d22..616e1911b 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -131,6 +131,7 @@ public final class SuggestedWords {
public static final int KIND_APP_DEFINED = 6; // Suggested by the application
public static final int KIND_SHORTCUT = 7; // A shortcut
public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input)
+ public static final int KIND_RESUMED = 9; // A resumed suggestion (comes from a span)
public final String mWord;
public final int mScore;
public final int mKind; // one of the KIND_* constants above
@@ -194,4 +195,21 @@ public final class SuggestedWords {
}
}
}
+
+ // SuggestedWords is an immutable object, as much as possible. We must not just remove
+ // words from the member ArrayList as some other parties may expect the object to never change.
+ public SuggestedWords getSuggestedWordsExcludingTypedWord() {
+ final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList();
+ for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
+ final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
+ if (SuggestedWordInfo.KIND_TYPED != info.mKind) {
+ newSuggestions.add(info);
+ }
+ }
+ // We should never autocorrect, so we say the typed word is valid. Also, in this case,
+ // no auto-correction should take place hence willAutoCorrect = false.
+ return new SuggestedWords(newSuggestions, true /* typedWordValid */,
+ false /* willAutoCorrect */, mIsPunctuationSuggestions, mIsObsoleteSuggestions,
+ mIsPrediction);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 0d5bde623..90f92972a 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -20,7 +20,6 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
-import android.content.Intent;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
@@ -28,7 +27,10 @@ import android.os.Build;
import android.provider.UserDictionary.Words;
import android.text.TextUtils;
+import com.android.inputmethod.compat.UserDictionaryCompatUtils;
+
import java.util.Arrays;
+import java.util.Locale;
/**
* An expandable dictionary that stores the words in the user dictionary provider into a binary
@@ -61,10 +63,6 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
private static final String NAME = "userunigram";
- // This is not exported by the framework so we pretty much have to write it here verbatim
- private static final String ACTION_USER_DICTIONARY_INSERT =
- "com.android.settings.USER_DICTIONARY_INSERT";
-
private ContentObserver mObserver;
final private String mLocale;
final private boolean mAlsoUseMoreRestrictiveLocales;
@@ -211,23 +209,19 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
/**
* Adds a word to the user dictionary and makes it persistent.
*
- * This will call upon the system interface to do the actual work through the intent readied by
- * the system to this effect.
- *
* @param word the word to add. If the word is capitalized, then the dictionary will
* recognize it as a capitalized word when searched.
*/
public synchronized void addWordToUserDictionary(final String word) {
- // TODO: do something for the UI. With the following, any sufficiently long word will
- // look like it will go to the user dictionary but it won't.
- // Safeguard against adding long words. Can cause stack overflow.
- if (word.length() >= MAX_WORD_LENGTH) return;
-
- Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT);
- intent.putExtra(Words.WORD, word);
- intent.putExtra(Words.LOCALE, mLocale);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
+ // Update the user dictionary provider
+ final Locale locale;
+ if (USER_DICTIONARY_ALL_LANGUAGES == mLocale) {
+ locale = null;
+ } else {
+ locale = LocaleUtils.constructLocaleFromString(mLocale);
+ }
+ UserDictionaryCompatUtils.addWord(mContext, word,
+ HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY, null, locale);
}
private int scaleFrequencyFromDefaultToLatinIme(final int defaultFrequency) {
@@ -258,10 +252,10 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
final int adjustedFrequency = scaleFrequencyFromDefaultToLatinIme(frequency);
// Safeguard against adding really long words.
if (word.length() < MAX_WORD_LENGTH) {
- super.addWord(word, null, adjustedFrequency);
+ super.addWord(word, null, adjustedFrequency, false /* isNotAWord */);
}
if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
- super.addWord(shortcut, word, adjustedFrequency);
+ super.addWord(shortcut, word, adjustedFrequency, true /* isNotAWord */);
}
cursor.moveToNext();
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index f7cb4346a..51bd901fb 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -27,6 +27,7 @@ import java.util.Arrays;
*/
public final class WordComposer {
private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
+ private static final boolean DBG = LatinImeLogger.sDBG;
public static final int CAPS_MODE_OFF = 0;
// 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits
@@ -42,6 +43,13 @@ public final class WordComposer {
private String mAutoCorrection;
private boolean mIsResumed;
private boolean mIsBatchMode;
+ // A memory of the last rejected batch mode suggestion, if any. This goes like this: the user
+ // gestures a word, is displeased with the results and hits backspace, then gestures again.
+ // At the very least we should avoid re-suggesting the same thing, and to do that we memorize
+ // the rejected suggestion in this variable.
+ // TODO: this should be done in a comprehensive way by the User History feature instead of
+ // as an ad-hockery here.
+ private String mRejectedBatchModeSuggestion;
// Cache these values for performance
private int mCapsCount;
@@ -49,6 +57,7 @@ public final class WordComposer {
private int mCapitalizedMode;
private int mTrailingSingleQuotesCount;
private int mCodePointSize;
+ private int mCursorPositionWithinWord;
/**
* Whether the user chose to capitalize the first char of the word.
@@ -62,6 +71,8 @@ public final class WordComposer {
mTrailingSingleQuotesCount = 0;
mIsResumed = false;
mIsBatchMode = false;
+ mCursorPositionWithinWord = 0;
+ mRejectedBatchModeSuggestion = null;
refreshSize();
}
@@ -76,6 +87,8 @@ public final class WordComposer {
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
mIsResumed = source.mIsResumed;
mIsBatchMode = source.mIsBatchMode;
+ mCursorPositionWithinWord = source.mCursorPositionWithinWord;
+ mRejectedBatchModeSuggestion = source.mRejectedBatchModeSuggestion;
refreshSize();
}
@@ -91,6 +104,8 @@ public final class WordComposer {
mTrailingSingleQuotesCount = 0;
mIsResumed = false;
mIsBatchMode = false;
+ mCursorPositionWithinWord = 0;
+ mRejectedBatchModeSuggestion = null;
refreshSize();
}
@@ -118,6 +133,13 @@ public final class WordComposer {
return mPrimaryKeyCodes[index];
}
+ public int getCodeBeforeCursor() {
+ if (mCursorPositionWithinWord < 1 || mCursorPositionWithinWord > mPrimaryKeyCodes.length) {
+ return Constants.NOT_A_CODE;
+ }
+ return mPrimaryKeyCodes[mCursorPositionWithinWord - 1];
+ }
+
public InputPointers getInputPointers() {
return mInputPointers;
}
@@ -135,6 +157,7 @@ public final class WordComposer {
final int newIndex = size();
mTypedWord.appendCodePoint(primaryCode);
refreshSize();
+ mCursorPositionWithinWord = mCodePointSize;
if (newIndex < MAX_WORD_LENGTH) {
mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE
? Character.toLowerCase(primaryCode) : primaryCode;
@@ -158,6 +181,18 @@ public final class WordComposer {
mAutoCorrection = null;
}
+ public void setCursorPositionWithinWord(final int posWithinWord) {
+ mCursorPositionWithinWord = posWithinWord;
+ }
+
+ public boolean isCursorFrontOrMiddleOfComposingWord() {
+ if (DBG && mCursorPositionWithinWord > mCodePointSize) {
+ throw new RuntimeException("Wrong cursor position : " + mCursorPositionWithinWord
+ + "in a word of size " + mCodePointSize);
+ }
+ return mCursorPositionWithinWord != mCodePointSize;
+ }
+
public void setBatchInputPointers(final InputPointers batchPointers) {
mInputPointers.set(batchPointers);
mIsBatchMode = true;
@@ -242,6 +277,7 @@ public final class WordComposer {
++mTrailingSingleQuotesCount;
}
}
+ mCursorPositionWithinWord = mCodePointSize;
mAutoCorrection = null;
}
@@ -368,7 +404,9 @@ public final class WordComposer {
mCapitalizedMode = CAPS_MODE_OFF;
refreshSize();
mAutoCorrection = null;
+ mCursorPositionWithinWord = 0;
mIsResumed = false;
+ mRejectedBatchModeSuggestion = null;
return lastComposedWord;
}
@@ -380,10 +418,20 @@ public final class WordComposer {
refreshSize();
mCapitalizedMode = lastComposedWord.mCapitalizedMode;
mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
+ mCursorPositionWithinWord = mCodePointSize;
+ mRejectedBatchModeSuggestion = null;
mIsResumed = true;
}
public boolean isBatchMode() {
return mIsBatchMode;
}
+
+ public void setRejectedBatchModeSuggestion(final String rejectedSuggestion) {
+ mRejectedBatchModeSuggestion = rejectedSuggestion;
+ }
+
+ public String getRejectedBatchModeSuggestion() {
+ return mRejectedBatchModeSuggestion;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 58ec1e83f..467f6a053 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -1467,8 +1467,8 @@ public final class BinaryDictInputOutput {
if (null == last) continue;
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
buffer.position(last.mChildrenAddress + headerSize);
- groupOffset = last.mChildrenAddress + 1;
- i = buffer.readUnsignedByte();
+ i = readCharGroupCount(buffer);
+ groupOffset = last.mChildrenAddress + getGroupCountSize(i);
last = null;
continue;
}
@@ -1477,8 +1477,8 @@ public final class BinaryDictInputOutput {
if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
buffer.position(last.mChildrenAddress + headerSize);
- groupOffset = last.mChildrenAddress + 1;
- i = buffer.readUnsignedByte();
+ i = readCharGroupCount(buffer);
+ groupOffset = last.mChildrenAddress + getGroupCountSize(i);
last = null;
continue;
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index e7c7e2b8a..17d281518 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -647,7 +647,7 @@ public final class FusionDictionary implements Iterable<Word> {
if (index < codePoints.length) return null;
if (!currentGroup.isTerminal()) return null;
- if (DBG && !codePoints.equals(checker.toString())) return null;
+ if (DBG && !string.equals(checker.toString())) return null;
return currentGroup;
}
@@ -853,16 +853,19 @@ public final class FusionDictionary implements Iterable<Word> {
if (currentPos.pos.hasNext()) {
final CharGroup currentGroup = currentPos.pos.next();
currentPos.length = mCurrentString.length();
- for (int i : currentGroup.mChars)
+ for (int i : currentGroup.mChars) {
mCurrentString.append(Character.toChars(i));
+ }
if (null != currentGroup.mChildren) {
currentPos = new Position(currentGroup.mChildren.mData);
+ currentPos.length = mCurrentString.length();
mPositions.addLast(currentPos);
}
- if (currentGroup.mFrequency >= 0)
+ if (currentGroup.mFrequency >= 0) {
return new Word(mCurrentString.toString(), currentGroup.mFrequency,
currentGroup.mShortcutTargets, currentGroup.mBigrams,
currentGroup.mIsNotAWord, currentGroup.mIsBlacklistEntry);
+ }
} else {
mPositions.removeLast();
currentPos = mPositions.getLast();
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 8d3b062ff..2d0a89bb3 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -330,7 +330,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
} else if (StringUtils.CAPITALIZE_FIRST == capitalizeType) {
for (int i = 0; i < mSuggestions.size(); ++i) {
// Likewise
- mSuggestions.set(i, StringUtils.toTitleCase(
+ mSuggestions.set(i, StringUtils.capitalizeFirstCodePoint(
mSuggestions.get(i).toString(), locale));
}
}
@@ -403,11 +403,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
public DictAndProximity createDictAndProximity(final Locale locale) {
final int script = getScriptFromLocale(locale);
- final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo(
- SpellCheckerProximityInfo.getProximityForScript(script),
- SpellCheckerProximityInfo.ROW_SIZE,
- SpellCheckerProximityInfo.PROXIMITY_GRID_WIDTH,
- SpellCheckerProximityInfo.PROXIMITY_GRID_HEIGHT);
+ final ProximityInfo proximityInfo = new SpellCheckerProximityInfo(script);
final DictionaryCollection dictionaryCollection =
DictionaryFactory.createMainDictionaryFromManager(this, locale,
true /* useFullEditDistance */);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index b15063235..da8657201 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -189,10 +189,12 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
int letterCount = 0;
for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
final int codePoint = text.codePointAt(i);
- // Any word containing a '@' is probably an e-mail address
- // Any word containing a '/' is probably either an ad-hoc combination of two
+ // Any word containing a COMMERCIAL_AT is probably an e-mail address
+ // Any word containing a SLASH is probably either an ad-hoc combination of two
// words or a URI - in either case we don't want to spell check that
- if ('@' == codePoint || '/' == codePoint) return true;
+ if (Constants.CODE_COMMERCIAL_AT == codePoint || Constants.CODE_SLASH == codePoint) {
+ return true;
+ }
if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount;
}
// Guestimate heuristic: perform spell checking if at least 3/4 of the characters
@@ -226,7 +228,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
// If the lower case version is not in the dictionary, it's still possible
// that we have an all-caps version of a word that needs to be capitalized
// according to the dictionary. E.g. "GERMANS" only exists in the dictionary as "Germans".
- return dict.isValidWord(StringUtils.toTitleCase(lowerCaseText, mLocale));
+ return dict.isValidWord(StringUtils.capitalizeFirstAndDowncaseRest(lowerCaseText, mLocale));
}
// Note : this must be reentrant
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 49dca21e6..0c480eaba 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -16,53 +16,40 @@
package com.android.inputmethod.latin.spellcheck;
-import com.android.inputmethod.annotations.UsedForTesting;
+import android.util.SparseIntArray;
+
import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
-import java.util.TreeMap;
+public final class SpellCheckerProximityInfo extends ProximityInfo {
+ public SpellCheckerProximityInfo(final int script) {
+ super(getProximityForScript(script), PROXIMITY_GRID_WIDTH, PROXIMITY_GRID_HEIGHT);
+ }
-public final class SpellCheckerProximityInfo {
- @UsedForTesting
- final public static int NUL = Constants.NOT_A_CODE;
+ private static final int NUL = Constants.NOT_A_CODE;
// This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
// native code - this value is passed at creation of the binary object and reused
// as the size of the passed array afterwards so they can't be different.
- final public static int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
+ private static final int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
// The number of keys in a row of the grid used by the spell checker.
- final public static int PROXIMITY_GRID_WIDTH = 11;
+ private static final int PROXIMITY_GRID_WIDTH = 11;
// The number of rows in the grid used by the spell checker.
- final public static int PROXIMITY_GRID_HEIGHT = 3;
+ private static final int PROXIMITY_GRID_HEIGHT = 3;
- final private static int NOT_AN_INDEX = -1;
- final public static int NOT_A_COORDINATE_PAIR = -1;
+ private static final int NOT_AN_INDEX = -1;
+ public static final int NOT_A_COORDINATE_PAIR = -1;
// Helper methods
- final protected static void buildProximityIndices(final int[] proximity,
- final TreeMap<Integer, Integer> indices) {
- for (int i = 0; i < proximity.length; i += ROW_SIZE) {
- if (NUL != proximity[i]) indices.put(proximity[i], i / ROW_SIZE);
+ static void buildProximityIndices(final int[] proximity, final int rowSize,
+ final SparseIntArray indices) {
+ for (int i = 0; i < proximity.length; i += rowSize) {
+ if (NUL != proximity[i]) indices.put(proximity[i], i / rowSize);
}
}
- final protected static int computeIndex(final int characterCode,
- final TreeMap<Integer, Integer> indices) {
- final Integer result = indices.get(characterCode);
- if (null == result) return NOT_AN_INDEX;
- return result;
- }
private static final class Latin {
- // This is a map from the code point to the index in the PROXIMITY array.
- // At the time the native code to read the binary dictionary needs the proximity info be
- // passed as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input
- // character.
- // Since we need to build such an array, we want to be able to search in our big proximity
- // data quickly by character, and a map is probably the best way to do this.
- final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
-
// The proximity here is the union of
// - the proximity for a QWERTY keyboard.
// - the proximity for an AZERTY keyboard.
@@ -79,7 +66,7 @@ public final class SpellCheckerProximityInfo {
a s d f g h j k l
z x c v b n m
*/
- final static int[] PROXIMITY = {
+ static final int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
// The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
@@ -121,16 +108,21 @@ public final class SpellCheckerProximityInfo {
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
+
+ // This is a mapping array from the code point to the index in the PROXIMITY array.
+ // When we check the spelling of a word, we need to pass (x,y) coordinates to the native
+ // code for each letter of the word. These are most easily computed from the index in the
+ // PROXIMITY array. Since we'll need to do that very often, the index lookup from the code
+ // point needs to be as fast as possible, and a map is probably the best way to do this.
+ // To avoid unnecessary boxing conversion to Integer, here we use SparseIntArray.
+ static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
+
static {
- buildProximityIndices(PROXIMITY, INDICES);
- }
- static int getIndexOf(int characterCode) {
- return computeIndex(characterCode, INDICES);
+ buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
}
}
private static final class Cyrillic {
- final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
// speakers on commonly misspelled words/letters.
/*
@@ -207,7 +199,7 @@ public final class SpellCheckerProximityInfo {
private static final int CY_SOFT_SIGN = '\u044C'; // ь
private static final int CY_BE = '\u0431'; // б
private static final int CY_YU = '\u044E'; // ю
- final static int[] PROXIMITY = {
+ static final int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
// The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
@@ -280,16 +272,15 @@ public final class SpellCheckerProximityInfo {
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
+
+ static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
+
static {
- buildProximityIndices(PROXIMITY, INDICES);
- }
- static int getIndexOf(int characterCode) {
- return computeIndex(characterCode, INDICES);
+ buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
}
}
private static final class Greek {
- final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// TODO: The following table is solely based on the keyboard layout. Consult with Greek
// speakers on commonly misspelled words/letters.
/*
@@ -354,7 +345,7 @@ public final class SpellCheckerProximityInfo {
private static final int GR_BETA = '\u03B2'; // β
private static final int GR_NU = '\u03BD'; // ν
private static final int GR_MU = '\u03BC'; // μ
- final static int[] PROXIMITY = {
+ static final int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
// The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
@@ -419,37 +410,37 @@ public final class SpellCheckerProximityInfo {
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
+
+ static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
+
static {
- buildProximityIndices(PROXIMITY, INDICES);
- }
- static int getIndexOf(int characterCode) {
- return computeIndex(characterCode, INDICES);
+ buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
}
}
- public static int[] getProximityForScript(final int script) {
+ private static int[] getProximityForScript(final int script) {
switch (script) {
- case AndroidSpellCheckerService.SCRIPT_LATIN:
- return Latin.PROXIMITY;
- case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
- return Cyrillic.PROXIMITY;
- case AndroidSpellCheckerService.SCRIPT_GREEK:
- return Greek.PROXIMITY;
- default:
- throw new RuntimeException("Wrong script supplied: " + script);
+ case AndroidSpellCheckerService.SCRIPT_LATIN:
+ return Latin.PROXIMITY;
+ case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+ return Cyrillic.PROXIMITY;
+ case AndroidSpellCheckerService.SCRIPT_GREEK:
+ return Greek.PROXIMITY;
+ default:
+ throw new RuntimeException("Wrong script supplied: " + script);
}
}
private static int getIndexOfCodeForScript(final int codePoint, final int script) {
switch (script) {
- case AndroidSpellCheckerService.SCRIPT_LATIN:
- return Latin.getIndexOf(codePoint);
- case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
- return Cyrillic.getIndexOf(codePoint);
- case AndroidSpellCheckerService.SCRIPT_GREEK:
- return Greek.getIndexOf(codePoint);
- default:
- throw new RuntimeException("Wrong script supplied: " + script);
+ case AndroidSpellCheckerService.SCRIPT_LATIN:
+ return Latin.INDICES.get(codePoint, NOT_AN_INDEX);
+ case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+ return Cyrillic.INDICES.get(codePoint, NOT_AN_INDEX);
+ case AndroidSpellCheckerService.SCRIPT_GREEK:
+ return Greek.INDICES.get(codePoint, NOT_AN_INDEX);
+ default:
+ throw new RuntimeException("Wrong script supplied: " + script);
}
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index ed408bb3c..3037669c0 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -16,12 +16,14 @@
package com.android.inputmethod.latin.suggestions;
+import android.content.Context;
import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.TypefaceUtils;
import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -50,16 +52,12 @@ public final class MoreSuggestions extends Keyboard {
super();
}
- // TODO: Remove {@link MoreSuggestionsView} argument.
public int layout(final SuggestedWords suggestions, final int fromPos, final int maxWidth,
- final int minWidth, final int maxRow, final MoreSuggestionsView view) {
+ final int minWidth, final int maxRow, final Paint paint, final Resources res) {
clearKeys();
- final Resources res = view.getResources();
mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
mDividerWidth = mDivider.getIntrinsicWidth();
- final int padding = (int) res.getDimension(
- R.dimen.more_suggestions_key_horizontal_padding);
- final Paint paint = view.newDefaultLabelPaint();
+ final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding);
int row = 0;
int pos = fromPos, rowStartPos = fromPos;
@@ -67,7 +65,7 @@ public final class MoreSuggestions extends Keyboard {
while (pos < size) {
final String word = suggestions.getWord(pos);
// TODO: Should take care of text x-scaling.
- mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
+ mWidths[pos] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
final int numColumn = pos - rowStartPos + 1;
final int columnWidth =
(maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
@@ -169,8 +167,8 @@ public final class MoreSuggestions extends Keyboard {
private int mFromPos;
private int mToPos;
- public Builder(final MoreSuggestionsView paneView) {
- super(paneView.getContext(), new MoreSuggestionsParam());
+ public Builder(final Context context, final MoreSuggestionsView paneView) {
+ super(context, new MoreSuggestionsParam());
mPaneView = paneView;
}
@@ -183,7 +181,7 @@ public final class MoreSuggestions extends Keyboard {
mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow,
- mPaneView);
+ mPaneView.newLabelPaint(null /* key */), mResources);
mFromPos = fromPos;
mToPos = fromPos + count;
mSuggestions = suggestions;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 438820d17..6509f394b 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -43,7 +43,13 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
}
public void updateKeyboardGeometry(final int keyHeight) {
- mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
+ updateKeyDrawParams(keyHeight);
+ }
+
+ public void adjustVerticalCorrectionForModalMode() {
+ // Set vertical correction to zero (Reset more keys keyboard sliding allowance
+ // {@link R#dimen.more_keys_keyboard_slide_allowance}).
+ mKeyDetector.setKeyboard(getKeyboard(), -getPaddingLeft(), -getPaddingTop());
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index eeaf828a7..2a21ec2f5 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -596,7 +596,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null);
mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer
.findViewById(R.id.more_suggestions_view);
- mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView);
+ mMoreSuggestionsBuilder = new MoreSuggestions.Builder(context, mMoreSuggestionsView);
final Resources res = context.getResources();
mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset(
@@ -755,8 +755,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public boolean dispatchTouchEvent(final MotionEvent me) {
- if (!mMoreSuggestionsView.isShowingInParent()
- || mMoreSuggestionsMode == MORE_SUGGESTIONS_IN_MODAL_MODE) {
+ if (!mMoreSuggestionsView.isShowingInParent()) {
mLastX = (int)me.getX();
mLastY = (int)me.getY();
if (mMoreSuggestionsSlidingDetector.onTouchEvent(me)) {
@@ -784,6 +783,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
// Decided to be in the modal input mode
mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE;
+ mMoreSuggestionsView.adjustVerticalCorrectionForModalMode();
}
return true;
}