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/AudioAndHapticFeedbackManager.java104
-rw-r--r--java/src/com/android/inputmethod/latin/AutoCorrection.java86
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java47
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java6
-rw-r--r--java/src/com/android/inputmethod/latin/ComposingStateManager.java68
-rw-r--r--java/src/com/android/inputmethod/latin/DebugSettings.java6
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java11
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java22
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java14
-rw-r--r--java/src/com/android/inputmethod/latin/EditingUtils.java17
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java8
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java166
-rw-r--r--java/src/com/android/inputmethod/latin/JniUtils.java41
-rw-r--r--java/src/com/android/inputmethod/latin/LastComposedWord.java86
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java1798
-rw-r--r--java/src/com/android/inputmethod/latin/LatinImeLogger.java22
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java363
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsValues.java362
-rw-r--r--java/src/com/android/inputmethod/latin/StringBuilderPool.java70
-rw-r--r--java/src/com/android/inputmethod/latin/StringUtils.java190
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java11
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeUtils.java135
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java360
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java228
-rw-r--r--java/src/com/android/inputmethod/latin/TextEntryState.java237
-rw-r--r--java/src/com/android/inputmethod/latin/UserBigramDictionary.java42
-rw-r--r--java/src/com/android/inputmethod/latin/UserDictionary.java75
-rw-r--r--java/src/com/android/inputmethod/latin/UserUnigramDictionary.java38
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java573
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java240
-rw-r--r--java/src/com/android/inputmethod/latin/XmlParseUtils.java76
-rw-r--r--java/src/com/android/inputmethod/latin/define/JniLibName.java21
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java266
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java198
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java (renamed from java/src/com/android/inputmethod/latin/MoreSuggestions.java)59
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java (renamed from java/src/com/android/inputmethod/latin/MoreSuggestionsView.java)18
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java (renamed from java/src/com/android/inputmethod/latin/SuggestionsView.java)335
37 files changed, 3516 insertions, 2883 deletions
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
new file mode 100644
index 000000000..9c5ccc76b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2012 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;
+import android.media.AudioManager;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+
+import com.android.inputmethod.compat.VibratorCompatWrapper;
+import com.android.inputmethod.keyboard.Keyboard;
+
+/**
+ * This class gathers audio feedback and haptic feedback functions.
+ *
+ * It offers a consistent and simple interface that allows LatinIME to forget about the
+ * complexity of settings and the like.
+ */
+public class AudioAndHapticFeedbackManager {
+ final private SettingsValues mSettingsValues;
+ final private AudioManager mAudioManager;
+ final private VibratorCompatWrapper mVibrator;
+ private boolean mSoundOn;
+
+ public AudioAndHapticFeedbackManager(final LatinIME latinIme,
+ final SettingsValues settingsValues) {
+ mSettingsValues = settingsValues;
+ mVibrator = VibratorCompatWrapper.getInstance(latinIme);
+ mAudioManager = (AudioManager) latinIme.getSystemService(Context.AUDIO_SERVICE);
+ mSoundOn = reevaluateIfSoundIsOn();
+ }
+
+ public void hapticAndAudioFeedback(final int primaryCode,
+ final View viewToPerformHapticFeedbackOn) {
+ vibrate(viewToPerformHapticFeedbackOn);
+ playKeyClick(primaryCode);
+ }
+
+ private boolean reevaluateIfSoundIsOn() {
+ if (!mSettingsValues.mSoundOn || mAudioManager == null) {
+ return false;
+ } else {
+ return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL;
+ }
+ }
+
+ private void playKeyClick(int primaryCode) {
+ // if mAudioManager is null, we can't play a sound anyway, so return
+ if (mAudioManager == null) return;
+ if (mSoundOn) {
+ final int sound;
+ switch (primaryCode) {
+ case Keyboard.CODE_DELETE:
+ sound = AudioManager.FX_KEYPRESS_DELETE;
+ break;
+ case Keyboard.CODE_ENTER:
+ sound = AudioManager.FX_KEYPRESS_RETURN;
+ break;
+ case Keyboard.CODE_SPACE:
+ sound = AudioManager.FX_KEYPRESS_SPACEBAR;
+ break;
+ default:
+ sound = AudioManager.FX_KEYPRESS_STANDARD;
+ break;
+ }
+ mAudioManager.playSoundEffect(sound, mSettingsValues.mFxVolume);
+ }
+ }
+
+ // TODO: make this private when LatinIME does not call it any more
+ public void vibrate(final View viewToPerformHapticFeedbackOn) {
+ if (!mSettingsValues.mVibrateOn) {
+ return;
+ }
+ if (mSettingsValues.mKeypressVibrationDuration < 0) {
+ // Go ahead with the system default
+ if (viewToPerformHapticFeedbackOn != null) {
+ viewToPerformHapticFeedbackOn.performHapticFeedback(
+ HapticFeedbackConstants.KEYBOARD_TAP,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+ }
+ } else if (mVibrator != null) {
+ mVibrator.vibrate(mSettingsValues.mKeypressVibrationDuration);
+ }
+ }
+
+ public void onRingerModeChanged() {
+ mSoundOn = reevaluateIfSoundIsOn();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
index 485ec511f..9c35f8f6f 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -25,44 +25,25 @@ import java.util.Map;
public class AutoCorrection {
private static final boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = AutoCorrection.class.getSimpleName();
- private boolean mHasAutoCorrection;
- private CharSequence mAutoCorrectionWord;
- private double mNormalizedScore;
- public void init() {
- mHasAutoCorrection = false;
- mAutoCorrectionWord = null;
- mNormalizedScore = Integer.MIN_VALUE;
+ private AutoCorrection() {
+ // Purely static class: can't instantiate.
}
- public boolean hasAutoCorrection() {
- return mHasAutoCorrection;
- }
-
- public CharSequence getAutoCorrectionWord() {
- return mAutoCorrectionWord;
- }
-
- public double getNormalizedScore() {
- return mNormalizedScore;
- }
-
- public void updateAutoCorrectionStatus(Map<String, Dictionary> dictionaries,
+ public static CharSequence computeAutoCorrectionWord(Map<String, Dictionary> dictionaries,
WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] sortedScores,
- CharSequence typedWord, double autoCorrectionThreshold, int correctionMode,
+ CharSequence consideredWord, double autoCorrectionThreshold,
CharSequence whitelistedWord) {
if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) {
- mHasAutoCorrection = true;
- mAutoCorrectionWord = whitelistedWord;
- } else if (hasAutoCorrectionForTypedWord(
- dictionaries, wordComposer, suggestions, typedWord, correctionMode)) {
- mHasAutoCorrection = true;
- mAutoCorrectionWord = typedWord;
- } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode,
- sortedScores, typedWord, autoCorrectionThreshold)) {
- mHasAutoCorrection = true;
- mAutoCorrectionWord = suggestions.get(0);
+ return whitelistedWord;
+ } else if (hasAutoCorrectionForConsideredWord(
+ dictionaries, wordComposer, suggestions, consideredWord)) {
+ return consideredWord;
+ } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions,
+ sortedScores, consideredWord, autoCorrectionThreshold)) {
+ return suggestions.get(0);
}
+ return null;
}
public static boolean isValidWord(
@@ -74,6 +55,14 @@ public class AutoCorrection {
for (final String key : dictionaries.keySet()) {
if (key.equals(Suggest.DICT_KEY_WHITELIST)) continue;
final Dictionary dictionary = dictionaries.get(key);
+ // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
+ // managing to get null in here. Presumably the language is changing to a language with
+ // no main dictionary and the monkey manages to type a whole word before the thread
+ // that reads the dictionary is started or something?
+ // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
+ // would be immutable once it's finished initializing, but concretely a null test is
+ // probably good enough for the time being.
+ if (null == dictionary) continue;
if (dictionary.isValidWord(word)
|| (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
return true;
@@ -98,34 +87,31 @@ public class AutoCorrection {
return whiteListedWord != null;
}
- private boolean hasAutoCorrectionForTypedWord(Map<String, Dictionary> dictionaries,
- WordComposer wordComposer, ArrayList<CharSequence> suggestions, CharSequence typedWord,
- int correctionMode) {
- if (TextUtils.isEmpty(typedWord)) return false;
- boolean allowsAutoCorrect = allowsToBeAutoCorrected(dictionaries, typedWord, false);
- return wordComposer.size() > 1 && suggestions.size() > 0 && !allowsAutoCorrect
- && (correctionMode == Suggest.CORRECTION_FULL
- || correctionMode == Suggest.CORRECTION_FULL_BIGRAM);
+ private static boolean hasAutoCorrectionForConsideredWord(Map<String, Dictionary> dictionaries,
+ WordComposer wordComposer, ArrayList<CharSequence> suggestions,
+ CharSequence consideredWord) {
+ if (TextUtils.isEmpty(consideredWord)) return false;
+ return wordComposer.size() > 1 && suggestions.size() > 0
+ && !allowsToBeAutoCorrected(dictionaries, consideredWord, false);
}
- private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
- ArrayList<CharSequence> suggestions, int correctionMode, int[] sortedScores,
- CharSequence typedWord, double autoCorrectionThreshold) {
- if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL
- || correctionMode == Suggest.CORRECTION_FULL_BIGRAM)
- && typedWord != null && suggestions.size() > 0 && sortedScores.length > 0) {
+ private static boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
+ ArrayList<CharSequence> suggestions, int[] sortedScores,
+ CharSequence consideredWord, double autoCorrectionThreshold) {
+ if (wordComposer.size() > 1 && suggestions.size() > 0 && sortedScores.length > 0) {
final CharSequence autoCorrectionSuggestion = suggestions.get(0);
final int autoCorrectionSuggestionScore = sortedScores[0];
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
- mNormalizedScore = Utils.calcNormalizedScore(
- typedWord,autoCorrectionSuggestion, autoCorrectionSuggestionScore);
+ final double normalizedScore = BinaryDictionary.calcNormalizedScore(
+ consideredWord.toString(), autoCorrectionSuggestion.toString(),
+ autoCorrectionSuggestionScore);
if (DBG) {
- Log.d(TAG, "Normalized " + typedWord + "," + autoCorrectionSuggestion + ","
- + autoCorrectionSuggestionScore + ", " + mNormalizedScore
+ Log.d(TAG, "Normalized " + consideredWord + "," + autoCorrectionSuggestion + ","
+ + autoCorrectionSuggestionScore + ", " + normalizedScore
+ "(" + autoCorrectionThreshold + ")");
}
- if (mNormalizedScore >= autoCorrectionThreshold) {
+ if (normalizedScore >= autoCorrectionThreshold) {
if (DBG) {
Log.d(TAG, "Auto corrected by S-threshold.");
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b9fd57434..31ff4e7b4 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -46,7 +46,7 @@ public class BinaryDictionary extends Dictionary {
private static final int TYPED_LETTER_MULTIPLIER = 2;
private int mDicTypeId;
- private int mNativeDict;
+ private long mNativeDict;
private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
@@ -104,20 +104,24 @@ public class BinaryDictionary extends Dictionary {
}
static {
- Utils.loadNativeLibrary();
+ JniUtils.loadNativeLibrary();
}
- private native int openNative(String sourceDir, long dictOffset, long dictSize,
+ private native long openNative(String sourceDir, long dictOffset, long dictSize,
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength,
int maxWords, int maxAlternatives);
- private native void closeNative(int dict);
- private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
- private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates,
+ private native void closeNative(long dict);
+ private native boolean isValidWordNative(long dict, char[] word, int wordLength);
+ private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates,
int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars,
int[] scores);
- private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
+ private native int getBigramsNative(long dict, char[] prevWord, int prevWordLength,
int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
int maxWordLength, int maxBigrams, int maxAlternatives);
+ private static native double calcNormalizedScoreNative(
+ char[] before, int beforeLength, char[] after, int afterLength, int score);
+ private static native int editDistanceNative(
+ char[] before, int beforeLength, char[] after, int afterLength);
private final void loadDictionary(String path, long startOffset, long length) {
mNativeDict = openNative(path, startOffset, length,
@@ -135,22 +139,19 @@ public class BinaryDictionary extends Dictionary {
Arrays.fill(mBigramScores, 0);
int codesSize = codes.size();
- if (codesSize <= 0) {
- // Do not return bigrams from BinaryDictionary when nothing was typed.
- // Only use user-history bigrams (or whatever other bigram dictionaries decide).
- return;
- }
Arrays.fill(mInputCodes, -1);
- int[] alternatives = codes.getCodesAt(0);
- System.arraycopy(alternatives, 0, mInputCodes, 0,
- Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
+ if (codesSize > 0) {
+ int[] alternatives = codes.getCodesAt(0);
+ System.arraycopy(alternatives, 0, mInputCodes, 0,
+ Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
+ }
int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize,
mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS,
MAX_PROXIMITY_CHARS_SIZE);
for (int j = 0; j < count; ++j) {
- if (mBigramScores[j] < 1) break;
+ if (codesSize > 0 && mBigramScores[j] < 1) break;
final int start = j * MAX_WORD_LENGTH;
int len = 0;
while (len < MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) {
@@ -158,7 +159,7 @@ public class BinaryDictionary extends Dictionary {
}
if (len > 0) {
callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j],
- mDicTypeId, DataType.BIGRAM);
+ mDicTypeId, Dictionary.BIGRAM);
}
}
}
@@ -178,7 +179,7 @@ public class BinaryDictionary extends Dictionary {
}
if (len > 0) {
callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId,
- DataType.UNIGRAM);
+ Dictionary.UNIGRAM);
}
}
}
@@ -211,6 +212,16 @@ public class BinaryDictionary extends Dictionary {
mFlags, outputChars, scores);
}
+ public static double calcNormalizedScore(String before, String after, int score) {
+ return calcNormalizedScoreNative(before.toCharArray(), before.length(),
+ after.toCharArray(), after.length(), score);
+ }
+
+ public static int editDistance(String before, String after) {
+ return editDistanceNative(
+ before.toCharArray(), before.length(), after.toCharArray(), after.length());
+ }
+
@Override
public boolean isValidWord(CharSequence word) {
if (word == null) return false;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index b333e4873..79441c557 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -75,7 +75,8 @@ class BinaryDictionaryGetter {
// This assumes '%' is fully available as a non-separator, normal
// character in a file name. This is probably true for all file systems.
final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < name.length(); ++i) {
+ final int nameLength = name.length();
+ for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) {
final int codePoint = name.codePointAt(i);
if (isFileNameCharacter(codePoint)) {
sb.appendCodePoint(codePoint);
@@ -92,7 +93,8 @@ class BinaryDictionaryGetter {
*/
private static String getWordListIdFromFileName(final String fname) {
final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < fname.length(); ++i) {
+ final int fnameLength = fname.length();
+ for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) {
final int codePoint = fname.codePointAt(i);
if ('%' != codePoint) {
sb.appendCodePoint(codePoint);
diff --git a/java/src/com/android/inputmethod/latin/ComposingStateManager.java b/java/src/com/android/inputmethod/latin/ComposingStateManager.java
deleted file mode 100644
index 8811f2023..000000000
--- a/java/src/com/android/inputmethod/latin/ComposingStateManager.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * Copyright (C) 2011 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.util.Log;
-
-public class ComposingStateManager {
- private static final String TAG = ComposingStateManager.class.getSimpleName();
- private static final ComposingStateManager sInstance = new ComposingStateManager();
- private boolean mAutoCorrectionIndicatorOn;
- private boolean mIsComposing;
-
- public static ComposingStateManager getInstance() {
- return sInstance;
- }
-
- private ComposingStateManager() {
- mAutoCorrectionIndicatorOn = false;
- mIsComposing = false;
- }
-
- public synchronized void onStartComposingText() {
- if (!mIsComposing) {
- if (LatinImeLogger.sDBG) {
- Log.i(TAG, "Start composing text.");
- }
- mAutoCorrectionIndicatorOn = false;
- mIsComposing = true;
- }
- }
-
- public synchronized void onFinishComposingText() {
- if (mIsComposing) {
- if (LatinImeLogger.sDBG) {
- Log.i(TAG, "Finish composing text.");
- }
- mAutoCorrectionIndicatorOn = false;
- mIsComposing = false;
- }
- }
-
- public synchronized boolean isAutoCorrectionIndicatorOn() {
- return mAutoCorrectionIndicatorOn;
- }
-
- public synchronized void setAutoCorrectionIndicatorOn(boolean on) {
- // Auto-correction indicator should be specified only when the current state is "composing".
- if (!mIsComposing) return;
- if (LatinImeLogger.sDBG) {
- Log.i(TAG, "Set auto correction Indicator: " + on);
- }
- mAutoCorrectionIndicatorOn = on;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 2f1e7c2b8..870b33f9a 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -25,11 +25,14 @@ import android.preference.CheckBoxPreference;
import android.preference.PreferenceActivity;
import android.util.Log;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+
public class DebugSettings extends PreferenceActivity
implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "DebugSettings";
private static final String DEBUG_MODE_KEY = "debug_mode";
+ public static final String FORCE_NON_DISTINCT_MULTITOUCH_KEY = "force_non_distinct_multitouch";
private boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
@@ -60,6 +63,9 @@ public class DebugSettings extends PreferenceActivity
updateDebugMode();
mServiceNeedsRestart = true;
}
+ } else if (key.equals(FORCE_NON_DISTINCT_MULTITOUCH_KEY)
+ || key.equals(KeyboardSwitcher.PREF_KEYBOARD_LAYOUT)) {
+ mServiceNeedsRestart = true;
}
}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index c35b42877..79bf33850 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -33,9 +33,8 @@ public abstract class Dictionary {
*/
protected static final int FULL_WORD_SCORE_MULTIPLIER = 2;
- public static enum DataType {
- UNIGRAM, BIGRAM
- }
+ public static final int UNIGRAM = 0;
+ public static final int BIGRAM = 1;
/**
* Interface to be implemented by classes requesting words to be fetched from the dictionary.
@@ -51,11 +50,11 @@ public abstract class Dictionary {
* @param score the score of occurrence. This is normalized between 1 and 255, but
* can exceed those limits
* @param dicTypeId of the dictionary where word was from
- * @param dataType tells type of this data
+ * @param dataType tells type of this data, either UNIGRAM or BIGRAM
* @return true if the word was added, false if no more words are required
*/
boolean addWord(char[] word, int wordOffset, int wordLength, int score, int dicTypeId,
- DataType dataType);
+ int dataType);
}
/**
@@ -64,7 +63,7 @@ public abstract class Dictionary {
* @param composer the key sequence to match
* @param callback the callback object to send matched words to as possible candidates
* @param proximityInfo the object for key proximity. May be ignored by some implementations.
- * @see WordCallback#addWord(char[], int, int, int, int, DataType)
+ * @see WordCallback#addWord(char[], int, int, int, int, int)
*/
abstract public void getWords(final WordComposer composer, final WordCallback callback,
final ProximityInfo proximityInfo);
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 739153044..c19a5a718 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -18,6 +18,8 @@ package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.ProximityInfo;
+import android.util.Log;
+
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -27,7 +29,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
* Class for a collection of dictionaries that behave like one dictionary.
*/
public class DictionaryCollection extends Dictionary {
-
+ private final String TAG = DictionaryCollection.class.getSimpleName();
protected final List<Dictionary> mDictionaries;
public DictionaryCollection() {
@@ -75,7 +77,21 @@ public class DictionaryCollection extends Dictionary {
dict.close();
}
- public void addDictionary(Dictionary newDict) {
- if (null != newDict) mDictionaries.add(newDict);
+ // Warning: this is not thread-safe. Take necessary precaution when calling.
+ public void addDictionary(final Dictionary newDict) {
+ if (null == newDict) return;
+ if (mDictionaries.contains(newDict)) {
+ Log.w(TAG, "This collection already contains this dictionary: " + newDict);
+ }
+ mDictionaries.add(newDict);
+ }
+
+ // Warning: this is not thread-safe. Take necessary precaution when calling.
+ public void removeDictionary(final Dictionary dict) {
+ if (mDictionaries.contains(dict)) {
+ mDictionaries.remove(dict);
+ } else {
+ Log.w(TAG, "This collection does not contain this dictionary: " + dict);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 1607f86a8..7a81f7bd5 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -164,7 +164,7 @@ public class DictionaryFactory {
final Resources res = context.getResources();
final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
- final int resourceId = Utils.getMainDictionaryResourceId(res);
+ final int resourceId = getMainDictionaryResourceId(res);
final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
final boolean hasDictionary = isFullDictionary(afd);
try {
@@ -182,7 +182,7 @@ public class DictionaryFactory {
final Resources res = context.getResources();
final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale);
- final int resourceId = Utils.getMainDictionaryResourceId(res);
+ final int resourceId = getMainDictionaryResourceId(res);
final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
? afd.getLength()
@@ -209,4 +209,14 @@ public class DictionaryFactory {
protected static boolean isFullDictionary(final AssetFileDescriptor afd) {
return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH);
}
+
+ /**
+ * Returns a main dictionary resource id
+ * @return main dictionary resource id
+ */
+ public static int getMainDictionaryResourceId(Resources res) {
+ final String MAIN_DIC_NAME = "main";
+ String packageName = LatinIME.class.getPackage().getName();
+ return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java
index 634dbbdfc..1e8ad1840 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtils.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtils.java
@@ -87,23 +87,6 @@ public class EditingUtils {
}
/**
- * Removes the word surrounding the cursor. Parameters are identical to
- * getWordAtCursor.
- */
- public static void deleteWordAtCursor(InputConnection connection, String separators) {
- // getWordRangeAtCursor returns null if the connection is null
- Range range = getWordRangeAtCursor(connection, separators);
- if (range == null) return;
-
- connection.finishComposingText();
- // Move cursor to beginning of word, to avoid crash when cursor is outside
- // of valid range after deleting text.
- int newCursor = getCursorPosition(connection) - range.mCharsBefore;
- connection.setSelection(newCursor, newCursor);
- connection.deleteSurroundingText(0, range.mCharsBefore + range.mCharsAfter);
- }
-
- /**
* Represents a range of text, relative to the current cursor position.
*/
public static class Range {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index cad69bb0e..8e8adc1c2 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -51,6 +51,7 @@ public class ExpandableDictionary extends Dictionary {
private Object mUpdatingLock = new Object();
private static class Node {
+ Node() {}
char mCode;
int mFrequency;
boolean mTerminal;
@@ -300,7 +301,7 @@ public class ExpandableDictionary extends Dictionary {
finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
}
if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
- DataType.UNIGRAM)) {
+ Dictionary.UNIGRAM)) {
return;
}
}
@@ -341,7 +342,7 @@ public class ExpandableDictionary extends Dictionary {
snr * addedAttenuation, mInputLength);
}
callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
- DataType.UNIGRAM);
+ Dictionary.UNIGRAM);
}
}
if (children != null) {
@@ -505,7 +506,7 @@ public class ExpandableDictionary extends Dictionary {
} while (node != null);
callback.addWord(mLookedUpString, index, MAX_WORD_LENGTH - index, freq, mDicTypeId,
- DataType.BIGRAM);
+ Dictionary.BIGRAM);
}
}
@@ -547,6 +548,7 @@ public class ExpandableDictionary extends Dictionary {
}
private class LoadDictionaryTask extends Thread {
+ LoadDictionaryTask() {}
@Override
public void run() {
loadDictionaryAsync();
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
new file mode 100644
index 000000000..f7afed2a5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2011 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.InputType;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+
+/**
+ * Class to hold attributes of the input field.
+ */
+public class InputAttributes {
+ private final String TAG = InputAttributes.class.getSimpleName();
+
+ final public boolean mInputTypeNoAutoCorrect;
+ final public boolean mIsSettingsSuggestionStripOn;
+ final public boolean mApplicationSpecifiedCompletionOn;
+
+ public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
+ final int inputType = null != editorInfo ? editorInfo.inputType : 0;
+ final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
+ if (inputClass != InputType.TYPE_CLASS_TEXT) {
+ // If we are not looking at a TYPE_CLASS_TEXT field, the following strange
+ // cases may arise, so we do a couple sanity checks for them. If it's a
+ // TYPE_CLASS_TEXT field, these special cases cannot happen, by construction
+ // of the flags.
+ if (null == editorInfo) {
+ Log.w(TAG, "No editor info for this field. Bug?");
+ } else if (InputType.TYPE_NULL == inputType) {
+ // TODO: We should honor TYPE_NULL specification.
+ Log.i(TAG, "InputType.TYPE_NULL is specified");
+ } else if (inputClass == 0) {
+ // TODO: is this check still necessary?
+ Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
+ + " imeOptions=0x%08x",
+ inputType, editorInfo.imeOptions));
+ }
+ mIsSettingsSuggestionStripOn = false;
+ mInputTypeNoAutoCorrect = false;
+ mApplicationSpecifiedCompletionOn = false;
+ } else {
+ final int variation = inputType & InputType.TYPE_MASK_VARIATION;
+ final boolean flagNoSuggestions =
+ 0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ final boolean flagMultiLine =
+ 0 != (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE);
+ final boolean flagAutoCorrect =
+ 0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
+ final boolean flagAutoComplete =
+ 0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
+
+ // Make sure that passwords are not displayed in {@link SuggestionsView}.
+ if (InputTypeCompatUtils.isPasswordInputType(inputType)
+ || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)
+ || InputTypeCompatUtils.isEmailVariation(variation)
+ || InputType.TYPE_TEXT_VARIATION_URI == variation
+ || InputType.TYPE_TEXT_VARIATION_FILTER == variation
+ || flagNoSuggestions
+ || flagAutoComplete) {
+ mIsSettingsSuggestionStripOn = false;
+ } else {
+ mIsSettingsSuggestionStripOn = true;
+ }
+
+ // If it's a browser edit field and auto correct is not ON explicitly, then
+ // disable auto correction, but keep suggestions on.
+ // If NO_SUGGESTIONS is set, don't do prediction.
+ // If it's not multiline and the autoCorrect flag is not set, then don't correct
+ if ((variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
+ && !flagAutoCorrect)
+ || flagNoSuggestions
+ || (!flagAutoCorrect && !flagMultiLine)) {
+ mInputTypeNoAutoCorrect = true;
+ } else {
+ mInputTypeNoAutoCorrect = false;
+ }
+
+ mApplicationSpecifiedCompletionOn = flagAutoComplete && isFullscreenMode;
+ }
+ }
+
+ private void dumpFlags(final int inputType) {
+ Log.i(TAG, "Input class:");
+ final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
+ if (inputClass == InputType.TYPE_CLASS_TEXT)
+ Log.i(TAG, " TYPE_CLASS_TEXT");
+ if (inputClass == InputType.TYPE_CLASS_PHONE)
+ Log.i(TAG, " TYPE_CLASS_PHONE");
+ if (inputClass == InputType.TYPE_CLASS_NUMBER)
+ Log.i(TAG, " TYPE_CLASS_NUMBER");
+ if (inputClass == InputType.TYPE_CLASS_DATETIME)
+ Log.i(TAG, " TYPE_CLASS_DATETIME");
+ Log.i(TAG, "Variation:");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_ADDRESS");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_SUBJECT");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_FILTER))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_FILTER");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_LONG_MESSAGE");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_NORMAL))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_NORMAL");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PASSWORD))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_PASSWORD");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PERSON_NAME))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_PERSON_NAME");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PHONETIC))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_PHONETIC");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_POSTAL_ADDRESS");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_SHORT_MESSAGE");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_URI))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_URI");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_VISIBLE_PASSWORD");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EDIT_TEXT");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS");
+ if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
+ Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_PASSWORD");
+ Log.i(TAG, "Flags:");
+ if (0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS))
+ Log.i(TAG, " TYPE_TEXT_FLAG_NO_SUGGESTIONS");
+ if (0 != (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE))
+ Log.i(TAG, " TYPE_TEXT_FLAG_MULTI_LINE");
+ if (0 != (inputType & InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE))
+ Log.i(TAG, " TYPE_TEXT_FLAG_IME_MULTI_LINE");
+ if (0 != (inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS))
+ Log.i(TAG, " TYPE_TEXT_FLAG_CAP_WORDS");
+ if (0 != (inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES))
+ Log.i(TAG, " TYPE_TEXT_FLAG_CAP_SENTENCES");
+ if (0 != (inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS))
+ Log.i(TAG, " TYPE_TEXT_FLAG_CAP_CHARACTERS");
+ if (0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT))
+ Log.i(TAG, " TYPE_TEXT_FLAG_AUTO_CORRECT");
+ if (0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE))
+ Log.i(TAG, " TYPE_TEXT_FLAG_AUTO_COMPLETE");
+ }
+
+ // Pretty print
+ @Override
+ public String toString() {
+ return "\n mInputTypeNoAutoCorrect = " + mInputTypeNoAutoCorrect
+ + "\n mIsSettingsSuggestionStripOn = " + mIsSettingsSuggestionStripOn
+ + "\n mApplicationSpecifiedCompletionOn = " + mApplicationSpecifiedCompletionOn;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/JniUtils.java b/java/src/com/android/inputmethod/latin/JniUtils.java
new file mode 100644
index 000000000..4808b867a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/JniUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012 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.util.Log;
+
+import com.android.inputmethod.latin.define.JniLibName;
+
+public class JniUtils {
+ private static final String TAG = JniUtils.class.getSimpleName();
+
+ private JniUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static void loadNativeLibrary() {
+ try {
+ System.loadLibrary(JniLibName.JNI_LIB_NAME);
+ } catch (UnsatisfiedLinkError ule) {
+ Log.e(TAG, "Could not load native library " + JniLibName.JNI_LIB_NAME);
+ if (LatinImeLogger.sDBG) {
+ throw new RuntimeException(
+ "Could not load native library " + JniLibName.JNI_LIB_NAME);
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
new file mode 100644
index 000000000..bc0792434
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 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 java.util.ArrayList;
+
+/**
+ * This class encapsulates data about a word previously composed, but that has been
+ * committed already. This is used for resuming suggestion, and cancel auto-correction.
+ */
+public class LastComposedWord {
+ // COMMIT_TYPE_USER_TYPED_WORD is used when the word committed is the exact typed word, with
+ // no hinting from the IME. It happens when some external event happens (rotating the device,
+ // for example) or when auto-correction is off by settings or editor attributes.
+ public static final int COMMIT_TYPE_USER_TYPED_WORD = 0;
+ // COMMIT_TYPE_MANUAL_PICK is used when the user pressed a field in the suggestion strip.
+ public static final int COMMIT_TYPE_MANUAL_PICK = 1;
+ // COMMIT_TYPE_DECIDED_WORD is used when the IME commits the word it decided was best
+ // for the current user input. It may be different from what the user typed (true auto-correct)
+ // or it may be exactly what the user typed if it's in the dictionary or the IME does not have
+ // enough confidence in any suggestion to auto-correct (auto-correct to typed word).
+ public static final int COMMIT_TYPE_DECIDED_WORD = 2;
+ // COMMIT_TYPE_CANCEL_AUTO_CORRECT is used upon committing back the old word upon cancelling
+ // an auto-correction.
+ public static final int COMMIT_TYPE_CANCEL_AUTO_CORRECT = 3;
+
+ public static final int NOT_A_SEPARATOR = -1;
+
+ public final ArrayList<int[]> mCodes;
+ public final int[] mXCoordinates;
+ public final int[] mYCoordinates;
+ public final String mTypedWord;
+ public final String mCommittedWord;
+ public final int mSeparatorCode;
+
+ private boolean mActive;
+
+ public static final LastComposedWord NOT_A_COMPOSED_WORD =
+ new LastComposedWord(null, null, null, "", "", NOT_A_SEPARATOR);
+
+ // Warning: this is using the passed objects as is and fully expects them to be
+ // immutable. Do not fiddle with their contents after you passed them to this constructor.
+ public LastComposedWord(final ArrayList<int[]> codes, final int[] xCoordinates,
+ final int[] yCoordinates, final String typedWord, final String committedWord,
+ final int separatorCode) {
+ mCodes = codes;
+ mXCoordinates = xCoordinates;
+ mYCoordinates = yCoordinates;
+ mTypedWord = typedWord;
+ mCommittedWord = committedWord;
+ mSeparatorCode = separatorCode;
+ mActive = true;
+ }
+
+ public void deactivate() {
+ mActive = false;
+ }
+
+ public boolean canRevertCommit() {
+ return mActive && !TextUtils.isEmpty(mCommittedWord);
+ }
+
+ public boolean didCommitTypedWord() {
+ return TextUtils.equals(mTypedWord, mCommittedWord);
+ }
+
+ public static int getSeparatorLength(final int separatorCode) {
+ return NOT_A_SEPARATOR == separatorCode ? 0 : 1;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 9c321bcb9..fa5a46fca 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -25,10 +25,12 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Rect;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.os.Debug;
+import android.os.IBinder;
import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceActivity;
@@ -38,10 +40,10 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
-import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
@@ -49,26 +51,29 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.InputConnection;
import com.android.inputmethod.accessibility.AccessibilityUtils;
+import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.compat.CompatUtils;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
import com.android.inputmethod.compat.InputConnectionCompatUtils;
import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
import com.android.inputmethod.compat.InputTypeCompatUtils;
import com.android.inputmethod.compat.SuggestionSpanUtils;
-import com.android.inputmethod.compat.VibratorCompatWrapper;
import com.android.inputmethod.deprecated.LanguageSwitcherProxy;
import com.android.inputmethod.deprecated.VoiceProxy;
-import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
+import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView;
-import com.android.inputmethod.keyboard.LatinKeyboard;
import com.android.inputmethod.keyboard.LatinKeyboardView;
+import com.android.inputmethod.latin.suggestions.SuggestionsView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
/**
@@ -77,7 +82,6 @@ import java.util.Locale;
public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener,
SuggestionsView.Listener {
private static final String TAG = LatinIME.class.getSimpleName();
- private static final boolean PERF_DEBUG = false;
private static final boolean TRACE = false;
private static boolean DEBUG;
@@ -107,7 +111,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
/**
* The private IME option used to indicate that the given text field needs
* ASCII code points input.
+ *
+ * @deprecated Use {@link EditorInfo#IME_FLAG_FORCE_ASCII}.
*/
+ @SuppressWarnings("dep-ann")
public static final String IME_OPTION_FORCE_ASCII = "forceAscii";
/**
@@ -143,6 +150,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
*/
private static final String SCHEME_PACKAGE = "package";
+ // TODO: migrate this to SettingsValues
private int mSuggestionVisibility;
private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
= R.string.prefs_suggestion_visibility_show_value;
@@ -157,51 +165,57 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
SUGGESTION_VISIBILILTY_HIDE_VALUE
};
- private Settings.Values mSettingsValues;
+ private static final int SPACE_STATE_NONE = 0;
+ // Double space: the state where the user pressed space twice quickly, which LatinIME
+ // resolved as period-space. Undoing this converts the period to a space.
+ private static final int SPACE_STATE_DOUBLE = 1;
+ // Swap punctuation: the state where a weak space and a punctuation from the suggestion strip
+ // have just been swapped. Undoing this swaps them back; the space is still considered weak.
+ private static final int SPACE_STATE_SWAP_PUNCTUATION = 2;
+ // Weak space: a space that should be swapped only by suggestion strip punctuation. Weak
+ // spaces happen when the user presses space, accepting the current suggestion (whether
+ // it's an auto-correction or not).
+ private static final int SPACE_STATE_WEAK = 3;
+ // Phantom space: a not-yet-inserted space that should get inserted on the next input,
+ // character provided it's not a separator. If it's a separator, the phantom space is dropped.
+ // Phantom spaces happen when a user chooses a word from the suggestion strip.
+ private static final int SPACE_STATE_PHANTOM = 4;
+
+ // Current space state of the input method. This can be any of the above constants.
+ private int mSpaceState;
+
+ private SettingsValues mSettingsValues;
+ private InputAttributes mInputAttributes;
private View mExtractArea;
private View mKeyPreviewBackingView;
private View mSuggestionsContainer;
private SuggestionsView mSuggestionsView;
- private Suggest mSuggest;
+ /* package for tests */ Suggest mSuggest;
private CompletionInfo[] mApplicationSpecifiedCompletions;
private InputMethodManagerCompatWrapper mImm;
private Resources mResources;
private SharedPreferences mPrefs;
- private String mInputMethodId;
- private KeyboardSwitcher mKeyboardSwitcher;
- private SubtypeSwitcher mSubtypeSwitcher;
+ private final KeyboardSwitcher mKeyboardSwitcher;
+ private final SubtypeSwitcher mSubtypeSwitcher;
private VoiceProxy mVoiceProxy;
+ private boolean mShouldSwitchToLastSubtype = true;
private UserDictionary mUserDictionary;
private UserBigramDictionary mUserBigramDictionary;
private UserUnigramDictionary mUserUnigramDictionary;
- private boolean mIsUserDictionaryAvaliable;
-
- // TODO: Create an inner class to group options and pseudo-options to improve readability.
- // These variables are initialized according to the {@link EditorInfo#inputType}.
- private boolean mInsertSpaceOnPickSuggestionManually;
- private boolean mInputTypeNoAutoCorrect;
- private boolean mIsSettingsSuggestionStripOn;
- private boolean mApplicationSpecifiedCompletionOn;
+ private boolean mIsUserDictionaryAvailable;
- private final StringBuilder mComposingStringBuilder = new StringBuilder();
+ private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
private WordComposer mWordComposer = new WordComposer();
- private CharSequence mBestWord;
- private boolean mHasUncommittedTypedChars;
- // Magic space: a space that should disappear on space/apostrophe insertion, move after the
- // punctuation on punctuation insertion, and become a real space on alpha char insertion.
- private boolean mJustAddedMagicSpace; // This indicates whether the last char is a magic space.
- // This indicates whether the last keypress resulted in processing of double space replacement
- // with period-space.
- private boolean mJustReplacedDoubleSpace;
private int mCorrectionMode;
- private int mCommittedLength;
+
// Keep track of the last selection range to decide if we need to show word alternatives
- private int mLastSelectionStart;
- private int mLastSelectionEnd;
+ private static final int NOT_A_CURSOR_POSITION = -1;
+ private int mLastSelectionStart = NOT_A_CURSOR_POSITION;
+ private int mLastSelectionEnd = NOT_A_CURSOR_POSITION;
// Whether we are expecting an onUpdateSelection event to fire. If it does when we don't
// "expect" it, it means the user actually moved the cursor.
@@ -209,15 +223,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private int mDeleteCount;
private long mLastKeyTime;
- private AudioManager mAudioManager;
- private float mFxVolume = -1.0f; // default volume
- private boolean mSilentModeOn; // System-wide current configuration
-
- private VibratorCompatWrapper mVibrator;
- private long mKeypressVibrationDuration = -1;
-
- // TODO: Move this flag to VoiceProxy
- private boolean mConfigurationChanging;
+ private AudioAndHapticFeedbackManager mFeedbackManager;
// Member variables for remembering the current device orientation.
private int mDisplayOrientation;
@@ -229,29 +235,21 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Keeps track of most recently inserted text (multi-character key) for reverting
private CharSequence mEnteredText;
- private final ComposingStateManager mComposingStateManager =
- ComposingStateManager.getInstance();
+ private boolean mIsAutoCorrectionIndicatorOn;
public final UIHandler mHandler = new UIHandler(this);
public static class UIHandler extends StaticInnerHandlerWrapper<LatinIME> {
- private static final int MSG_UPDATE_SUGGESTIONS = 0;
private static final int MSG_UPDATE_SHIFT_STATE = 1;
private static final int MSG_VOICE_RESULTS = 2;
- private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 3;
- private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 4;
- private static final int MSG_SPACE_TYPED = 5;
- private static final int MSG_KEY_TYPED = 6;
- private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
- private static final int MSG_PENDING_IMS_CALLBACK = 8;
-
- private int mDelayBeforeFadeoutLanguageOnSpacebar;
+ private static final int MSG_SPACE_TYPED = 4;
+ private static final int MSG_SET_BIGRAM_PREDICTIONS = 5;
+ private static final int MSG_PENDING_IMS_CALLBACK = 6;
+ private static final int MSG_UPDATE_SUGGESTIONS = 7;
+
private int mDelayUpdateSuggestions;
private int mDelayUpdateShiftState;
- private int mDurationOfFadeoutLanguageOnSpacebar;
- private float mFinalFadeoutFactorOfLanguageOnSpacebar;
private long mDoubleSpacesTurnIntoPeriodTimeout;
- private long mIgnoreSpecialKeyTimeout;
public UIHandler(LatinIME outerInstance) {
super(outerInstance);
@@ -259,27 +257,18 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
public void onCreate() {
final Resources res = getOuterInstance().getResources();
- mDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger(
- R.integer.config_delay_before_fadeout_language_on_spacebar);
mDelayUpdateSuggestions =
res.getInteger(R.integer.config_delay_update_suggestions);
mDelayUpdateShiftState =
res.getInteger(R.integer.config_delay_update_shift_state);
- mDurationOfFadeoutLanguageOnSpacebar = res.getInteger(
- R.integer.config_duration_of_fadeout_language_on_spacebar);
- mFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger(
- R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f;
mDoubleSpacesTurnIntoPeriodTimeout = res.getInteger(
R.integer.config_double_spaces_turn_into_period_timeout);
- mIgnoreSpecialKeyTimeout = res.getInteger(
- R.integer.config_ignore_special_key_timeout);
}
@Override
public void handleMessage(Message msg) {
final LatinIME latinIme = getOuterInstance();
final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher;
- final LatinKeyboardView inputView = switcher.getKeyboardView();
switch (msg.what) {
case MSG_UPDATE_SUGGESTIONS:
latinIme.updateSuggestions();
@@ -291,23 +280,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
latinIme.updateBigramPredictions();
break;
case MSG_VOICE_RESULTS:
+ final Keyboard keyboard = switcher.getKeyboard();
latinIme.mVoiceProxy.handleVoiceResults(latinIme.preferCapitalization()
- || (switcher.isAlphabetMode() && switcher.isShiftedOrShiftLocked()));
- break;
- case MSG_FADEOUT_LANGUAGE_ON_SPACEBAR:
- if (inputView != null) {
- inputView.setSpacebarTextFadeFactor(
- (1.0f + mFinalFadeoutFactorOfLanguageOnSpacebar) / 2,
- (LatinKeyboard)msg.obj);
- }
- sendMessageDelayed(obtainMessage(MSG_DISMISS_LANGUAGE_ON_SPACEBAR, msg.obj),
- mDurationOfFadeoutLanguageOnSpacebar);
- break;
- case MSG_DISMISS_LANGUAGE_ON_SPACEBAR:
- if (inputView != null) {
- inputView.setSpacebarTextFadeFactor(mFinalFadeoutFactorOfLanguageOnSpacebar,
- (LatinKeyboard)msg.obj);
- }
+ || (keyboard != null && keyboard.isShiftedOrShiftLocked()));
break;
}
}
@@ -325,7 +300,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return hasMessages(MSG_UPDATE_SUGGESTIONS);
}
- public void postUpdateShiftKeyState() {
+ public void postUpdateShiftState() {
removeMessages(MSG_UPDATE_SHIFT_STATE);
sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mDelayUpdateShiftState);
}
@@ -347,30 +322,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
sendMessage(obtainMessage(MSG_VOICE_RESULTS));
}
- public void startDisplayLanguageOnSpacebar(boolean localeChanged) {
- final LatinIME latinIme = getOuterInstance();
- removeMessages(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR);
- removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR);
- final LatinKeyboardView inputView = latinIme.mKeyboardSwitcher.getKeyboardView();
- if (inputView != null) {
- final LatinKeyboard keyboard = latinIme.mKeyboardSwitcher.getLatinKeyboard();
- // The language is always displayed when the delay is negative.
- final boolean needsToDisplayLanguage = localeChanged
- || mDelayBeforeFadeoutLanguageOnSpacebar < 0;
- // The language is never displayed when the delay is zero.
- if (mDelayBeforeFadeoutLanguageOnSpacebar != 0) {
- inputView.setSpacebarTextFadeFactor(needsToDisplayLanguage ? 1.0f
- : mFinalFadeoutFactorOfLanguageOnSpacebar,
- keyboard);
- }
- // The fadeout animation will start when the delay is positive.
- if (localeChanged && mDelayBeforeFadeoutLanguageOnSpacebar > 0) {
- sendMessageDelayed(obtainMessage(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR, keyboard),
- mDelayBeforeFadeoutLanguageOnSpacebar);
- }
- }
- }
-
public void startDoubleSpacesTimer() {
removeMessages(MSG_SPACE_TYPED);
sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED), mDoubleSpacesTurnIntoPeriodTimeout);
@@ -384,21 +335,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return hasMessages(MSG_SPACE_TYPED);
}
- public void startKeyTypedTimer() {
- removeMessages(MSG_KEY_TYPED);
- sendMessageDelayed(obtainMessage(MSG_KEY_TYPED), mIgnoreSpecialKeyTimeout);
- }
-
- public boolean isIgnoringSpecialKey() {
- return hasMessages(MSG_KEY_TYPED);
- }
-
// Working variables for the following methods.
private boolean mIsOrientationChanging;
- private boolean mPendingSuccesiveImsCallback;
+ private boolean mPendingSuccessiveImsCallback;
private boolean mHasPendingStartInput;
private boolean mHasPendingFinishInputView;
private boolean mHasPendingFinishInput;
+ private EditorInfo mAppliedEditorInfo;
public void startOrientationChanging() {
removeMessages(MSG_PENDING_IMS_CALLBACK);
@@ -416,18 +359,18 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mHasPendingStartInput = false;
}
- private void executePendingImsCallback(LatinIME latinIme, EditorInfo attribute,
+ private void executePendingImsCallback(LatinIME latinIme, EditorInfo editorInfo,
boolean restarting) {
if (mHasPendingFinishInputView)
latinIme.onFinishInputViewInternal(mHasPendingFinishInput);
if (mHasPendingFinishInput)
latinIme.onFinishInputInternal();
if (mHasPendingStartInput)
- latinIme.onStartInputInternal(attribute, restarting);
+ latinIme.onStartInputInternal(editorInfo, restarting);
resetPendingImsCallback();
}
- public void onStartInput(EditorInfo attribute, boolean restarting) {
+ public void onStartInput(EditorInfo editorInfo, boolean restarting) {
if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
// Typically this is the second onStartInput after orientation changed.
mHasPendingStartInput = true;
@@ -435,30 +378,32 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (mIsOrientationChanging && restarting) {
// This is the first onStartInput after orientation changed.
mIsOrientationChanging = false;
- mPendingSuccesiveImsCallback = true;
+ mPendingSuccessiveImsCallback = true;
}
final LatinIME latinIme = getOuterInstance();
- executePendingImsCallback(latinIme, attribute, restarting);
- latinIme.onStartInputInternal(attribute, restarting);
+ executePendingImsCallback(latinIme, editorInfo, restarting);
+ latinIme.onStartInputInternal(editorInfo, restarting);
}
}
- public void onStartInputView(EditorInfo attribute, boolean restarting) {
- if (hasMessages(MSG_PENDING_IMS_CALLBACK)) {
- // Typically this is the second onStartInputView after orientation changed.
- resetPendingImsCallback();
- } else {
- if (mPendingSuccesiveImsCallback) {
- // This is the first onStartInputView after orientation changed.
- mPendingSuccesiveImsCallback = false;
- resetPendingImsCallback();
- sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
- PENDING_IMS_CALLBACK_DURATION);
- }
- final LatinIME latinIme = getOuterInstance();
- executePendingImsCallback(latinIme, attribute, restarting);
- latinIme.onStartInputViewInternal(attribute, restarting);
- }
+ public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+ if (hasMessages(MSG_PENDING_IMS_CALLBACK)
+ && KeyboardId.equivalentEditorInfoForKeyboard(editorInfo, mAppliedEditorInfo)) {
+ // Typically this is the second onStartInputView after orientation changed.
+ resetPendingImsCallback();
+ } else {
+ if (mPendingSuccessiveImsCallback) {
+ // This is the first onStartInputView after orientation changed.
+ mPendingSuccessiveImsCallback = false;
+ resetPendingImsCallback();
+ sendMessageDelayed(obtainMessage(MSG_PENDING_IMS_CALLBACK),
+ PENDING_IMS_CALLBACK_DURATION);
+ }
+ final LatinIME latinIme = getOuterInstance();
+ executePendingImsCallback(latinIme, editorInfo, restarting);
+ latinIme.onStartInputViewInternal(editorInfo, restarting);
+ mAppliedEditorInfo = editorInfo;
+ }
}
public void onFinishInputView(boolean finishingInput) {
@@ -468,6 +413,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
} else {
final LatinIME latinIme = getOuterInstance();
latinIme.onFinishInputViewInternal(finishingInput);
+ mAppliedEditorInfo = null;
}
}
@@ -483,6 +429,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
+ public LatinIME() {
+ super();
+ mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+ mKeyboardSwitcher = KeyboardSwitcher.getInstance();
+ }
+
@Override
public void onCreate() {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
@@ -492,15 +444,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
InputMethodManagerCompatWrapper.init(this);
SubtypeSwitcher.init(this);
KeyboardSwitcher.init(this, prefs);
- AccessibilityUtils.init(this, prefs);
+ AccessibilityUtils.init(this);
super.onCreate();
mImm = InputMethodManagerCompatWrapper.getInstance();
- mInputMethodId = Utils.getInputMethodId(mImm, getPackageName());
- mSubtypeSwitcher = SubtypeSwitcher.getInstance();
- mKeyboardSwitcher = KeyboardSwitcher.getInstance();
- mVibrator = VibratorCompatWrapper.getInstance(this);
mHandler.onCreate();
DEBUG = LatinImeLogger.sDBG;
@@ -509,6 +457,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
loadSettings();
+ // TODO: remove the following when it's not needed by updateCorrectionMode() any more
+ mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
+ updateCorrectionMode();
+
Utils.GCUtils.getInstance().reset();
boolean tryGC = true;
for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
@@ -525,8 +477,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Register to receive ringer mode change and network state change.
// Also receive installation and removal of a dictionary pack.
final IntentFilter filter = new IntentFilter();
- filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+ filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
registerReceiver(mReceiver, filter);
mVoiceProxy = VoiceProxy.init(this, prefs, mHandler);
@@ -545,11 +497,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Has to be package-visible for unit tests
/* package */ void loadSettings() {
if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
- if (null == mSubtypeSwitcher) mSubtypeSwitcher = SubtypeSwitcher.getInstance();
- mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr());
+ mSettingsValues = new SettingsValues(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr());
+ mFeedbackManager = new AudioAndHapticFeedbackManager(this, mSettingsValues);
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
- updateSoundEffectVolume();
- updateKeypressVibrationDuration();
}
private void initSuggest() {
@@ -566,7 +516,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
oldContactsDictionary = null;
}
- int mainDicResId = Utils.getMainDictionaryResourceId(res);
+ int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(res);
mSuggest = new Suggest(this, mainDicResId, keyboardLocale);
if (mSettingsValues.mAutoCorrectEnabled) {
mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
@@ -574,7 +524,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mUserDictionary = new UserDictionary(this, localeStr);
mSuggest.setUserDictionary(mUserDictionary);
- mIsUserDictionaryAvaliable = mUserDictionary.isEnabled();
+ mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
resetContactsDictionary(oldContactsDictionary);
@@ -586,8 +536,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
= new UserBigramDictionary(this, this, localeStr, Suggest.DIC_USER_BIGRAM);
mSuggest.setUserBigramDictionary(mUserBigramDictionary);
- updateCorrectionMode();
-
LocaleUtils.setSystemLocale(res, savedLocale);
}
@@ -626,7 +574,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
/* package private */ void resetSuggestMainDict() {
final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
- int mainDicResId = Utils.getMainDictionaryResourceId(mResources);
+ int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(mResources);
mSuggest.resetMainDict(this, mainDicResId, keyboardLocale);
}
@@ -647,22 +595,21 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public void onConfigurationChanged(Configuration conf) {
mSubtypeSwitcher.onConfigurationChanged(conf);
- mComposingStateManager.onFinishComposingText();
// If orientation changed while predicting, commit the change
if (mDisplayOrientation != conf.orientation) {
mDisplayOrientation = conf.orientation;
mHandler.startOrientationChanging();
final InputConnection ic = getCurrentInputConnection();
- commitTyped(ic);
+ commitTyped(ic, LastComposedWord.NOT_A_SEPARATOR);
if (ic != null) ic.finishComposingText(); // For voice input
if (isShowingOptionDialog())
mOptionsDialog.dismiss();
}
- mConfigurationChanging = true;
+ mVoiceProxy.startChangingConfiguration();
super.onConfigurationChanged(conf);
mVoiceProxy.onConfigurationChanged(conf);
- mConfigurationChanging = false;
+ mVoiceProxy.finishChangingConfiguration();
// This will work only when the subtype is not supported.
LanguageSwitcherProxy.onConfigurationChanged(conf);
@@ -695,13 +642,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
@Override
- public void onStartInput(EditorInfo attribute, boolean restarting) {
- mHandler.onStartInput(attribute, restarting);
+ public void onStartInput(EditorInfo editorInfo, boolean restarting) {
+ mHandler.onStartInput(editorInfo, restarting);
}
@Override
- public void onStartInputView(EditorInfo attribute, boolean restarting) {
- mHandler.onStartInputView(attribute, restarting);
+ public void onStartInputView(EditorInfo editorInfo, boolean restarting) {
+ mHandler.onStartInputView(editorInfo, restarting);
}
@Override
@@ -714,20 +661,39 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mHandler.onFinishInput();
}
- private void onStartInputInternal(EditorInfo attribute, boolean restarting) {
- super.onStartInput(attribute, restarting);
+ private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) {
+ super.onStartInput(editorInfo, restarting);
}
- private void onStartInputViewInternal(EditorInfo attribute, boolean restarting) {
- super.onStartInputView(attribute, restarting);
+ private void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) {
+ super.onStartInputView(editorInfo, restarting);
final KeyboardSwitcher switcher = mKeyboardSwitcher;
LatinKeyboardView inputView = switcher.getKeyboardView();
+ if (editorInfo == null) {
+ Log.e(TAG, "Null EditorInfo in onStartInputView()");
+ if (LatinImeLogger.sDBG) {
+ throw new NullPointerException("Null EditorInfo in onStartInputView()");
+ }
+ return;
+ }
if (DEBUG) {
- Log.d(TAG, "onStartInputView: attribute:" + ((attribute == null) ? "none"
- : String.format("inputType=0x%08x imeOptions=0x%08x",
- attribute.inputType, attribute.imeOptions)));
+ Log.d(TAG, "onStartInputView: editorInfo:"
+ + String.format("inputType=0x%08x imeOptions=0x%08x",
+ editorInfo.inputType, editorInfo.imeOptions));
+ }
+ if (StringUtils.inPrivateImeOptions(null, IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo)) {
+ Log.w(TAG, "Deprecated private IME option specified: "
+ + editorInfo.privateImeOptions);
+ Log.w(TAG, "Use " + getPackageName() + "." + IME_OPTION_NO_MICROPHONE + " instead");
+ }
+ if (StringUtils.inPrivateImeOptions(getPackageName(), IME_OPTION_FORCE_ASCII, editorInfo)) {
+ Log.w(TAG, "Deprecated private IME option specified: "
+ + editorInfo.privateImeOptions);
+ Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
}
+
+ LatinImeLogger.onStartInputView(editorInfo);
// In landscape mode, this method gets called without the input view being created.
if (inputView == null) {
return;
@@ -736,47 +702,45 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Forward this event to the accessibility utilities, if enabled.
final AccessibilityUtils accessUtils = AccessibilityUtils.getInstance();
if (accessUtils.isTouchExplorationEnabled()) {
- accessUtils.onStartInputViewInternal(attribute, restarting);
+ accessUtils.onStartInputViewInternal(editorInfo, restarting);
}
mSubtypeSwitcher.updateParametersOnStartInputView();
- TextEntryState.reset();
-
// Most such things we decide below in initializeInputAttributesAndGetMode, but we need to
// know now whether this is a password text field, because we need to know now whether we
// want to enable the voice button.
- final VoiceProxy voiceIme = mVoiceProxy;
- final int inputType = (attribute != null) ? attribute.inputType : 0;
- voiceIme.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(inputType)
+ final int inputType = editorInfo.inputType;
+ mVoiceProxy.resetVoiceStates(InputTypeCompatUtils.isPasswordInputType(inputType)
|| InputTypeCompatUtils.isVisiblePasswordInputType(inputType));
// The EditorInfo might have a flag that affects fullscreen mode.
// Note: This call should be done by InputMethodService?
updateFullscreenMode();
- initializeInputAttributes(attribute);
+ mLastSelectionStart = editorInfo.initialSelStart;
+ mLastSelectionEnd = editorInfo.initialSelEnd;
+ mInputAttributes = new InputAttributes(editorInfo, isFullscreenMode());
+ mApplicationSpecifiedCompletions = null;
inputView.closing();
mEnteredText = null;
- mComposingStringBuilder.setLength(0);
- mHasUncommittedTypedChars = false;
+ resetComposingState(true /* alsoResetLastComposedWord */);
mDeleteCount = 0;
- mJustAddedMagicSpace = false;
- mJustReplacedDoubleSpace = false;
+ mSpaceState = SPACE_STATE_NONE;
loadSettings();
updateCorrectionMode();
- updateSuggestionVisibility(mPrefs, mResources);
+ updateSuggestionVisibility(mResources);
if (mSuggest != null && mSettingsValues.mAutoCorrectEnabled) {
mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
}
- mVoiceProxy.loadSettings(attribute, mPrefs);
+ mVoiceProxy.loadSettings(editorInfo, mPrefs);
// This will work only when the subtype is not supported.
LanguageSwitcherProxy.loadSettings();
if (mSubtypeSwitcher.isKeyboardMode()) {
- switcher.loadKeyboard(attribute, mSettingsValues);
+ switcher.loadKeyboard(editorInfo, mSettingsValues);
}
if (mSuggestionsView != null)
@@ -785,83 +749,17 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
isSuggestionsStripVisible(), /* needsInputViewShown */ false);
// Delay updating suggestions because keyboard input view may not be shown at this point.
mHandler.postUpdateSuggestions();
+ mHandler.cancelDoubleSpacesTimer();
inputView.setKeyPreviewPopupEnabled(mSettingsValues.mKeyPreviewPopupOn,
mSettingsValues.mKeyPreviewPopupDismissDelay);
inputView.setProximityCorrectionEnabled(true);
- voiceIme.onStartInputView(inputView.getWindowToken());
+ mVoiceProxy.onStartInputView(inputView.getWindowToken());
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
}
- private void initializeInputAttributes(EditorInfo attribute) {
- if (attribute == null)
- return;
- final int inputType = attribute.inputType;
- if (inputType == InputType.TYPE_NULL) {
- // TODO: We should honor TYPE_NULL specification.
- Log.i(TAG, "InputType.TYPE_NULL is specified");
- }
- final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
- final int variation = inputType & InputType.TYPE_MASK_VARIATION;
- if (inputClass == 0) {
- Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x imeOptions=0x%08x",
- inputType, attribute.imeOptions));
- }
-
- mInsertSpaceOnPickSuggestionManually = false;
- mInputTypeNoAutoCorrect = false;
- mIsSettingsSuggestionStripOn = false;
- mApplicationSpecifiedCompletionOn = false;
- mApplicationSpecifiedCompletions = null;
-
- if (inputClass == InputType.TYPE_CLASS_TEXT) {
- mIsSettingsSuggestionStripOn = true;
- // Make sure that passwords are not displayed in {@link SuggestionsView}.
- if (InputTypeCompatUtils.isPasswordInputType(inputType)
- || InputTypeCompatUtils.isVisiblePasswordInputType(inputType)) {
- mIsSettingsSuggestionStripOn = false;
- }
- if (InputTypeCompatUtils.isEmailVariation(variation)
- || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
- // The point in turning this off is that we don't want to insert a space after
- // a name when filling a form: we can't delete trailing spaces when changing fields
- mInsertSpaceOnPickSuggestionManually = false;
- } else {
- mInsertSpaceOnPickSuggestionManually = true;
- }
- if (InputTypeCompatUtils.isEmailVariation(variation)) {
- mIsSettingsSuggestionStripOn = false;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
- mIsSettingsSuggestionStripOn = false;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
- mIsSettingsSuggestionStripOn = false;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
- // If it's a browser edit field and auto correct is not ON explicitly, then
- // disable auto correction, but keep suggestions on.
- if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
- mInputTypeNoAutoCorrect = true;
- }
- }
-
- // If NO_SUGGESTIONS is set, don't do prediction.
- if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
- mIsSettingsSuggestionStripOn = false;
- mInputTypeNoAutoCorrect = true;
- }
- // If it's not multiline and the autoCorrect flag is not set, then don't correct
- if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0
- && (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
- mInputTypeNoAutoCorrect = true;
- }
- if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
- mIsSettingsSuggestionStripOn = false;
- mApplicationSpecifiedCompletionOn = isFullscreenMode();
- }
- }
- }
-
@Override
public void onWindowHidden() {
super.onWindowHidden();
@@ -874,7 +772,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
LatinImeLogger.commit();
- mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging);
+ mVoiceProxy.flushVoiceInputLogs();
KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
if (inputView != null) inputView.closing();
@@ -900,9 +798,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public void onUpdateSelection(int oldSelStart, int oldSelEnd,
int newSelStart, int newSelEnd,
- int candidatesStart, int candidatesEnd) {
+ int composingSpanStart, int composingSpanEnd) {
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
- candidatesStart, candidatesEnd);
+ composingSpanStart, composingSpanEnd);
if (DEBUG) {
Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
@@ -911,51 +809,58 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
+ ", lse=" + mLastSelectionEnd
+ ", nss=" + newSelStart
+ ", nse=" + newSelEnd
- + ", cs=" + candidatesStart
- + ", ce=" + candidatesEnd);
+ + ", cs=" + composingSpanStart
+ + ", ce=" + composingSpanEnd);
}
mVoiceProxy.setCursorAndSelection(newSelEnd, newSelStart);
- // If the current selection in the text view changes, we should
- // clear whatever candidate text we have.
- final boolean selectionChanged = (newSelStart != candidatesEnd
- || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
- final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
+ // TODO: refactor the following code to be less contrived.
+ // "newSelStart != composingSpanEnd" || "newSelEnd != composingSpanEnd" means
+ // that the cursor is not at the end of the composing span, or there is a selection.
+ // "mLastSelectionStart != newSelStart" means that the cursor is not in the same place
+ // as last time we were called (if there is a selection, it means the start hasn't
+ // changed, so it's the end that did).
+ final boolean selectionChanged = (newSelStart != composingSpanEnd
+ || newSelEnd != composingSpanEnd) && mLastSelectionStart != newSelStart;
+ // if composingSpanStart and composingSpanEnd are -1, it means there is no composing
+ // span in the view - we can use that to narrow down whether the cursor was moved
+ // by us or not. If we are composing a word but there is no composing span, then
+ // we know for sure the cursor moved while we were composing and we should reset
+ // the state.
+ final boolean noComposingSpan = composingSpanStart == -1 && composingSpanEnd == -1;
if (!mExpectingUpdateSelection) {
- if (((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars)
- || mVoiceProxy.isVoiceInputHighlighted())
- && (selectionChanged || candidatesCleared)) {
- mComposingStringBuilder.setLength(0);
- mHasUncommittedTypedChars = false;
- TextEntryState.reset();
- updateSuggestions();
- final InputConnection ic = getCurrentInputConnection();
- if (ic != null) {
- ic.finishComposingText();
- }
- mComposingStateManager.onFinishComposingText();
- mVoiceProxy.setVoiceInputHighlighted(false);
- } else if (!mHasUncommittedTypedChars) {
- TextEntryState.reset();
- updateSuggestions();
+ // TAKE CARE: there is a race condition when we enter this test even when the user
+ // did not explicitly move the cursor. This happens when typing fast, where two keys
+ // turn this flag on in succession and both onUpdateSelection() calls arrive after
+ // the second one - the first call successfully avoids this test, but the second one
+ // enters. For the moment we rely on noComposingSpan to further reduce the impact.
+
+ // TODO: the following is probably better done in resetEntireInputState().
+ // it should only happen when the cursor moved, and the very purpose of the
+ // test below is to narrow down whether this happened or not. Likewise with
+ // the call to postUpdateShiftState.
+ // We set this to NONE because after a cursor move, we don't want the space
+ // state-related special processing to kick in.
+ mSpaceState = SPACE_STATE_NONE;
+
+ if ((!mWordComposer.isComposingWord()) || selectionChanged || noComposingSpan) {
+ resetEntireInputState();
}
- mJustAddedMagicSpace = false; // The user moved the cursor.
- mJustReplacedDoubleSpace = false;
+
+ mHandler.postUpdateShiftState();
}
mExpectingUpdateSelection = false;
- mHandler.postUpdateShiftKeyState();
+ // 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;
mLastSelectionEnd = newSelEnd;
}
- public void setLastSelection(int start, int end) {
- mLastSelectionStart = start;
- mLastSelectionEnd = end;
- }
-
/**
* This is called when the user has clicked on the extracted text view,
* when running in fullscreen mode. The default implementation hides
@@ -997,7 +902,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mOptionsDialog.dismiss();
mOptionsDialog = null;
}
- mVoiceProxy.hideVoiceWindow(mConfigurationChanging);
+ mVoiceProxy.hideVoiceWindow();
super.hideWindow();
}
@@ -1011,20 +916,30 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
}
- if (mApplicationSpecifiedCompletionOn) {
+ if (mInputAttributes.mApplicationSpecifiedCompletionOn) {
mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
if (applicationSpecifiedCompletions == null) {
clearSuggestions();
return;
}
- SuggestedWords.Builder builder = new SuggestedWords.Builder()
- .setApplicationSpecifiedCompletions(applicationSpecifiedCompletions)
- .setTypedWordValid(false)
- .setHasMinimalSuggestion(false);
+ final List<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
+ SuggestedWords.getFromApplicationSpecifiedCompletions(
+ applicationSpecifiedCompletions);
+ final SuggestedWords suggestedWords = new SuggestedWords(
+ applicationSuggestedWords,
+ false /* typedWordValid */,
+ false /* hasAutoCorrectionCandidate */,
+ false /* allowsToBeAutoCorrected */,
+ false /* isPunctuationSuggestions */,
+ false /* isObsoleteSuggestions */);
// When in fullscreen mode, show completions generated by the application
- setSuggestions(builder.build());
- mBestWord = null;
+ final boolean isAutoCorrection = false;
+ setSuggestions(suggestedWords, isAutoCorrection);
+ setAutoCorrectionIndicator(isAutoCorrection);
+ // TODO: is this the right thing to do? What should we auto-correct to in
+ // this case? This says to keep whatever the user typed.
+ mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
setSuggestionStripShown(true);
}
}
@@ -1032,8 +947,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) {
// TODO: Modify this if we support suggestions with hard keyboard
if (onEvaluateInputViewShown() && mSuggestionsContainer != null) {
+ final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView();
+ final boolean inputViewShown = (keyboardView != null) ? keyboardView.isShown() : false;
final boolean shouldShowSuggestions = shown
- && (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true);
+ && (needsInputViewShown ? inputViewShown : true);
if (isFullscreenMode()) {
mSuggestionsContainer.setVisibility(
shouldShowSuggestions ? View.VISIBLE : View.GONE);
@@ -1048,12 +965,34 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
setSuggestionStripShownInternal(shown, /* needsInputViewShown */true);
}
+ private void adjustInputViewHeight() {
+ if (mKeyPreviewBackingView.getHeight() > 0) {
+ return;
+ }
+
+ final KeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView();
+ if (keyboardView == null) return;
+ final int keyboardHeight = keyboardView.getHeight();
+ final int suggestionsHeight = mSuggestionsContainer.getHeight();
+ final int displayHeight = mResources.getDisplayMetrics().heightPixels;
+ final Rect rect = new Rect();
+ mKeyPreviewBackingView.getWindowVisibleDisplayFrame(rect);
+ final int notificationBarHeight = rect.top;
+ final int remainingHeight = displayHeight - notificationBarHeight - suggestionsHeight
+ - keyboardHeight;
+
+ final LayoutParams params = mKeyPreviewBackingView.getLayoutParams();
+ params.height = mSuggestionsView.setMoreSuggestionsHeight(remainingHeight);
+ mKeyPreviewBackingView.setLayoutParams(params);
+ }
+
@Override
public void onComputeInsets(InputMethodService.Insets outInsets) {
super.onComputeInsets(outInsets);
final KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
if (inputView == null || mSuggestionsContainer == null)
return;
+ adjustInputViewHeight();
// In fullscreen mode, the height of the extract area managed by InputMethodService should
// be considered.
// See {@link android.inputmethodservice.InputMethodService#onComputeInsets}.
@@ -1065,7 +1004,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final int extraHeight = extractHeight + backingHeight + suggestionsHeight;
int touchY = extraHeight;
// Need to set touchable region only if input view is being shown
- if (mKeyboardSwitcher.isInputViewShown()) {
+ final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView();
+ if (keyboardView != null && keyboardView.isShown()) {
if (mSuggestionsContainer.getVisibility() == View.VISIBLE) {
touchY -= suggestionsHeight;
}
@@ -1085,8 +1025,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public boolean onEvaluateFullscreenMode() {
- return super.onEvaluateFullscreenMode()
- && mResources.getBoolean(R.bool.config_use_fullscreen_mode);
+ // Reread resource value here, because this method is called by framework anytime as needed.
+ final boolean isFullscreenModeAllowed =
+ mSettingsValues.isFullscreenModeAllowed(getResources());
+ return super.onEvaluateFullscreenMode() && isFullscreenModeAllowed;
}
@Override
@@ -1124,9 +1066,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
+ final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView();
+ final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
// Enable shift key and DPAD to do selections
- if (mKeyboardSwitcher.isInputViewShown()
- && mKeyboardSwitcher.isShiftedOrShiftLocked()) {
+ if ((keyboardView != null && keyboardView.isShown())
+ && (keyboard != null && keyboard.isShiftedOrShiftLocked())) {
KeyEvent newEvent = new KeyEvent(event.getDownTime(), event.getEventTime(),
event.getAction(), event.getKeyCode(), event.getRepeatCount(),
event.getDeviceId(), event.getScanCode(),
@@ -1141,16 +1085,36 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return super.onKeyUp(keyCode, event);
}
- public void commitTyped(final InputConnection ic) {
- if (!mHasUncommittedTypedChars) return;
- mHasUncommittedTypedChars = false;
- if (mComposingStringBuilder.length() > 0) {
+ // This will reset the whole input state to the starting state. It will clear
+ // the composing word, reset the last composed word, tell the inputconnection
+ // and the composingStateManager about it.
+ private void resetEntireInputState() {
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ updateSuggestions();
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.finishComposingText();
+ }
+ mVoiceProxy.setVoiceInputHighlighted(false);
+ }
+
+ private void resetComposingState(final boolean alsoResetLastComposedWord) {
+ mWordComposer.reset();
+ if (alsoResetLastComposedWord)
+ mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
+ }
+
+ public void commitTyped(final InputConnection ic, final int separatorCode) {
+ if (!mWordComposer.isComposingWord()) return;
+ final CharSequence typedWord = mWordComposer.getTypedWord();
+ if (typedWord.length() > 0) {
+ mLastComposedWord = mWordComposer.commitWord(
+ LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(),
+ separatorCode);
if (ic != null) {
- ic.commitText(mComposingStringBuilder, 1);
+ ic.commitText(typedWord, 1);
}
- mCommittedLength = mComposingStringBuilder.length();
- TextEntryState.acceptedTyped(mComposingStringBuilder);
- addToUserUnigramAndBigramDictionaries(mComposingStringBuilder,
+ addToUserUnigramAndBigramDictionaries(typedWord,
UserUnigramDictionary.FREQUENCY_FOR_TYPED);
}
updateSuggestions();
@@ -1166,60 +1130,41 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return false;
}
- private void swapSwapperAndSpace() {
- final InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
+ // "ic" may be null
+ private void swapSwapperAndSpaceWhileInBatchEdit(final InputConnection ic) {
+ if (null == ic) return;
CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
// It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called.
if (lastTwo != null && lastTwo.length() == 2
&& lastTwo.charAt(0) == Keyboard.CODE_SPACE) {
- ic.beginBatchEdit();
ic.deleteSurroundingText(2, 0);
ic.commitText(lastTwo.charAt(1) + " ", 1);
- ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
}
}
- private void maybeDoubleSpace() {
- if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
- final InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
+ private boolean maybeDoubleSpaceWhileInBatchEdit(final InputConnection ic) {
+ if (mCorrectionMode == Suggest.CORRECTION_NONE) return false;
+ if (ic == null) return false;
final CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
if (lastThree != null && lastThree.length() == 3
- && Utils.canBeFollowedByPeriod(lastThree.charAt(0))
+ && StringUtils.canBeFollowedByPeriod(lastThree.charAt(0))
&& lastThree.charAt(1) == Keyboard.CODE_SPACE
&& lastThree.charAt(2) == Keyboard.CODE_SPACE
&& mHandler.isAcceptingDoubleSpaces()) {
mHandler.cancelDoubleSpacesTimer();
- ic.beginBatchEdit();
ic.deleteSurroundingText(2, 0);
ic.commitText(". ", 1);
- ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
- mJustReplacedDoubleSpace = true;
- } else {
- mHandler.startDoubleSpacesTimer();
- }
- }
-
- // "ic" must not null
- private void maybeRemovePreviousPeriod(final InputConnection ic, CharSequence text) {
- // When the text's first character is '.', remove the previous period
- // if there is one.
- CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
- if (lastOne != null && lastOne.length() == 1
- && lastOne.charAt(0) == Keyboard.CODE_PERIOD
- && text.charAt(0) == Keyboard.CODE_PERIOD) {
- ic.deleteSurroundingText(1, 0);
+ return true;
}
+ return false;
}
- private void removeTrailingSpace() {
- final InputConnection ic = getCurrentInputConnection();
+ // "ic" may be null
+ private static void removeTrailingSpaceWhileInBatchEdit(final InputConnection ic) {
if (ic == null) return;
-
- CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
+ final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
if (lastOne != null && lastOne.length() == 1
&& lastOne.charAt(0) == Keyboard.CODE_SPACE) {
ic.deleteSurroundingText(1, 0);
@@ -1235,19 +1180,16 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return true;
}
- private boolean isAlphabet(int code) {
- if (Character.isLetter(code)) {
- return true;
- } else {
- return false;
- }
+ private static boolean isAlphabet(int code) {
+ return Character.isLetter(code);
}
private void onSettingsKeyPressed() {
if (isShowingOptionDialog()) return;
if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
showSubtypeSelectorAndSettings();
- } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, false /* exclude aux subtypes */)) {
+ } else if (SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(
+ false /* exclude aux subtypes */)) {
showOptionsMenu();
} else {
launchSettings();
@@ -1256,17 +1198,21 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Virtual codes representing custom requests. These are used in onCustomRequest() below.
public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1;
+ public static final int CODE_HAPTIC_AND_AUDIO_FEEDBACK = 2;
@Override
public boolean onCustomRequest(int requestCode) {
if (isShowingOptionDialog()) return false;
switch (requestCode) {
case CODE_SHOW_INPUT_METHOD_PICKER:
- if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, true /* include aux subtypes */)) {
+ if (SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
mImm.showInputMethodPicker();
return true;
}
return false;
+ case CODE_HAPTIC_AND_AUDIO_FEEDBACK:
+ hapticAndAudioFeedback(Keyboard.CODE_UNSPECIFIED);
+ return true;
}
return false;
}
@@ -1275,91 +1221,121 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return mOptionsDialog != null && mOptionsDialog.isShowing();
}
+ private static int getActionId(Keyboard keyboard) {
+ return keyboard != null ? keyboard.mId.imeActionId() : EditorInfo.IME_ACTION_NONE;
+ }
+
+ private void performeEditorAction(int actionId) {
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.performEditorAction(actionId);
+ }
+ }
+
+ private void handleLanguageSwitchKey() {
+ final boolean includesOtherImes = mSettingsValues.mIncludesOtherImesInLanguageSwitchList;
+ final IBinder token = getWindow().getWindow().getAttributes().token;
+ if (mShouldSwitchToLastSubtype) {
+ final InputMethodSubtypeCompatWrapper lastSubtype = mImm.getLastInputMethodSubtype();
+ final boolean lastSubtypeBelongsToThisIme = SubtypeUtils.checkIfSubtypeBelongsToThisIme(
+ this, lastSubtype);
+ if ((includesOtherImes || lastSubtypeBelongsToThisIme)
+ && mImm.switchToLastInputMethod(token)) {
+ mShouldSwitchToLastSubtype = false;
+ } else {
+ mImm.switchToNextInputMethod(token, !includesOtherImes);
+ mShouldSwitchToLastSubtype = true;
+ }
+ } else {
+ mImm.switchToNextInputMethod(token, !includesOtherImes);
+ }
+ }
+
+ private void sendKeyCodePoint(int code) {
+ // TODO: Remove this special handling of digit letters.
+ // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
+ if (code >= '0' && code <= '9') {
+ super.sendKeyChar((char)code);
+ return;
+ }
+
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ final String text = new String(new int[] { code }, 0, 1);
+ ic.commitText(text, text.length());
+ }
+ }
+
// Implementation of {@link KeyboardActionListener}.
@Override
- public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
+ public void onCodeInput(int primaryCode, int x, int y) {
final long when = SystemClock.uptimeMillis();
if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
mDeleteCount = 0;
}
mLastKeyTime = when;
final KeyboardSwitcher switcher = mKeyboardSwitcher;
- final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
- final boolean lastStateOfJustReplacedDoubleSpace = mJustReplacedDoubleSpace;
- mJustReplacedDoubleSpace = false;
- boolean shouldStartKeyTypedTimer = true;
+ // The space state depends only on the last character pressed and its own previous
+ // state. Here, we revert the space state to neutral if the key is actually modifying
+ // the input contents (any non-shift key), which is what we should do for
+ // all inputs that do not result in a special state. Each character handling is then
+ // free to override the state as they see fit.
+ final int spaceState = mSpaceState;
+ if (!mWordComposer.isComposingWord()) mIsAutoCorrectionIndicatorOn = false;
+
+ // TODO: Consolidate the double space timer, mLastKeyTime, and the space state.
+ if (primaryCode != Keyboard.CODE_SPACE) {
+ mHandler.cancelDoubleSpacesTimer();
+ }
+
+ boolean didAutoCorrect = false;
switch (primaryCode) {
case Keyboard.CODE_DELETE:
- handleBackspace(lastStateOfJustReplacedDoubleSpace);
+ mSpaceState = SPACE_STATE_NONE;
+ handleBackspace(spaceState);
mDeleteCount++;
mExpectingUpdateSelection = true;
+ mShouldSwitchToLastSubtype = true;
LatinImeLogger.logOnDelete();
break;
case Keyboard.CODE_SHIFT:
- // Shift key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch) {
- switcher.toggleShift();
- }
- shouldStartKeyTypedTimer = false;
- break;
case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
- // Symbol key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch) {
- switcher.changeKeyboardMode();
- }
- shouldStartKeyTypedTimer = false;
- break;
- case Keyboard.CODE_CANCEL:
- if (!isShowingOptionDialog()) {
- handleClose();
- }
+ // Shift and symbol key is handled in onPressKey() and onReleaseKey().
break;
case Keyboard.CODE_SETTINGS:
- if (!mHandler.isIgnoringSpecialKey()) {
- onSettingsKeyPressed();
- }
- shouldStartKeyTypedTimer = false;
- break;
- case Keyboard.CODE_CAPSLOCK:
- switcher.toggleCapsLock();
- //$FALL-THROUGH$
- case Keyboard.CODE_HAPTIC_AND_AUDIO_FEEDBACK_ONLY:
- // Dummy code for haptic and audio feedbacks.
- vibrate();
- playKeyClick(primaryCode);
+ onSettingsKeyPressed();
break;
case Keyboard.CODE_SHORTCUT:
- if (!mHandler.isIgnoringSpecialKey()) {
- mSubtypeSwitcher.switchToShortcutIME();
- }
- shouldStartKeyTypedTimer = false;
+ mSubtypeSwitcher.switchToShortcutIME();
break;
- case Keyboard.CODE_TAB:
- handleTab();
- // There are two cases for tab. Either we send a "next" event, that may change the
- // focus but will never move the cursor. Or, we send a real tab keycode, which some
- // applications may accept or ignore, and we don't know whether this will move the
- // cursor or not. So actually, we don't really know.
- // So to go with the safer option, we'd rather behave as if the user moved the
- // cursor when they didn't than the opposite. We also expect that most applications
- // will actually use tab only for focus movement.
- // To sum it up: do not update mExpectingUpdateSelection here.
+ case Keyboard.CODE_ACTION_ENTER:
+ performeEditorAction(getActionId(switcher.getKeyboard()));
+ break;
+ case Keyboard.CODE_ACTION_NEXT:
+ performeEditorAction(EditorInfo.IME_ACTION_NEXT);
+ break;
+ case Keyboard.CODE_ACTION_PREVIOUS:
+ EditorInfoCompatUtils.performEditorActionPrevious(getCurrentInputConnection());
+ break;
+ case Keyboard.CODE_LANGUAGE_SWITCH:
+ handleLanguageSwitchKey();
break;
default:
+ mSpaceState = SPACE_STATE_NONE;
if (mSettingsValues.isWordSeparator(primaryCode)) {
- handleSeparator(primaryCode, x, y);
+ didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
} else {
- handleCharacter(primaryCode, keyCodes, x, y);
+ handleCharacter(primaryCode, x, y, spaceState);
}
mExpectingUpdateSelection = true;
+ mShouldSwitchToLastSubtype = true;
break;
}
- switcher.onKey(primaryCode);
+ switcher.onCodeInput(primaryCode);
// Reset after any single keystroke
+ if (!didAutoCorrect)
+ mLastComposedWord.deactivate();
mEnteredText = null;
- if (shouldStartKeyTypedTimer) {
- mHandler.startKeyTypedTimer();
- }
}
@Override
@@ -1368,15 +1344,38 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
ic.beginBatchEdit();
- commitTyped(ic);
- maybeRemovePreviousPeriod(ic, text);
+ commitTyped(ic, LastComposedWord.NOT_A_SEPARATOR);
+ text = specificTldProcessingOnTextInput(ic, text);
+ if (SPACE_STATE_PHANTOM == mSpaceState) {
+ sendKeyCodePoint(Keyboard.CODE_SPACE);
+ }
ic.commitText(text, 1);
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
- mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
- mJustAddedMagicSpace = false;
+ mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT);
+ mSpaceState = SPACE_STATE_NONE;
mEnteredText = text;
- mHandler.startKeyTypedTimer();
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ }
+
+ // ic may not be null
+ private CharSequence specificTldProcessingOnTextInput(final InputConnection ic,
+ final CharSequence text) {
+ if (text.length() <= 1 || text.charAt(0) != Keyboard.CODE_PERIOD
+ || !Character.isLetter(text.charAt(1))) {
+ // Not a tld: do nothing.
+ return text;
+ }
+ // We have a TLD (or something that looks like this): make sure we don't add
+ // a space even if currently in phantom mode.
+ mSpaceState = SPACE_STATE_NONE;
+ final CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
+ if (lastOne != null && lastOne.length() == 1
+ && lastOne.charAt(0) == Keyboard.CODE_PERIOD) {
+ return text.subSequence(1, text.length());
+ } else {
+ return text;
+ }
}
@Override
@@ -1385,176 +1384,194 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mKeyboardSwitcher.onCancelInput();
}
- private void handleBackspace(boolean justReplacedDoubleSpace) {
+ private void handleBackspace(final int spaceState) {
if (mVoiceProxy.logAndRevertVoiceInput()) return;
-
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
ic.beginBatchEdit();
+ handleBackspaceWhileInBatchEdit(spaceState, ic);
+ ic.endBatchEdit();
+ }
+ // "ic" may not be null.
+ private void handleBackspaceWhileInBatchEdit(final int spaceState, final InputConnection ic) {
mVoiceProxy.handleBackspace();
- final boolean deleteChar = !mHasUncommittedTypedChars;
- if (mHasUncommittedTypedChars) {
- final int length = mComposingStringBuilder.length();
+ // In many cases, we may have to put the keyboard in auto-shift state again.
+ mHandler.postUpdateShiftState();
+
+ if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
+ // Cancel multi-character input: remove the text we just entered.
+ // This is triggered on backspace after a key that inputs multiple characters,
+ // like the smiley key or the .com key.
+ ic.deleteSurroundingText(mEnteredText.length(), 0);
+ // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
+ // In addition we know that spaceState is false, and that we should not be
+ // reverting any autocorrect at this point. So we can safely return.
+ return;
+ }
+
+ if (mWordComposer.isComposingWord()) {
+ final int length = mWordComposer.size();
if (length > 0) {
- mComposingStringBuilder.delete(length - 1, length);
mWordComposer.deleteLast();
- final CharSequence textWithUnderline =
- mComposingStateManager.isAutoCorrectionIndicatorOn()
- ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
- this, mComposingStringBuilder)
- : mComposingStringBuilder;
- ic.setComposingText(textWithUnderline, 1);
- if (mComposingStringBuilder.length() == 0) {
- mHasUncommittedTypedChars = false;
- }
- if (1 == length) {
- // 1 == length means we are about to erase the last character of the word,
- // so we can show bigrams.
+ ic.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+ // If we have deleted the last remaining character of a word, then we are not
+ // isComposingWord() any more.
+ if (!mWordComposer.isComposingWord()) {
+ // Not composing word any more, so we can show bigrams.
mHandler.postUpdateBigramPredictions();
} else {
- // length > 1, so we still have letters to deduce a suggestion from.
+ // Still composing a word, so we still have letters to deduce a suggestion from.
mHandler.postUpdateSuggestions();
}
} else {
ic.deleteSurroundingText(1, 0);
}
- }
- mHandler.postUpdateShiftKeyState();
-
- TextEntryState.backspace();
- if (TextEntryState.isUndoCommit()) {
- revertLastWord(ic);
- ic.endBatchEdit();
- return;
- }
- if (justReplacedDoubleSpace) {
- if (revertDoubleSpace(ic)) {
- ic.endBatchEdit();
+ } else {
+ if (mLastComposedWord.canRevertCommit()) {
+ Utils.Stats.onAutoCorrectionCancellation();
+ revertCommit(ic);
return;
}
- }
+ if (SPACE_STATE_DOUBLE == spaceState) {
+ if (revertDoubleSpaceWhileInBatchEdit(ic)) {
+ // No need to reset mSpaceState, it has already be done (that's why we
+ // receive it as a parameter)
+ return;
+ }
+ } else if (SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
+ if (revertSwapPunctuation(ic)) {
+ // Likewise
+ return;
+ }
+ }
- if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
- ic.deleteSurroundingText(mEnteredText.length(), 0);
- } else if (deleteChar) {
- if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) {
- // Go back to the suggestion mode if the user canceled the
- // "Touch again to save".
- // NOTE: In gerenal, we don't revert the word when backspacing
- // from a manual suggestion pick. We deliberately chose a
- // different behavior only in the case of picking the first
- // suggestion (typed word). It's intentional to have made this
- // inconsistent with backspacing after selecting other suggestions.
- revertLastWord(ic);
+ // No cancelling of commit/double space/swap: we have a regular backspace.
+ // We should backspace one char and restart suggestion if at the end of a word.
+ if (mLastSelectionStart != mLastSelectionEnd) {
+ // If there is a selection, remove it.
+ final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
+ ic.setSelection(mLastSelectionEnd, mLastSelectionEnd);
+ ic.deleteSurroundingText(lengthToDelete, 0);
} else {
- sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ // There is no selection, just delete one character.
+ if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
+ // This should never happen.
+ Log.e(TAG, "Backspace when we don't know the selection position");
+ }
+ ic.deleteSurroundingText(1, 0);
if (mDeleteCount > DELETE_ACCELERATE_AT) {
- sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ ic.deleteSurroundingText(1, 0);
}
}
+ if (isSuggestionsRequested()) {
+ restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(ic);
+ }
}
- ic.endBatchEdit();
}
- private void handleTab() {
- final int imeOptions = getCurrentInputEditorInfo().imeOptions;
- if (!EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
- && !EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)) {
- sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
- return;
- }
-
- final InputConnection ic = getCurrentInputConnection();
- if (ic == null)
- return;
-
- // True if keyboard is in either chording shift or manual temporary upper case mode.
- final boolean isManualTemporaryUpperCase = mKeyboardSwitcher.isManualTemporaryUpperCase();
- if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions)
- && !isManualTemporaryUpperCase) {
- EditorInfoCompatUtils.performEditorActionNext(ic);
- } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions)
- && isManualTemporaryUpperCase) {
- EditorInfoCompatUtils.performEditorActionPrevious(ic);
+ // ic may be null
+ private boolean maybeStripSpaceWhileInBatchEdit(final InputConnection ic, final int code,
+ final int spaceState, final boolean isFromSuggestionStrip) {
+ if (Keyboard.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) {
+ removeTrailingSpaceWhileInBatchEdit(ic);
+ return false;
+ } else if ((SPACE_STATE_WEAK == spaceState
+ || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
+ && isFromSuggestionStrip) {
+ if (mSettingsValues.isWeakSpaceSwapper(code)) {
+ return true;
+ } else {
+ if (mSettingsValues.isWeakSpaceStripper(code)) {
+ removeTrailingSpaceWhileInBatchEdit(ic);
+ }
+ return false;
+ }
+ } else {
+ return false;
}
}
- private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
+ private void handleCharacter(final int primaryCode, final int x,
+ final int y, final int spaceState) {
mVoiceProxy.handleCharacter();
-
- if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceStripper(primaryCode)) {
- removeTrailingSpace();
+ final InputConnection ic = getCurrentInputConnection();
+ if (null != ic) ic.beginBatchEdit();
+ // TODO: if ic is null, does it make any sense to call this?
+ handleCharacterWhileInBatchEdit(primaryCode, x, y, spaceState, ic);
+ if (null != ic) ic.endBatchEdit();
+ }
+
+ // "ic" may be null without this crashing, but the behavior will be really strange
+ private void handleCharacterWhileInBatchEdit(final int primaryCode,
+ final int x, final int y, final int spaceState, final InputConnection ic) {
+ boolean isComposingWord = mWordComposer.isComposingWord();
+
+ if (SPACE_STATE_PHANTOM == spaceState &&
+ !mSettingsValues.isSymbolExcludedFromWordSeparators(primaryCode)) {
+ if (isComposingWord) {
+ // Sanity check
+ throw new RuntimeException("Should not be composing here");
+ }
+ sendKeyCodePoint(Keyboard.CODE_SPACE);
}
- int code = primaryCode;
- if ((isAlphabet(code) || mSettingsValues.isSymbolExcludedFromWordSeparators(code))
+ if ((isAlphabet(primaryCode)
+ || mSettingsValues.isSymbolExcludedFromWordSeparators(primaryCode))
&& isSuggestionsRequested() && !isCursorTouchingWord()) {
- if (!mHasUncommittedTypedChars) {
- mHasUncommittedTypedChars = true;
- mComposingStringBuilder.setLength(0);
- mWordComposer.reset();
+ if (!isComposingWord) {
+ // Reset entirely the composing state anyway, then start composing a new word unless
+ // the character is a single quote. The idea here is, single quote is not a
+ // separator and it should be treated as a normal character, except in the first
+ // position where it should not start composing a word.
+ isComposingWord = (Keyboard.CODE_SINGLE_QUOTE != primaryCode);
+ // Here we don't need to reset the last composed word. It will be reset
+ // when we commit this one, if we ever do; if on the other hand we backspace
+ // it entirely and resume suggestions on the previous word, we'd like to still
+ // have touch coordinates for it.
+ resetComposingState(false /* alsoResetLastComposedWord */);
clearSuggestions();
- mComposingStateManager.onFinishComposingText();
- }
- }
- final KeyboardSwitcher switcher = mKeyboardSwitcher;
- if (switcher.isShiftedOrShiftLocked()) {
- if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
- || keyCodes[0] > Character.MAX_CODE_POINT) {
- return;
- }
- code = keyCodes[0];
- if (switcher.isAlphabetMode() && Character.isLowerCase(code)) {
- // In some locales, such as Turkish, Character.toUpperCase() may return a wrong
- // character because it doesn't take care of locale.
- final String upperCaseString = new String(new int[] {code}, 0, 1)
- .toUpperCase(mSubtypeSwitcher.getInputLocale());
- if (upperCaseString.codePointCount(0, upperCaseString.length()) == 1) {
- code = upperCaseString.codePointAt(0);
- } else {
- // Some keys, such as [eszett], have upper case as multi-characters.
- onTextInput(upperCaseString);
- return;
- }
}
}
- if (mHasUncommittedTypedChars) {
- mComposingStringBuilder.append((char) code);
- mWordComposer.add(code, keyCodes, x, y);
- final InputConnection ic = getCurrentInputConnection();
+ if (isComposingWord) {
+ mWordComposer.add(
+ primaryCode, x, y, mKeyboardSwitcher.getKeyboardView().getKeyDetector());
if (ic != null) {
// If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) {
mWordComposer.setAutoCapitalized(getCurrentAutoCapsState());
- mComposingStateManager.onStartComposingText();
}
- final CharSequence textWithUnderline =
- mComposingStateManager.isAutoCorrectionIndicatorOn()
- ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
- this, mComposingStringBuilder)
- : mComposingStringBuilder;
- ic.setComposingText(textWithUnderline, 1);
+ ic.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
}
mHandler.postUpdateSuggestions();
} else {
- sendKeyChar((char)code);
- }
- if (mJustAddedMagicSpace && mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
- swapSwapperAndSpace();
- } else {
- mJustAddedMagicSpace = false;
- }
+ final boolean swapWeakSpace = maybeStripSpaceWhileInBatchEdit(ic, primaryCode,
+ spaceState, KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
+
+ sendKeyCodePoint(primaryCode);
- switcher.updateShiftState();
- if (LatinIME.PERF_DEBUG) measureCps();
- TextEntryState.typedCharacter((char) code, mSettingsValues.isWordSeparator(code), x, y);
+ if (swapWeakSpace) {
+ swapSwapperAndSpaceWhileInBatchEdit(ic);
+ mSpaceState = SPACE_STATE_WEAK;
+ }
+ // Some characters are not word separators, yet they don't start a new
+ // composing span. For these, we haven't changed the suggestion strip, and
+ // if the "add to dictionary" hint is shown, we should do so now. Examples of
+ // such characters include single quote, dollar, and others; the exact list is
+ // the list of characters for which we enter handleCharacterWhileInBatchEdit
+ // that don't match the test if ((isAlphabet...)) at the top of this method.
+ if (null != mSuggestionsView && mSuggestionsView.dismissAddToDictionaryHint()) {
+ mHandler.postUpdateBigramPredictions();
+ }
+ }
+ Utils.Stats.onNonSeparator((char)primaryCode, x, y);
}
- private void handleSeparator(int primaryCode, int x, int y) {
+ // Returns true if we did an autocorrection, false otherwise.
+ private boolean handleSeparator(final int primaryCode, final int x, final int y,
+ final int spaceState) {
mVoiceProxy.handleSeparator();
- mComposingStateManager.onFinishComposingText();
// Should dismiss the "Touch again to save" message when handling separator
if (mSuggestionsView != null && mSuggestionsView.dismissAddToDictionaryHint()) {
@@ -1562,71 +1579,85 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mHandler.postUpdateSuggestions();
}
- boolean pickedDefault = false;
+ boolean didAutoCorrect = false;
// Handle separator
final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.beginBatchEdit();
}
- if (mHasUncommittedTypedChars) {
+ if (mWordComposer.isComposingWord()) {
// In certain languages where single quote is a separator, it's better
// not to auto correct, but accept the typed word. For instance,
// in Italian dov' should not be expanded to dove' because the elision
// requires the last vowel to be removed.
final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
- && !mInputTypeNoAutoCorrect;
+ && !mInputAttributes.mInputTypeNoAutoCorrect;
if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
- pickedDefault = pickDefaultSuggestion(primaryCode);
+ commitCurrentAutoCorrection(primaryCode, ic);
+ didAutoCorrect = true;
} else {
- commitTyped(ic);
+ commitTyped(ic, primaryCode);
}
}
- if (mJustAddedMagicSpace) {
- if (mSettingsValues.isMagicSpaceSwapper(primaryCode)) {
- sendKeyChar((char)primaryCode);
- swapSwapperAndSpace();
- } else {
- if (mSettingsValues.isMagicSpaceStripper(primaryCode)) removeTrailingSpace();
- sendKeyChar((char)primaryCode);
- mJustAddedMagicSpace = false;
- }
- } else {
- sendKeyChar((char)primaryCode);
- }
+ final boolean swapWeakSpace = maybeStripSpaceWhileInBatchEdit(ic, primaryCode, spaceState,
+ KeyboardActionListener.SUGGESTION_STRIP_COORDINATE == x);
- if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
- maybeDoubleSpace();
+ if (SPACE_STATE_PHANTOM == spaceState &&
+ mSettingsValues.isPhantomSpacePromotingSymbol(primaryCode)) {
+ sendKeyCodePoint(Keyboard.CODE_SPACE);
}
+ sendKeyCodePoint(primaryCode);
- TextEntryState.typedCharacter((char) primaryCode, true, x, y);
-
- if (pickedDefault) {
- CharSequence typedWord = mWordComposer.getTypedWord();
- TextEntryState.backToAcceptedDefault(typedWord);
- if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) {
- InputConnectionCompatUtils.commitCorrection(
- ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord);
- }
- }
if (Keyboard.CODE_SPACE == primaryCode) {
+ if (isSuggestionsRequested()) {
+ if (maybeDoubleSpaceWhileInBatchEdit(ic)) {
+ mSpaceState = SPACE_STATE_DOUBLE;
+ } else if (!isShowingPunctuationList()) {
+ mSpaceState = SPACE_STATE_WEAK;
+ }
+ }
+
+ mHandler.startDoubleSpacesTimer();
if (!isCursorTouchingWord()) {
mHandler.cancelUpdateSuggestions();
mHandler.postUpdateBigramPredictions();
}
} else {
+ if (swapWeakSpace) {
+ swapSwapperAndSpaceWhileInBatchEdit(ic);
+ mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
+ } else if (SPACE_STATE_PHANTOM == spaceState) {
+ // If we are in phantom space state, and the user presses a separator, we want to
+ // stay in phantom space state so that the next keypress has a chance to add the
+ // space. For example, if I type "Good dat", pick "day" from the suggestion strip
+ // then insert a comma and go on to typing the next word, I want the space to be
+ // inserted automatically before the next word, the same way it is when I don't
+ // input the comma.
+ mSpaceState = SPACE_STATE_PHANTOM;
+ }
+
// Set punctuation right away. onUpdateSelection will fire but tests whether it is
// already displayed or not, so it's okay.
setPunctuationSuggestions();
}
- mKeyboardSwitcher.updateShiftState();
+
+ Utils.Stats.onSeparator((char)primaryCode, x, y);
+
if (ic != null) {
ic.endBatchEdit();
}
+ return didAutoCorrect;
+ }
+
+ private CharSequence getTextWithUnderline(final CharSequence text) {
+ return mIsAutoCorrectionIndicatorOn
+ ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text)
+ : text;
}
private void handleClose() {
- commitTyped(getCurrentInputConnection());
+ commitTyped(getCurrentInputConnection(), LastComposedWord.NOT_A_SEPARATOR);
mVoiceProxy.handleClose();
requestHideSelf(0);
LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
@@ -1635,7 +1666,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
public boolean isSuggestionsRequested() {
- return mIsSettingsSuggestionStripOn
+ return mInputAttributes.mIsSettingsSuggestionStripOn
&& (mCorrectionMode > 0 || isShowingSuggestionsStrip());
}
@@ -1653,11 +1684,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
public boolean isSuggestionsStripVisible() {
if (mSuggestionsView == null)
return false;
- if (mSuggestionsView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting())
+ if (mSuggestionsView.isShowingAddToDictionaryHint())
return true;
if (!isShowingSuggestionsStrip())
return false;
- if (mApplicationSpecifiedCompletionOn)
+ if (mInputAttributes.mApplicationSpecifiedCompletionOn)
return true;
return isSuggestionsRequested();
}
@@ -1681,36 +1712,27 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
public void clearSuggestions() {
- setSuggestions(SuggestedWords.EMPTY);
+ setSuggestions(SuggestedWords.EMPTY, false);
+ setAutoCorrectionIndicator(false);
}
- public void setSuggestions(SuggestedWords words) {
+ private void setSuggestions(final SuggestedWords words, final boolean isAutoCorrection) {
if (mSuggestionsView != null) {
mSuggestionsView.setSuggestions(words);
- mKeyboardSwitcher.onAutoCorrectionStateChanged(
- words.hasWordAboveAutoCorrectionScoreThreshold());
+ mKeyboardSwitcher.onAutoCorrectionStateChanged(isAutoCorrection);
}
+ }
+ private void setAutoCorrectionIndicator(final boolean newAutoCorrectionIndicator) {
// Put a blue underline to a word in TextView which will be auto-corrected.
final InputConnection ic = getCurrentInputConnection();
- if (ic != null) {
- final boolean oldAutoCorrectionIndicator =
- mComposingStateManager.isAutoCorrectionIndicatorOn();
- final boolean newAutoCorrectionIndicator = Utils.willAutoCorrect(words);
- if (oldAutoCorrectionIndicator != newAutoCorrectionIndicator) {
- if (LatinImeLogger.sDBG) {
- Log.d(TAG, "Flip the indicator. " + oldAutoCorrectionIndicator
- + " -> " + newAutoCorrectionIndicator);
- }
- final CharSequence textWithUnderline = newAutoCorrectionIndicator
- ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(
- this, mComposingStringBuilder)
- : mComposingStringBuilder;
- if (!TextUtils.isEmpty(textWithUnderline)) {
- ic.setComposingText(textWithUnderline, 1);
- }
- mComposingStateManager.setAutoCorrectionIndicatorOn(newAutoCorrectionIndicator);
- }
+ if (ic == null) return;
+ if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator
+ && mWordComposer.isComposingWord()) {
+ mIsAutoCorrectionIndicatorOn = newAutoCorrectionIndicator;
+ final CharSequence textWithUnderline =
+ getTextWithUnderline(mWordComposer.getTypedWord());
+ ic.setComposingText(textWithUnderline, 1);
}
}
@@ -1718,18 +1740,21 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Check if we have a suggestion engine attached.
if ((mSuggest == null || !isSuggestionsRequested())
&& !mVoiceProxy.isVoiceInputHighlighted()) {
+ if (mWordComposer.isComposingWord()) {
+ Log.w(TAG, "Called updateSuggestions but suggestions were not requested!");
+ mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
+ }
return;
}
mHandler.cancelUpdateSuggestions();
mHandler.cancelUpdateBigramPredictions();
- if (!mHasUncommittedTypedChars) {
+ if (!mWordComposer.isComposingWord()) {
setPunctuationSuggestions();
return;
}
- final WordComposer wordComposer = mWordComposer;
// TODO: May need a better way of retrieving previous word
final InputConnection ic = getCurrentInputConnection();
final CharSequence prevWord;
@@ -1738,27 +1763,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
} else {
prevWord = EditingUtils.getPreviousWord(ic, mSettingsValues.mWordSeparators);
}
- // getSuggestedWordBuilder handles gracefully a null value of prevWord
- final SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
- wordComposer, prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo());
-
- boolean autoCorrectionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
- final CharSequence typedWord = wordComposer.getTypedWord();
- // Here, we want to promote a whitelisted word if exists.
- // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
- // but still autocorrected from - in the case the whitelist only capitalizes the word.
- // The whitelist should be case-insensitive, so it's not possible to be consistent with
- // a boolean flag. Right now this is handled with a slight hack in
- // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
- final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
- mSuggest.getUnigramDictionaries(), typedWord, preferCapitalization());
- if (mCorrectionMode == Suggest.CORRECTION_FULL
- || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
- autoCorrectionAvailable |= (!allowsToBeAutoCorrected);
- }
- // Don't auto-correct words with multiple capital letter
- autoCorrectionAvailable &= !wordComposer.isMostlyCaps();
- autoCorrectionAvailable &= !TextEntryState.isRecorrecting();
+
+ final CharSequence typedWord = mWordComposer.getTypedWord();
+ // getSuggestedWords handles gracefully a null value of prevWord
+ final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer,
+ prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), mCorrectionMode);
// Basically, we update the suggestion strip only when suggestion count > 1. However,
// there is an exception: We update the suggestion strip whenever typed word's length
@@ -1766,131 +1775,126 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// in most cases, suggestion count is 1 when typed word's length is 1, but we do always
// need to clear the previous state when the user starts typing a word (i.e. typed word's
// length == 1).
- if (typedWord != null) {
- if (builder.size() > 1 || typedWord.length() == 1 || (!allowsToBeAutoCorrected)
- || mSuggestionsView.isShowingAddToDictionaryHint()) {
- builder.setTypedWordValid(!allowsToBeAutoCorrected).setHasMinimalSuggestion(
- autoCorrectionAvailable);
- } else {
- SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions();
- if (previousSuggestions == mSettingsValues.mSuggestPuncList) {
- if (builder.size() == 0) {
- return;
- }
- previousSuggestions = SuggestedWords.EMPTY;
- }
- builder.addTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
+ if (suggestedWords.size() > 1 || typedWord.length() == 1
+ || !suggestedWords.mAllowsToBeAutoCorrected
+ || mSuggestionsView.isShowingAddToDictionaryHint()) {
+ showSuggestions(suggestedWords, typedWord);
+ } else {
+ SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions();
+ if (previousSuggestions == mSettingsValues.mSuggestPuncList) {
+ previousSuggestions = SuggestedWords.EMPTY;
}
- }
- showSuggestions(builder.build(), typedWord);
- }
-
- public void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) {
- final boolean shouldBlockAutoCorrectionBySafetyNet =
- Utils.shouldBlockAutoCorrectionBySafetyNet(suggestedWords, mSuggest);
- if (shouldBlockAutoCorrectionBySafetyNet) {
- suggestedWords.setShouldBlockAutoCorrection();
- }
- setSuggestions(suggestedWords);
+ final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
+ SuggestedWords.getTypedWordAndPreviousSuggestions(
+ typedWord, previousSuggestions);
+ final SuggestedWords obsoleteSuggestedWords =
+ new SuggestedWords(typedWordAndPreviousSuggestions,
+ false /* typedWordValid */,
+ false /* hasAutoCorrectionCandidate */,
+ false /* allowsToBeAutoCorrected */,
+ false /* isPunctuationSuggestions */,
+ true /* isObsoleteSuggestions */);
+ showSuggestions(obsoleteSuggestedWords, typedWord);
+ }
+ }
+
+ public void showSuggestions(final SuggestedWords suggestedWords, final CharSequence typedWord) {
+ final CharSequence autoCorrection;
if (suggestedWords.size() > 0) {
- if (shouldBlockAutoCorrectionBySafetyNet) {
- mBestWord = typedWord;
- } else if (suggestedWords.hasAutoCorrectionWord()) {
- mBestWord = suggestedWords.getWord(1);
+ if (suggestedWords.hasAutoCorrectionWord()) {
+ autoCorrection = suggestedWords.getWord(1);
} else {
- mBestWord = typedWord;
+ autoCorrection = typedWord;
}
} else {
- mBestWord = null;
+ autoCorrection = null;
}
+ mWordComposer.setAutoCorrection(autoCorrection);
+ final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
+ setSuggestions(suggestedWords, isAutoCorrection);
+ setAutoCorrectionIndicator(isAutoCorrection);
setSuggestionStripShown(isSuggestionsStripVisible());
}
- private boolean pickDefaultSuggestion(int separatorCode) {
+ private void commitCurrentAutoCorrection(final int separatorCodePoint,
+ final InputConnection ic) {
// Complete any pending suggestions query first
if (mHandler.hasPendingUpdateSuggestions()) {
mHandler.cancelUpdateSuggestions();
updateSuggestions();
}
- if (mBestWord != null && mBestWord.length() > 0) {
- TextEntryState.acceptedDefault(mWordComposer.getTypedWord(), mBestWord, separatorCode);
+ final CharSequence autoCorrection = mWordComposer.getAutoCorrectionOrNull();
+ if (autoCorrection != null) {
+ final String typedWord = mWordComposer.getTypedWord();
+ if (TextUtils.isEmpty(typedWord)) {
+ throw new RuntimeException("We have an auto-correction but the typed word "
+ + "is empty? Impossible! I must commit suicide.");
+ }
+ Utils.Stats.onAutoCorrection(typedWord, autoCorrection.toString(), separatorCodePoint);
mExpectingUpdateSelection = true;
- commitBestWord(mBestWord);
+ commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
+ separatorCodePoint);
// Add the word to the user unigram dictionary if it's not a known word
- addToUserUnigramAndBigramDictionaries(mBestWord,
+ addToUserUnigramAndBigramDictionaries(autoCorrection,
UserUnigramDictionary.FREQUENCY_FOR_TYPED);
- return true;
+ if (!typedWord.equals(autoCorrection) && null != ic) {
+ // This will make the correction flash for a short while as a visual clue
+ // to the user that auto-correction happened.
+ InputConnectionCompatUtils.commitCorrection(ic,
+ mLastSelectionEnd - typedWord.length(), typedWord, autoCorrection);
+ }
}
- return false;
}
@Override
- public void pickSuggestionManually(int index, CharSequence suggestion) {
- mComposingStateManager.onFinishComposingText();
- SuggestedWords suggestions = mSuggestionsView.getSuggestions();
+ public void pickSuggestionManually(final int index, final CharSequence suggestion) {
+ final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions();
mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion,
mSettingsValues.mWordSeparators);
- final boolean recorrecting = TextEntryState.isRecorrecting();
- final InputConnection ic = getCurrentInputConnection();
- if (ic != null) {
- ic.beginBatchEdit();
+ if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0) {
+ int firstChar = Character.codePointAt(suggestion, 0);
+ if ((!mSettingsValues.isWeakSpaceStripper(firstChar))
+ && (!mSettingsValues.isWeakSpaceSwapper(firstChar))) {
+ sendKeyCodePoint(Keyboard.CODE_SPACE);
+ }
}
- if (mApplicationSpecifiedCompletionOn && mApplicationSpecifiedCompletions != null
+
+ if (mInputAttributes.mApplicationSpecifiedCompletionOn
+ && mApplicationSpecifiedCompletions != null
&& index >= 0 && index < mApplicationSpecifiedCompletions.length) {
- if (ic != null) {
- final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
- ic.commitCompletion(completionInfo);
- }
- mCommittedLength = suggestion.length();
if (mSuggestionsView != null) {
mSuggestionsView.clear();
}
mKeyboardSwitcher.updateShiftState();
+ resetComposingState(true /* alsoResetLastComposedWord */);
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
- ic.endBatchEdit();
+ final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
+ ic.commitCompletion(completionInfo);
}
return;
}
- // If this is a punctuation, apply it through the normal key press
- if (suggestion.length() == 1 && (mSettingsValues.isWordSeparator(suggestion.charAt(0))
- || mSettingsValues.isSuggestedPunctuation(suggestion.charAt(0)))) {
+ // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
+ if (suggestion.length() == 1 && isShowingPunctuationList()) {
// Word separators are suggested before the user inputs something.
// So, LatinImeLogger logs "" as a user's input.
- LatinImeLogger.logOnManualSuggestion(
- "", suggestion.toString(), index, suggestions.mWords);
- // Find out whether the previous character is a space. If it is, as a special case
- // for punctuation entered through the suggestion strip, it should be considered
- // a magic space even if it was a normal space. This is meant to help in case the user
- // pressed space on purpose of displaying the suggestion strip punctuation.
- final int rawPrimaryCode = suggestion.charAt(0);
- // Maybe apply the "bidi mirrored" conversions for parentheses
- final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
- final boolean isRtl = keyboard != null && keyboard.mIsRtlKeyboard;
- final int primaryCode = Key.getRtlParenthesisCode(rawPrimaryCode, isRtl);
-
- final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : "";
- final int toLeft = (ic == null || TextUtils.isEmpty(beforeText))
- ? 0 : beforeText.charAt(0);
- final boolean oldMagicSpace = mJustAddedMagicSpace;
- if (Keyboard.CODE_SPACE == toLeft) mJustAddedMagicSpace = true;
- onCodeInput(primaryCode, new int[] { primaryCode },
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
- KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
- mJustAddedMagicSpace = oldMagicSpace;
- if (ic != null) {
- ic.endBatchEdit();
- }
+ LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestedWords);
+ // Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
+ final int primaryCode = suggestion.charAt(0);
+ onCodeInput(primaryCode,
+ KeyboardActionListener.SUGGESTION_STRIP_COORDINATE,
+ KeyboardActionListener.SUGGESTION_STRIP_COORDINATE);
return;
}
- if (!mHasUncommittedTypedChars) {
- // If we are not composing a word, then it was a suggestion inferred from
- // context - no user input. We should reset the word composer.
- mWordComposer.reset();
- }
+ // We need to log before we commit, because the word composer will store away the user
+ // typed word.
+ LatinImeLogger.logOnManualSuggestion(mWordComposer.getTypedWord().toString(),
+ suggestion.toString(), index, suggestedWords);
mExpectingUpdateSelection = true;
- commitBestWord(suggestion);
+ commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
+ LastComposedWord.NOT_A_SEPARATOR);
// Add the word to the auto dictionary if it's not a known word
if (index == 0) {
addToUserUnigramAndBigramDictionaries(suggestion,
@@ -1898,13 +1902,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
} else {
addToOnlyBigramDictionary(suggestion, 1);
}
- LatinImeLogger.logOnManualSuggestion(mComposingStringBuilder.toString(),
- suggestion.toString(), index, suggestions.mWords);
- TextEntryState.acceptedSuggestion(mComposingStringBuilder.toString(), suggestion);
- // Follow it with a space
- if (mInsertSpaceOnPickSuggestionManually && !recorrecting) {
- sendMagicSpace();
- }
+ mSpaceState = SPACE_STATE_PHANTOM;
+ // TODO: is this necessary?
+ mKeyboardSwitcher.updateShiftState();
// We should show the "Touch again to save" hint if the user pressed the first entry
// AND either:
@@ -1922,13 +1922,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
|| !AutoCorrection.isValidWord(
mSuggest.getUnigramDictionaries(), suggestion, true));
- if (!recorrecting) {
- // Fool the state watcher so that a subsequent backspace will not do a revert, unless
- // we just did a correction, in which case we need to stay in
- // TextEntryState.State.PICKED_SUGGESTION state.
- TextEntryState.typedCharacter((char) Keyboard.CODE_SPACE, true,
- WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
- }
+ Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE, WordComposer.NOT_A_COORDINATE,
+ WordComposer.NOT_A_COORDINATE);
if (!showingAddToDictionaryHint) {
// If we're not showing the "Touch again to save", then show corrections again.
// In case the cursor position doesn't change, make sure we show the suggestions again.
@@ -1936,26 +1931,21 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Updating the predictions right away may be slow and feel unresponsive on slower
// terminals. On the other hand if we just postUpdateBigramPredictions() it will
// take a noticeable delay to update them which may feel uneasy.
- }
- if (showingAddToDictionaryHint) {
- if (mIsUserDictionaryAvaliable) {
- mSuggestionsView.showAddToDictionaryHint(suggestion);
+ } else {
+ if (mIsUserDictionaryAvailable) {
+ mSuggestionsView.showAddToDictionaryHint(
+ suggestion, mSettingsValues.mHintToSaveText);
} else {
mHandler.postUpdateSuggestions();
}
}
- if (ic != null) {
- ic.endBatchEdit();
- }
}
/**
* Commits the chosen word to the text field and saves it for later retrieval.
*/
- private void commitBestWord(CharSequence bestWord) {
- final KeyboardSwitcher switcher = mKeyboardSwitcher;
- if (!switcher.isKeyboardAvailable())
- return;
+ private void commitChosenWord(final CharSequence bestWord, final int commitType,
+ final int separatorCode) {
final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators);
@@ -1967,11 +1957,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
ic.commitText(bestWord, 1);
}
}
- mHasUncommittedTypedChars = false;
- mCommittedLength = bestWord.length();
+ // 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, bestWord.toString(),
+ separatorCode);
}
- private static final WordComposer sEmptyWordComposer = new WordComposer();
public void updateBigramPredictions() {
if (mSuggest == null || !isSuggestionsRequested())
return;
@@ -1981,22 +1974,31 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return;
}
- final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
- mSettingsValues.mWordSeparators);
- SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(sEmptyWordComposer,
- prevWord, mKeyboardSwitcher.getLatinKeyboard().getProximityInfo());
+ final SuggestedWords suggestedWords;
+ if (mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
+ final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
+ mSettingsValues.mWordSeparators);
+ if (!TextUtils.isEmpty(prevWord)) {
+ suggestedWords = mSuggest.getBigramPredictions(prevWord);
+ } else {
+ suggestedWords = null;
+ }
+ } else {
+ suggestedWords = null;
+ }
- if (builder.size() > 0) {
+ if (null != suggestedWords && suggestedWords.size() > 0) {
// Explicitly supply an empty typed word (the no-second-arg version of
// showSuggestions will retrieve the word near the cursor, we don't want that here)
- showSuggestions(builder.build(), "");
+ showSuggestions(suggestedWords, "");
} else {
if (!isShowingPunctuationList()) setPunctuationSuggestions();
}
}
public void setPunctuationSuggestions() {
- setSuggestions(mSettingsValues.mSuggestPuncList);
+ setSuggestions(mSettingsValues.mSuggestPuncList, false);
+ setAutoCorrectionIndicator(false);
setSuggestionStripShown(isSuggestionsStripVisible());
}
@@ -2055,72 +2057,157 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
public boolean isCursorTouchingWord() {
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return false;
- CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
- CharSequence toRight = ic.getTextAfterCursor(1, 0);
- if (!TextUtils.isEmpty(toLeft)
- && !mSettingsValues.isWordSeparator(toLeft.charAt(0))
- && !mSettingsValues.isSuggestedPunctuation(toLeft.charAt(0))) {
+ CharSequence before = ic.getTextBeforeCursor(1, 0);
+ CharSequence after = ic.getTextAfterCursor(1, 0);
+ if (!TextUtils.isEmpty(before) && !mSettingsValues.isWordSeparator(before.charAt(0))
+ && !mSettingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) {
return true;
}
- if (!TextUtils.isEmpty(toRight)
- && !mSettingsValues.isWordSeparator(toRight.charAt(0))
- && !mSettingsValues.isSuggestedPunctuation(toRight.charAt(0))) {
+ if (!TextUtils.isEmpty(after) && !mSettingsValues.isWordSeparator(after.charAt(0))
+ && !mSettingsValues.isSymbolExcludedFromWordSeparators(after.charAt(0))) {
return true;
}
return false;
}
- // "ic" must not null
- private boolean sameAsTextBeforeCursor(final InputConnection ic, CharSequence text) {
- CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
+ // "ic" must not be null
+ private static boolean sameAsTextBeforeCursor(final InputConnection ic,
+ final CharSequence text) {
+ final CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
return TextUtils.equals(text, beforeText);
}
- // "ic" must not null
- private void revertLastWord(final InputConnection ic) {
- if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
- sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ // "ic" must not be null
+ /**
+ * Check if the cursor is actually at the end of a word. If so, restart suggestions on this
+ * word, else do nothing.
+ */
+ private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(
+ final InputConnection ic) {
+ // Bail out if the cursor is not at the end of a word (cursor must be preceded by
+ // non-whitespace, non-separator, non-start-of-text)
+ // Example ("|" is the cursor here) : <SOL>"|a" " |a" " | " all get rejected here.
+ final CharSequence textBeforeCursor = ic.getTextBeforeCursor(1, 0);
+ if (TextUtils.isEmpty(textBeforeCursor)
+ || mSettingsValues.isWordSeparator(textBeforeCursor.charAt(0))) return;
+
+ // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace,
+ // separator or end of line/text)
+ // Example: "test|"<EOL> "te|st" get rejected here
+ final CharSequence textAfterCursor = ic.getTextAfterCursor(1, 0);
+ if (!TextUtils.isEmpty(textAfterCursor)
+ && !mSettingsValues.isWordSeparator(textAfterCursor.charAt(0))) return;
+
+ // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe)
+ // Example: " -|" gets rejected here but "e-|" and "e|" are okay
+ CharSequence word = EditingUtils.getWordAtCursor(ic, mSettingsValues.mWordSeparators);
+ // We don't suggest on leading single quotes, so we have to remove them from the word if
+ // it starts with single quotes.
+ while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) {
+ word = word.subSequence(1, word.length());
+ }
+ if (TextUtils.isEmpty(word)) return;
+ final char firstChar = word.charAt(0); // we just tested that word is not empty
+ if (word.length() == 1 && !Character.isLetter(firstChar)) return;
+
+ // We only suggest on words that start with a letter or a symbol that is excluded from
+ // word separators (see #handleCharacterWhileInBatchEdit).
+ if (!(isAlphabet(firstChar)
+ || mSettingsValues.isSymbolExcludedFromWordSeparators(firstChar))) {
return;
}
- final CharSequence separator = ic.getTextBeforeCursor(1, 0);
- ic.deleteSurroundingText(1, 0);
- final CharSequence textToTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
- ic.deleteSurroundingText(mCommittedLength, 0);
-
- // Re-insert "separator" only when the deleted character was word separator and the
- // composing text wasn't equal to the auto-corrected text which can be found before
- // the cursor.
- if (!TextUtils.isEmpty(separator)
- && mSettingsValues.isWordSeparator(separator.charAt(0))
- && !TextUtils.equals(mComposingStringBuilder, textToTheLeft)) {
- ic.commitText(mComposingStringBuilder, 1);
- TextEntryState.acceptedTyped(mComposingStringBuilder);
- ic.commitText(separator, 1);
- TextEntryState.typedCharacter(separator.charAt(0), true,
- WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
- // Clear composing text
- mComposingStringBuilder.setLength(0);
- } else {
- mHasUncommittedTypedChars = true;
- ic.setComposingText(mComposingStringBuilder, 1);
- TextEntryState.backspace();
+ // Okay, we are at the end of a word. Restart suggestions.
+ restartSuggestionsOnWordBeforeCursor(ic, word);
+ }
+
+ // "ic" must not be null
+ private void restartSuggestionsOnWordBeforeCursor(final InputConnection ic,
+ final CharSequence word) {
+ mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
+ ic.deleteSurroundingText(word.length(), 0);
+ ic.setComposingText(word, 1);
+ mHandler.postUpdateSuggestions();
+ }
+
+ // "ic" must not be null
+ private void revertCommit(final InputConnection ic) {
+ final String originallyTypedWord = mLastComposedWord.mTypedWord;
+ final CharSequence committedWord = mLastComposedWord.mCommittedWord;
+ final int cancelLength = committedWord.length();
+ final int separatorLength = LastComposedWord.getSeparatorLength(
+ mLastComposedWord.mSeparatorCode);
+ // TODO: should we check our saved separator against the actual contents of the text view?
+ if (DEBUG) {
+ if (mWordComposer.isComposingWord()) {
+ throw new RuntimeException("revertCommit, but we are composing a word");
+ }
+ final String wordBeforeCursor =
+ ic.getTextBeforeCursor(cancelLength + separatorLength, 0)
+ .subSequence(0, cancelLength).toString();
+ if (!TextUtils.equals(committedWord, wordBeforeCursor)) {
+ throw new RuntimeException("revertCommit check failed: we thought we were "
+ + "reverting \"" + committedWord
+ + "\", but before the cursor we found \"" + wordBeforeCursor + "\"");
+ }
}
+ ic.deleteSurroundingText(cancelLength + separatorLength, 0);
+ if (0 == separatorLength || mLastComposedWord.didCommitTypedWord()) {
+ // This is the case when we cancel a manual pick.
+ // We should restart suggestion on the word right away.
+ mWordComposer.resumeSuggestionOnLastComposedWord(mLastComposedWord);
+ ic.setComposingText(originallyTypedWord, 1);
+ } else {
+ ic.commitText(originallyTypedWord, 1);
+ // Re-insert the separator
+ sendKeyCodePoint(mLastComposedWord.mSeparatorCode);
+ Utils.Stats.onSeparator(mLastComposedWord.mSeparatorCode, WordComposer.NOT_A_COORDINATE,
+ WordComposer.NOT_A_COORDINATE);
+ // Don't restart suggestion yet. We'll restart if the user deletes the
+ // separator.
+ }
+ mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
mHandler.cancelUpdateBigramPredictions();
mHandler.postUpdateSuggestions();
}
- // "ic" must not null
- private boolean revertDoubleSpace(final InputConnection ic) {
+ // "ic" must not be null
+ private boolean revertDoubleSpaceWhileInBatchEdit(final InputConnection ic) {
mHandler.cancelDoubleSpacesTimer();
// Here we test whether we indeed have a period and a space before us. This should not
// be needed, but it's there just in case something went wrong.
final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
- if (!". ".equals(textBeforeCursor))
+ if (!". ".equals(textBeforeCursor)) {
+ // Theoretically we should not be coming here if there isn't ". " before the
+ // cursor, but the application may be changing the text while we are typing, so
+ // anything goes. We should not crash.
+ Log.d(TAG, "Tried to revert double-space combo but we didn't find "
+ + "\". \" just before the cursor.");
return false;
- ic.beginBatchEdit();
+ }
ic.deleteSurroundingText(2, 0);
ic.commitText(" ", 1);
+ return true;
+ }
+
+ private static boolean revertSwapPunctuation(final InputConnection ic) {
+ // Here we test whether we indeed have a space and something else before us. This should not
+ // be needed, but it's there just in case something went wrong.
+ final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0);
+ // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to
+ // enter surrogate pairs this code will have been removed.
+ if (TextUtils.isEmpty(textBeforeCursor)
+ || (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))) {
+ // We may only come here if the application is changing the text while we are typing.
+ // This is quite a broken case, but not logically impossible, so we shouldn't crash,
+ // but some debugging log may be in order.
+ Log.d(TAG, "Tried to revert a swap of punctuation but we didn't "
+ + "find a space just before the cursor.");
+ return false;
+ }
+ ic.beginBatchEdit();
+ ic.deleteSurroundingText(2, 0);
+ ic.commitText(" " + textBeforeCursor.subSequence(0, 1), 1);
ic.endBatchEdit();
return true;
}
@@ -2129,12 +2216,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return mSettingsValues.isWordSeparator(code);
}
- private void sendMagicSpace() {
- sendKeyChar((char)Keyboard.CODE_SPACE);
- mJustAddedMagicSpace = true;
- mKeyboardSwitcher.updateShiftState();
- }
-
public boolean preferCapitalization() {
return mWordComposer.isFirstCharCapitalized();
}
@@ -2154,142 +2235,77 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettingsValues);
}
initSuggest();
+ updateCorrectionMode();
loadSettings();
+ // Since we just changed languages, we should re-evaluate suggestions with whatever word
+ // we are currently composing. If we are not composing anything, we may want to display
+ // predictions or punctuation signs (which is done by updateBigramPredictions anyway).
+ if (isCursorTouchingWord()) {
+ mHandler.postUpdateSuggestions();
+ } else {
+ mHandler.postUpdateBigramPredictions();
+ }
+ }
+
+ public void hapticAndAudioFeedback(final int primaryCode) {
+ mFeedbackManager.hapticAndAudioFeedback(primaryCode, mKeyboardSwitcher.getKeyboardView());
}
@Override
- public void onPress(int primaryCode, boolean withSliding) {
- final KeyboardSwitcher switcher = mKeyboardSwitcher;
- if (switcher.isVibrateAndSoundFeedbackRequired()) {
- vibrate();
- playKeyClick(primaryCode);
- }
- final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
- if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
- switcher.onPressShift(withSliding);
- } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
- switcher.onPressSymbol();
- } else {
- switcher.onOtherKeyPressed();
- }
+ public void onPressKey(int primaryCode) {
+ mKeyboardSwitcher.onPressKey(primaryCode);
}
@Override
- public void onRelease(int primaryCode, boolean withSliding) {
- KeyboardSwitcher switcher = mKeyboardSwitcher;
- // Reset any drag flags in the keyboard
- final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
- if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
- switcher.onReleaseShift(withSliding);
- } else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
- switcher.onReleaseSymbol();
+ public void onReleaseKey(int primaryCode, boolean withSliding) {
+ mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
+
+ // If accessibility is on, ensure the user receives keyboard state updates.
+ if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
+ switch (primaryCode) {
+ case Keyboard.CODE_SHIFT:
+ AccessibleKeyboardViewProxy.getInstance().notifyShiftState();
+ break;
+ case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
+ AccessibleKeyboardViewProxy.getInstance().notifySymbolsState();
+ break;
+ }
}
}
-
// receive ringer mode change and network state change.
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
- updateRingerMode();
- } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
mSubtypeSwitcher.onNetworkStateChanged(intent);
+ } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ mFeedbackManager.onRingerModeChanged();
}
}
};
- // update keypress sound volume
- private void updateSoundEffectVolume() {
- mFxVolume = Utils.getCurrentKeypressSoundVolume(mPrefs, mResources);
- }
-
- // update flags for silent mode
- private void updateRingerMode() {
- if (mAudioManager == null) {
- mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- if (mAudioManager == null) return;
- }
- mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
- }
-
- private void updateKeypressVibrationDuration() {
- mKeypressVibrationDuration = Utils.getCurrentVibrationDuration(mPrefs, mResources);
- }
-
- private void playKeyClick(int primaryCode) {
- // if mAudioManager is null, we don't have the ringer state yet
- // mAudioManager will be set by updateRingerMode
- if (mAudioManager == null) {
- if (mKeyboardSwitcher.getKeyboardView() != null) {
- updateRingerMode();
- }
- }
- if (isSoundOn()) {
- final int sound;
- switch (primaryCode) {
- case Keyboard.CODE_DELETE:
- sound = AudioManager.FX_KEYPRESS_DELETE;
- break;
- case Keyboard.CODE_ENTER:
- sound = AudioManager.FX_KEYPRESS_RETURN;
- break;
- case Keyboard.CODE_SPACE:
- sound = AudioManager.FX_KEYPRESS_SPACEBAR;
- break;
- default:
- sound = AudioManager.FX_KEYPRESS_STANDARD;
- break;
- }
- mAudioManager.playSoundEffect(sound, mFxVolume);
- }
- }
-
+ // TODO: remove this method when VoiceProxy has been removed
public void vibrate() {
- if (!mSettingsValues.mVibrateOn) {
- return;
- }
- if (mKeypressVibrationDuration < 0) {
- // Go ahead with the system default
- LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
- if (inputView != null) {
- inputView.performHapticFeedback(
- HapticFeedbackConstants.KEYBOARD_TAP,
- HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
- }
- } else if (mVibrator != null) {
- mVibrator.vibrate(mKeypressVibrationDuration);
- }
+ mFeedbackManager.vibrate(mKeyboardSwitcher.getKeyboardView());
}
- public WordComposer getCurrentWord() {
- return mWordComposer;
- }
-
- boolean isSoundOn() {
- return mSettingsValues.mSoundOn && !mSilentModeOn;
+ public boolean isAutoCapitalized() {
+ return mWordComposer.isAutoCapitalized();
}
private void updateCorrectionMode() {
// TODO: cleanup messy flags
final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
- && !mInputTypeNoAutoCorrect;
- mCorrectionMode = (shouldAutoCorrect && mSettingsValues.mAutoCorrectEnabled)
- ? Suggest.CORRECTION_FULL
- : (shouldAutoCorrect ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
- mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect
- && mSettingsValues.mAutoCorrectEnabled)
+ && !mInputAttributes.mInputTypeNoAutoCorrect;
+ mCorrectionMode = shouldAutoCorrect ? Suggest.CORRECTION_FULL : Suggest.CORRECTION_NONE;
+ mCorrectionMode = (mSettingsValues.mBigramSuggestionEnabled && shouldAutoCorrect)
? Suggest.CORRECTION_FULL_BIGRAM : mCorrectionMode;
- if (mSuggest != null) {
- mSuggest.setCorrectionMode(mCorrectionMode);
- }
}
- private void updateSuggestionVisibility(final SharedPreferences prefs, final Resources res) {
- final String suggestionVisiblityStr = prefs.getString(
- Settings.PREF_SHOW_SUGGESTIONS_SETTING,
- res.getString(R.string.prefs_suggestion_visibility_default_value));
+ private void updateSuggestionVisibility(final Resources res) {
+ final String suggestionVisiblityStr = mSettingsValues.mShowSuggestionsSetting;
for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
if (suggestionVisiblityStr.equals(res.getString(visibility))) {
mSuggestionVisibility = visibility;
@@ -2328,7 +2344,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
switch (position) {
case 0:
Intent intent = CompatUtils.getInputLanguageSelectionIntent(
- mInputMethodId, Intent.FLAG_ACTIVITY_NEW_TASK
+ SubtypeUtils.getInputMethodId(getPackageName()),
+ Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
@@ -2377,35 +2394,16 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final Printer p = new PrintWriterPrinter(fout);
p.println("LatinIME state :");
- p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode());
- p.println(" mComposingStringBuilder=" + mComposingStringBuilder.toString());
- p.println(" mIsSuggestionsRequested=" + mIsSettingsSuggestionStripOn);
+ final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
+ final int keyboardMode = keyboard != null ? keyboard.mId.mMode : -1;
+ p.println(" Keyboard mode = " + keyboardMode);
+ p.println(" mIsSuggestionsRequested=" + mInputAttributes.mIsSettingsSuggestionStripOn);
p.println(" mCorrectionMode=" + mCorrectionMode);
- p.println(" mHasUncommittedTypedChars=" + mHasUncommittedTypedChars);
+ p.println(" isComposingWord=" + mWordComposer.isComposingWord());
p.println(" mAutoCorrectEnabled=" + mSettingsValues.mAutoCorrectEnabled);
- p.println(" mInsertSpaceOnPickSuggestionManually=" + mInsertSpaceOnPickSuggestionManually);
- p.println(" mApplicationSpecifiedCompletionOn=" + mApplicationSpecifiedCompletionOn);
- p.println(" TextEntryState.state=" + TextEntryState.getState());
p.println(" mSoundOn=" + mSettingsValues.mSoundOn);
p.println(" mVibrateOn=" + mSettingsValues.mVibrateOn);
p.println(" mKeyPreviewPopupOn=" + mSettingsValues.mKeyPreviewPopupOn);
- }
-
- // Characters per second measurement
-
- private long mLastCpsTime;
- private static final int CPS_BUFFER_SIZE = 16;
- private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE];
- private int mCpsIndex;
-
- private void measureCps() {
- long now = System.currentTimeMillis();
- if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial
- mCpsIntervals[mCpsIndex] = now - mLastCpsTime;
- mLastCpsTime = now;
- mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE;
- long total = 0;
- for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
- System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
+ p.println(" mInputAttributes=" + mInputAttributes.toString());
}
}
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index ae8eb374b..5390ee39e 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -16,11 +16,11 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.latin.Dictionary.DataType;
-
import android.content.Context;
import android.content.SharedPreferences;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.keyboard.Keyboard;
import java.util.List;
@@ -28,12 +28,13 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang
public static boolean sDBG = false;
public static boolean sVISUALDEBUG = false;
+ public static boolean sUsabilityStudy = false;
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
}
- public static void init(Context context, SharedPreferences prefs) {
+ public static void init(LatinIME context, SharedPreferences prefs) {
}
public static void commit() {
@@ -43,8 +44,8 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang
}
public static void logOnManualSuggestion(
- String before, String after, int position, List<CharSequence> suggestions) {
- }
+ String before, String after, int position, SuggestedWords suggestedWords) {
+ }
public static void logOnAutoCorrection(String before, String after, int separatorCode) {
}
@@ -67,10 +68,13 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang
public static void logOnWarning(String warning) {
}
+ public static void onStartInputView(EditorInfo editorInfo) {
+ }
+
public static void onStartSuggestion(CharSequence previousWords) {
}
- public static void onAddSuggestedWord(String word, int typeId, DataType dataType) {
+ public static void onAddSuggestedWord(String word, int typeId, int dataType) {
}
public static void onSetKeyboard(Keyboard kb) {
@@ -78,4 +82,8 @@ public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChang
public static void onPrintAllUsabilityStudyLogs() {
}
+
+ public static boolean isResearcherPackage(Context context) {
+ return false;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 773efe709..72391f31e 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -38,20 +38,16 @@ import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.View;
-import android.view.inputmethod.EditorInfo;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import com.android.inputmethod.compat.CompatUtils;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
import com.android.inputmethod.compat.InputMethodServiceCompatWrapper;
-import com.android.inputmethod.compat.InputTypeCompatUtils;
import com.android.inputmethod.compat.VibratorCompatWrapper;
import com.android.inputmethod.deprecated.VoiceProxy;
import com.android.inputmethodcommon.InputMethodSettingsActivity;
-import java.util.Arrays;
import java.util.Locale;
public class Settings extends InputMethodSettingsActivity
@@ -61,261 +57,47 @@ public class Settings extends InputMethodSettingsActivity
public static final boolean ENABLE_EXPERIMENTAL_SETTINGS = false;
- public static final String PREF_GENERAL_SETTINGS_KEY = "general_settings";
+ // In the same order as xml/prefs.xml
+ public static final String PREF_GENERAL_SETTINGS = "general_settings";
+ public static final String PREF_SUBTYPES_SETTINGS = "subtype_settings";
+ public static final String PREF_AUTO_CAP = "auto_cap";
public static final String PREF_VIBRATE_ON = "vibrate_on";
public static final String PREF_SOUND_ON = "sound_on";
- public static final String PREF_KEY_PREVIEW_POPUP_ON = "popup_on";
- public static final String PREF_AUTO_CAP = "auto_cap";
- public static final String PREF_SHOW_SETTINGS_KEY = "show_settings_key";
- public static final String PREF_VOICE_SETTINGS_KEY = "voice_mode";
- public static final String PREF_INPUT_LANGUAGE = "input_language";
- public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
- public static final String PREF_SUBTYPES = "subtype_settings";
-
+ public static final String PREF_POPUP_ON = "popup_on";
+ public static final String PREF_VOICE_MODE = "voice_mode";
+ public static final String PREF_CORRECTION_SETTINGS = "correction_settings";
public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key";
- public static final String PREF_CORRECTION_SETTINGS_KEY = "correction_settings";
- public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
- public static final String PREF_DEBUG_SETTINGS = "debug_settings";
-
- public static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
- public static final String PREF_BIGRAM_PREDICTIONS = "bigram_prediction";
-
- public static final String PREF_MISC_SETTINGS_KEY = "misc_settings";
-
+ public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
+ public static final String PREF_MISC_SETTINGS = "misc_settings";
+ public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
+ public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
+ public static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
+ "pref_suppress_language_switch_key";
+ public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST =
+ "pref_include_other_imes_in_language_switch_list";
public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
"pref_key_preview_popup_dismiss_delay";
- public static final String PREF_KEY_USE_CONTACTS_DICT =
- "pref_key_use_contacts_dict";
- public static final String PREF_KEY_ENABLE_SPAN_INSERT =
- "enable_span_insert";
-
- public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
-
- public static final String PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS =
+ public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
+ public static final String PREF_BIGRAM_SUGGESTION = "bigram_suggestion";
+ public static final String PREF_BIGRAM_PREDICTIONS = "bigram_prediction";
+ public static final String PREF_KEY_ENABLE_SPAN_INSERT = "enable_span_insert";
+ public static final String PREF_VIBRATION_DURATION_SETTINGS =
"pref_vibration_duration_settings";
-
public static final String PREF_KEYPRESS_SOUND_VOLUME =
"pref_keypress_sound_volume";
- // Dialog ids
- private static final int VOICE_INPUT_CONFIRM_DIALOG = 0;
-
- public static class Values {
- // From resources:
- public final int mDelayUpdateOldSuggestions;
- public final String mWordSeparators;
- public final String mMagicSpaceStrippers;
- public final String mMagicSpaceSwappers;
- public final String mSuggestPuncs;
- public final SuggestedWords mSuggestPuncList;
- private final String mSymbolsExcludedFromWordSeparators;
-
- // From preferences:
- public final boolean mSoundOn; // Sound setting private to Latin IME (see mSilentModeOn)
- public final boolean mVibrateOn;
- public final boolean mKeyPreviewPopupOn;
- public final int mKeyPreviewPopupDismissDelay;
- public final boolean mAutoCap;
- public final boolean mAutoCorrectEnabled;
- public final double mAutoCorrectionThreshold;
- // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
- public final boolean mBigramSuggestionEnabled;
- // Prediction: use bigrams to predict the next word when there is no input for it yet
- public final boolean mBigramPredictionEnabled;
- public final boolean mUseContactsDict;
- public final boolean mEnableSuggestionSpanInsertion;
-
- private final boolean mShowSettingsKey;
- private final boolean mVoiceKeyEnabled;
- private final boolean mVoiceKeyOnMain;
-
- public Values(final SharedPreferences prefs, final Context context,
- final String localeStr) {
- final Resources res = context.getResources();
- final Locale savedLocale;
- if (null != localeStr) {
- final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
- savedLocale = LocaleUtils.setSystemLocale(res, keyboardLocale);
- } else {
- savedLocale = null;
- }
-
- // Get the resources
- mDelayUpdateOldSuggestions = res.getInteger(
- R.integer.config_delay_update_old_suggestions);
- mMagicSpaceStrippers = res.getString(R.string.magic_space_stripping_symbols);
- mMagicSpaceSwappers = res.getString(R.string.magic_space_swapping_symbols);
- String wordSeparators = mMagicSpaceStrippers + mMagicSpaceSwappers
- + res.getString(R.string.magic_space_promoting_symbols);
- final String symbolsExcludedFromWordSeparators =
- res.getString(R.string.symbols_excluded_from_word_separators);
- for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) {
- wordSeparators = wordSeparators.replace(
- symbolsExcludedFromWordSeparators.substring(i, i + 1), "");
- }
- mSymbolsExcludedFromWordSeparators = symbolsExcludedFromWordSeparators;
- mWordSeparators = wordSeparators;
- mSuggestPuncs = res.getString(R.string.suggested_punctuations);
- // TODO: it would be nice not to recreate this each time we change the configuration
- mSuggestPuncList = createSuggestPuncList(mSuggestPuncs);
-
- // Get the settings preferences
- final boolean hasVibrator = VibratorCompatWrapper.getInstance(context).hasVibrator();
- mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON,
- res.getBoolean(R.bool.config_default_vibration_enabled));
- mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
- res.getBoolean(R.bool.config_default_sound_enabled));
- mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res);
- mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res);
- mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
- mAutoCorrectEnabled = isAutoCorrectEnabled(prefs, res);
- mBigramSuggestionEnabled = mAutoCorrectEnabled
- && isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled);
- mBigramPredictionEnabled = mBigramSuggestionEnabled
- && isBigramPredictionEnabled(prefs, res);
- mAutoCorrectionThreshold = getAutoCorrectionThreshold(prefs, res);
- mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
- mEnableSuggestionSpanInsertion =
- prefs.getBoolean(Settings.PREF_KEY_ENABLE_SPAN_INSERT, true);
- final boolean defaultShowSettingsKey = res.getBoolean(
- R.bool.config_default_show_settings_key);
- mShowSettingsKey = isShowSettingsKeyOption(res)
- ? prefs.getBoolean(Settings.PREF_SHOW_SETTINGS_KEY, defaultShowSettingsKey)
- : defaultShowSettingsKey;
- final String voiceModeMain = res.getString(R.string.voice_mode_main);
- final String voiceModeOff = res.getString(R.string.voice_mode_off);
- final String voiceMode = prefs.getString(PREF_VOICE_SETTINGS_KEY, voiceModeMain);
- mVoiceKeyEnabled = voiceMode != null && !voiceMode.equals(voiceModeOff);
- mVoiceKeyOnMain = voiceMode != null && voiceMode.equals(voiceModeMain);
-
- LocaleUtils.setSystemLocale(res, savedLocale);
- }
-
- public boolean isSuggestedPunctuation(int code) {
- return mSuggestPuncs.contains(String.valueOf((char)code));
- }
-
- public boolean isWordSeparator(int code) {
- return mWordSeparators.contains(String.valueOf((char)code));
- }
-
- public boolean isSymbolExcludedFromWordSeparators(int code) {
- return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code));
- }
-
- public boolean isMagicSpaceStripper(int code) {
- return mMagicSpaceStrippers.contains(String.valueOf((char)code));
- }
-
- public boolean isMagicSpaceSwapper(int code) {
- return mMagicSpaceSwappers.contains(String.valueOf((char)code));
- }
- private static boolean isAutoCorrectEnabled(SharedPreferences sp, Resources resources) {
- final String currentAutoCorrectionSetting = sp.getString(
- Settings.PREF_AUTO_CORRECTION_THRESHOLD,
- resources.getString(R.string.auto_correction_threshold_mode_index_modest));
- final String autoCorrectionOff = resources.getString(
- R.string.auto_correction_threshold_mode_index_off);
- return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
- }
-
- // Public to access from KeyboardSwitcher. Should it have access to some
- // process-global instance instead?
- public static boolean isKeyPreviewPopupEnabled(SharedPreferences sp, Resources resources) {
- final boolean showPopupOption = resources.getBoolean(
- R.bool.config_enable_show_popup_on_keypress_option);
- if (!showPopupOption) return resources.getBoolean(R.bool.config_default_popup_preview);
- return sp.getBoolean(Settings.PREF_KEY_PREVIEW_POPUP_ON,
- resources.getBoolean(R.bool.config_default_popup_preview));
- }
-
- // Likewise
- public static int getKeyPreviewPopupDismissDelay(SharedPreferences sp,
- Resources resources) {
- return Integer.parseInt(sp.getString(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
- Integer.toString(resources.getInteger(R.integer.config_delay_after_preview))));
- }
-
- private static boolean isBigramSuggestionEnabled(SharedPreferences sp, Resources resources,
- boolean autoCorrectEnabled) {
- final boolean showBigramSuggestionsOption = resources.getBoolean(
- R.bool.config_enable_bigram_suggestions_option);
- if (!showBigramSuggestionsOption) {
- return autoCorrectEnabled;
- }
- return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTIONS, resources.getBoolean(
- R.bool.config_default_bigram_suggestions));
- }
-
- private static boolean isBigramPredictionEnabled(SharedPreferences sp,
- Resources resources) {
- return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, resources.getBoolean(
- R.bool.config_default_bigram_prediction));
- }
-
- private static double getAutoCorrectionThreshold(SharedPreferences sp,
- Resources resources) {
- final String currentAutoCorrectionSetting = sp.getString(
- Settings.PREF_AUTO_CORRECTION_THRESHOLD,
- resources.getString(R.string.auto_correction_threshold_mode_index_modest));
- final String[] autoCorrectionThresholdValues = resources.getStringArray(
- R.array.auto_correction_threshold_values);
- // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
- double autoCorrectionThreshold = Double.MAX_VALUE;
- try {
- final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
- if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
- autoCorrectionThreshold = Double.parseDouble(
- autoCorrectionThresholdValues[arrayIndex]);
- }
- } catch (NumberFormatException e) {
- // Whenever the threshold settings are correct, never come here.
- autoCorrectionThreshold = Double.MAX_VALUE;
- Log.w(TAG, "Cannot load auto correction threshold setting."
- + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
- + ", autoCorrectionThresholdValues: "
- + Arrays.toString(autoCorrectionThresholdValues));
- }
- return autoCorrectionThreshold;
- }
-
- private static SuggestedWords createSuggestPuncList(final String puncs) {
- SuggestedWords.Builder builder = new SuggestedWords.Builder();
- if (puncs != null) {
- for (int i = 0; i < puncs.length(); i++) {
- builder.addWord(puncs.subSequence(i, i + 1));
- }
- }
- return builder.setIsPunctuationSuggestions().build();
- }
-
- public static boolean isShowSettingsKeyOption(final Resources resources) {
- return resources.getBoolean(R.bool.config_enable_show_settings_key_option);
-
- }
-
- public boolean isSettingsKeyEnabled() {
- return mShowSettingsKey;
- }
-
- public boolean isVoiceKeyEnabled(EditorInfo attribute) {
- final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
- final int inputType = (attribute != null) ? attribute.inputType : 0;
- return shortcutImeEnabled && mVoiceKeyEnabled
- && !InputTypeCompatUtils.isPasswordInputType(inputType);
- }
+ public static final String PREF_INPUT_LANGUAGE = "input_language";
+ public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
+ public static final String PREF_DEBUG_SETTINGS = "debug_settings";
- public boolean isVoiceKeyOnMain() {
- return mVoiceKeyOnMain;
- }
- }
+ // Dialog ids
+ private static final int VOICE_INPUT_CONFIRM_DIALOG = 0;
private PreferenceScreen mInputLanguageSelection;
private PreferenceScreen mKeypressVibrationDurationSettingsPref;
private PreferenceScreen mKeypressSoundVolumeSettingsPref;
private ListPreference mVoicePreference;
- private CheckBoxPreference mShowSettingsKeyPreference;
private ListPreference mShowCorrectionSuggestionsPreference;
private ListPreference mAutoCorrectionThresholdPreference;
private ListPreference mKeyPreviewPopupDismissDelay;
@@ -363,22 +145,21 @@ public class Settings extends InputMethodSettingsActivity
final Context context = getActivityInternal();
addPreferencesFromResource(R.xml.prefs);
- mInputLanguageSelection = (PreferenceScreen) findPreference(PREF_SUBTYPES);
+ mInputLanguageSelection = (PreferenceScreen) findPreference(PREF_SUBTYPES_SETTINGS);
mInputLanguageSelection.setOnPreferenceClickListener(this);
- mVoicePreference = (ListPreference) findPreference(PREF_VOICE_SETTINGS_KEY);
- mShowSettingsKeyPreference = (CheckBoxPreference) findPreference(PREF_SHOW_SETTINGS_KEY);
+ mVoicePreference = (ListPreference) findPreference(PREF_VOICE_MODE);
mShowCorrectionSuggestionsPreference =
(ListPreference) findPreference(PREF_SHOW_SUGGESTIONS_SETTING);
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
mVoiceModeOff = getString(R.string.voice_mode_off);
- mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
+ mVoiceOn = !(prefs.getString(PREF_VOICE_MODE, mVoiceModeOff)
.equals(mVoiceModeOff));
mAutoCorrectionThresholdPreference =
(ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
- mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
+ mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTION);
mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS);
mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
if (mDebugSettingsPreference != null) {
@@ -391,15 +172,11 @@ public class Settings extends InputMethodSettingsActivity
ensureConsistencyOfAutoCorrectionSettings();
final PreferenceGroup generalSettings =
- (PreferenceGroup) findPreference(PREF_GENERAL_SETTINGS_KEY);
+ (PreferenceGroup) findPreference(PREF_GENERAL_SETTINGS);
final PreferenceGroup textCorrectionGroup =
- (PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS_KEY);
+ (PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS);
final PreferenceGroup miscSettings =
- (PreferenceGroup) findPreference(PREF_MISC_SETTINGS_KEY);
-
- if (!Values.isShowSettingsKeyOption(res)) {
- generalSettings.removePreference(mShowSettingsKeyPreference);
- }
+ (PreferenceGroup) findPreference(PREF_MISC_SETTINGS);
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
@@ -412,13 +189,13 @@ public class Settings extends InputMethodSettingsActivity
}
if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
- generalSettings.removePreference(findPreference(PREF_SUBTYPES));
+ generalSettings.removePreference(findPreference(PREF_SUBTYPES_SETTINGS));
}
final boolean showPopupOption = res.getBoolean(
R.bool.config_enable_show_popup_on_keypress_option);
if (!showPopupOption) {
- generalSettings.removePreference(findPreference(PREF_KEY_PREVIEW_POPUP_ON));
+ generalSettings.removePreference(findPreference(PREF_POPUP_ON));
}
final boolean showBigramSuggestionsOption = res.getBoolean(
@@ -430,6 +207,11 @@ public class Settings extends InputMethodSettingsActivity
}
}
+ final CheckBoxPreference includeOtherImesInLanguageSwitchList =
+ (CheckBoxPreference)findPreference(PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST);
+ includeOtherImesInLanguageSwitchList.setEnabled(
+ !SettingsValues.isLanguageSwitchKeySupressed(prefs));
+
mKeyPreviewPopupDismissDelay =
(ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
final String[] entries = new String[] {
@@ -437,14 +219,15 @@ public class Settings extends InputMethodSettingsActivity
res.getString(R.string.key_preview_popup_dismiss_default_delay),
};
final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
- R.integer.config_delay_after_preview));
+ R.integer.config_key_preview_linger_timeout));
mKeyPreviewPopupDismissDelay.setEntries(entries);
mKeyPreviewPopupDismissDelay.setEntryValues(
new String[] { "0", popupDismissDelayDefaultValue });
if (null == mKeyPreviewPopupDismissDelay.getValue()) {
mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
}
- mKeyPreviewPopupDismissDelay.setEnabled(Values.isKeyPreviewPopupEnabled(prefs, res));
+ mKeyPreviewPopupDismissDelay.setEnabled(
+ SettingsValues.isKeyPreviewPopupEnabled(prefs, res));
final PreferenceScreen dictionaryLink =
(PreferenceScreen) findPreference(PREF_CONFIGURE_DICTIONARIES_KEY);
@@ -455,17 +238,26 @@ public class Settings extends InputMethodSettingsActivity
textCorrectionGroup.removePreference(dictionaryLink);
}
- final boolean showUsabilityModeStudyOption = res.getBoolean(
- R.bool.config_enable_usability_study_mode_option);
- if (!showUsabilityModeStudyOption || !ENABLE_EXPERIMENTAL_SETTINGS) {
- final Preference pref = findPreference(PREF_USABILITY_STUDY_MODE);
- if (pref != null) {
- miscSettings.removePreference(pref);
+ final boolean isResearcherPackage = LatinImeLogger.isResearcherPackage(this);
+ final boolean showUsabilityStudyModeOption =
+ res.getBoolean(R.bool.config_enable_usability_study_mode_option)
+ || isResearcherPackage || ENABLE_EXPERIMENTAL_SETTINGS;
+ final Preference usabilityStudyPref = findPreference(PREF_USABILITY_STUDY_MODE);
+ if (!showUsabilityStudyModeOption) {
+ if (usabilityStudyPref != null) {
+ miscSettings.removePreference(usabilityStudyPref);
+ }
+ }
+ if (isResearcherPackage) {
+ if (usabilityStudyPref instanceof CheckBoxPreference) {
+ CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref;
+ checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE, true));
+ checkbox.setSummary(R.string.settings_warning_researcher_mode);
}
}
mKeypressVibrationDurationSettingsPref =
- (PreferenceScreen) findPreference(PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS);
+ (PreferenceScreen) findPreference(PREF_VIBRATION_DURATION_SETTINGS);
if (mKeypressVibrationDurationSettingsPref != null) {
mKeypressVibrationDurationSettingsPref.setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@@ -521,20 +313,26 @@ public class Settings extends InputMethodSettingsActivity
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
(new BackupManager(getActivityInternal())).dataChanged();
// If turning on voice input, show dialog
- if (key.equals(PREF_VOICE_SETTINGS_KEY) && !mVoiceOn) {
- if (!prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
+ if (key.equals(PREF_VOICE_MODE) && !mVoiceOn) {
+ if (!prefs.getString(PREF_VOICE_MODE, mVoiceModeOff)
.equals(mVoiceModeOff)) {
showVoiceConfirmation();
}
- } else if (key.equals(PREF_KEY_PREVIEW_POPUP_ON)) {
+ } else if (key.equals(PREF_POPUP_ON)) {
final ListPreference popupDismissDelay =
(ListPreference)findPreference(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
if (null != popupDismissDelay) {
- popupDismissDelay.setEnabled(prefs.getBoolean(PREF_KEY_PREVIEW_POPUP_ON, true));
+ popupDismissDelay.setEnabled(prefs.getBoolean(PREF_POPUP_ON, true));
}
+ } else if (key.equals(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) {
+ final CheckBoxPreference includeOtherImesInLanguageSwicthList =
+ (CheckBoxPreference)findPreference(
+ PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST);
+ includeOtherImesInLanguageSwicthList.setEnabled(
+ !SettingsValues.isLanguageSwitchKeySupressed(prefs));
}
ensureConsistencyOfAutoCorrectionSettings();
- mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
+ mVoiceOn = !(prefs.getString(PREF_VOICE_MODE, mVoiceModeOff)
.equals(mVoiceModeOff));
updateVoiceModeSummary();
updateShowCorrectionSuggestionsSummary();
@@ -545,10 +343,9 @@ public class Settings extends InputMethodSettingsActivity
@Override
public boolean onPreferenceClick(Preference pref) {
if (pref == mInputLanguageSelection) {
- startActivity(CompatUtils.getInputLanguageSelectionIntent(
- Utils.getInputMethodId(
- InputMethodManagerCompatWrapper.getInstance(),
- getActivityInternal().getApplicationInfo().packageName), 0));
+ final String imeId = SubtypeUtils.getInputMethodId(
+ getActivityInternal().getApplicationInfo().packageName);
+ startActivity(CompatUtils.getInputLanguageSelectionIntent(imeId, 0));
return true;
}
return false;
@@ -660,7 +457,7 @@ public class Settings extends InputMethodSettingsActivity
SharedPreferences sp, Resources res) {
if (mKeypressVibrationDurationSettingsPref != null) {
mKeypressVibrationDurationSettingsPref.setSummary(
- Utils.getCurrentVibrationDuration(sp, res)
+ SettingsValues.getCurrentVibrationDuration(sp, res)
+ res.getString(R.string.settings_ms));
}
}
@@ -676,7 +473,7 @@ public class Settings extends InputMethodSettingsActivity
public void onClick(DialogInterface dialog, int whichButton) {
final int ms = Integer.valueOf(
mKeypressVibrationDurationSettingsTextView.getText().toString());
- sp.edit().putInt(Settings.PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS, ms).apply();
+ sp.edit().putInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, ms).apply();
updateKeypressVibrationDurationSettingsSummary(sp, res);
}
});
@@ -688,7 +485,7 @@ public class Settings extends InputMethodSettingsActivity
});
final View v = context.getLayoutInflater().inflate(
R.layout.vibration_settings_dialog, null);
- final int currentMs = Utils.getCurrentVibrationDuration(
+ final int currentMs = SettingsValues.getCurrentVibrationDuration(
getPreferenceManager().getSharedPreferences(), getResources());
mKeypressVibrationDurationSettingsTextView = (TextView)v.findViewById(R.id.vibration_value);
final SeekBar sb = (SeekBar)v.findViewById(R.id.vibration_settings);
@@ -717,8 +514,8 @@ public class Settings extends InputMethodSettingsActivity
private void updateKeypressSoundVolumeSummary(SharedPreferences sp, Resources res) {
if (mKeypressSoundVolumeSettingsPref != null) {
- mKeypressSoundVolumeSettingsPref.setSummary(
- String.valueOf((int)(Utils.getCurrentKeypressSoundVolume(sp, res) * 100)));
+ mKeypressSoundVolumeSettingsPref.setSummary(String.valueOf(
+ (int)(SettingsValues.getCurrentKeypressSoundVolume(sp, res) * 100)));
}
}
@@ -747,8 +544,8 @@ public class Settings extends InputMethodSettingsActivity
});
final View v = context.getLayoutInflater().inflate(
R.layout.sound_effect_volume_dialog, null);
- final int currentVolumeInt = (int)(Utils.getCurrentKeypressSoundVolume(
- getPreferenceManager().getSharedPreferences(), getResources()) * 100);
+ final int currentVolumeInt =
+ (int)(SettingsValues.getCurrentKeypressSoundVolume(sp, res) * 100);
mKeypressSoundVolumeSettingsTextView =
(TextView)v.findViewById(R.id.sound_effect_volume_value);
final SeekBar sb = (SeekBar)v.findViewById(R.id.sound_effect_volume_bar);
@@ -774,4 +571,4 @@ public class Settings extends InputMethodSettingsActivity
builder.setView(v);
builder.create().show();
}
-} \ No newline at end of file
+}
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
new file mode 100644
index 000000000..4346a3671
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2011 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;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.os.Build;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputTypeCompatUtils;
+import com.android.inputmethod.compat.VibratorCompatWrapper;
+import com.android.inputmethod.keyboard.internal.KeySpecParser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+public class SettingsValues {
+ private static final String TAG = SettingsValues.class.getSimpleName();
+
+ // From resources:
+ public final int mDelayUpdateOldSuggestions;
+ public final String mWeakSpaceStrippers;
+ public final String mWeakSpaceSwappers;
+ private final String mPhantomSpacePromotingSymbols;
+ public final SuggestedWords mSuggestPuncList;
+ private final String mSymbolsExcludedFromWordSeparators;
+ public final String mWordSeparators;
+ public final CharSequence mHintToSaveText;
+
+ // From preferences, in the same order as xml/prefs.xml:
+ public final boolean mAutoCap;
+ public final boolean mVibrateOn;
+ public final boolean mSoundOn;
+ public final boolean mKeyPreviewPopupOn;
+ private final String mVoiceMode;
+ private final String mAutoCorrectionThresholdRawValue;
+ public final String mShowSuggestionsSetting;
+ @SuppressWarnings("unused") // TODO: Use this
+ private final boolean mUsabilityStudyMode;
+ public final boolean mIncludesOtherImesInLanguageSwitchList;
+ public final boolean mIsLanguageSwitchKeySuppressed;
+ @SuppressWarnings("unused") // TODO: Use this
+ private final String mKeyPreviewPopupDismissDelayRawValue;
+ public final boolean mUseContactsDict;
+ // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
+ public final boolean mBigramSuggestionEnabled;
+ // Prediction: use bigrams to predict the next word when there is no input for it yet
+ public final boolean mBigramPredictionEnabled;
+ public final boolean mEnableSuggestionSpanInsertion;
+ @SuppressWarnings("unused") // TODO: Use this
+ private final int mVibrationDurationSettingsRawValue;
+ @SuppressWarnings("unused") // TODO: Use this
+ private final float mKeypressSoundVolumeRawValue;
+
+ // Deduced settings
+ public final int mKeypressVibrationDuration;
+ public final float mFxVolume;
+ public final int mKeyPreviewPopupDismissDelay;
+ public final boolean mAutoCorrectEnabled;
+ public final double mAutoCorrectionThreshold;
+ private final boolean mVoiceKeyEnabled;
+ private final boolean mVoiceKeyOnMain;
+
+ public SettingsValues(final SharedPreferences prefs, final Context context,
+ final String localeStr) {
+ final Resources res = context.getResources();
+ final Locale savedLocale;
+ if (null != localeStr) {
+ final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
+ savedLocale = LocaleUtils.setSystemLocale(res, keyboardLocale);
+ } else {
+ savedLocale = null;
+ }
+
+ // Get the resources
+ mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions);
+ mWeakSpaceStrippers = res.getString(R.string.weak_space_stripping_symbols);
+ mWeakSpaceSwappers = res.getString(R.string.weak_space_swapping_symbols);
+ mPhantomSpacePromotingSymbols = res.getString(R.string.phantom_space_promoting_symbols);
+ if (LatinImeLogger.sDBG) {
+ final int length = mWeakSpaceStrippers.length();
+ for (int i = 0; i < length; i = mWeakSpaceStrippers.offsetByCodePoints(i, 1)) {
+ if (isWeakSpaceSwapper(mWeakSpaceStrippers.codePointAt(i))) {
+ throw new RuntimeException("Char code " + mWeakSpaceStrippers.codePointAt(i)
+ + " is both a weak space swapper and stripper.");
+ }
+ }
+ }
+ final String[] suggestPuncsSpec = KeySpecParser.parseCsvString(
+ res.getString(R.string.suggested_punctuations), res, R.string.english_ime_name);
+ mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
+ mSymbolsExcludedFromWordSeparators =
+ res.getString(R.string.symbols_excluded_from_word_separators);
+ mWordSeparators = createWordSeparators(mWeakSpaceStrippers, mWeakSpaceSwappers,
+ mSymbolsExcludedFromWordSeparators, res);
+ mHintToSaveText = context.getText(R.string.hint_add_to_dictionary);
+
+ // Get the settings preferences
+ mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
+ mVibrateOn = isVibrateOn(context, prefs, res);
+ mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
+ res.getBoolean(R.bool.config_default_sound_enabled));
+ mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res);
+ final String voiceModeMain = res.getString(R.string.voice_mode_main);
+ final String voiceModeOff = res.getString(R.string.voice_mode_off);
+ mVoiceMode = prefs.getString(Settings.PREF_VOICE_MODE, voiceModeMain);
+ mAutoCorrectionThresholdRawValue = prefs.getString(Settings.PREF_AUTO_CORRECTION_THRESHOLD,
+ res.getString(R.string.auto_correction_threshold_mode_index_modest));
+ mShowSuggestionsSetting = prefs.getString(Settings.PREF_SHOW_SUGGESTIONS_SETTING,
+ res.getString(R.string.prefs_suggestion_visibility_default_value));
+ mUsabilityStudyMode = getUsabilityStudyMode(prefs);
+ mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean(
+ Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
+ mIsLanguageSwitchKeySuppressed = isLanguageSwitchKeySupressed(prefs);
+ mKeyPreviewPopupDismissDelayRawValue = prefs.getString(
+ Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
+ Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout)));
+ mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
+ mAutoCorrectEnabled = isAutoCorrectEnabled(res, mAutoCorrectionThresholdRawValue);
+ mBigramSuggestionEnabled = mAutoCorrectEnabled
+ && isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled);
+ mBigramPredictionEnabled = mBigramSuggestionEnabled
+ && isBigramPredictionEnabled(prefs, res);
+ mEnableSuggestionSpanInsertion =
+ prefs.getBoolean(Settings.PREF_KEY_ENABLE_SPAN_INSERT, true);
+ mVibrationDurationSettingsRawValue =
+ prefs.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1);
+ mKeypressSoundVolumeRawValue = prefs.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
+
+ // Compute other readable settings
+ mKeypressVibrationDuration = getCurrentVibrationDuration(prefs, res);
+ mFxVolume = getCurrentKeypressSoundVolume(prefs, res);
+ mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res);
+ mAutoCorrectionThreshold = getAutoCorrectionThreshold(res,
+ mAutoCorrectionThresholdRawValue);
+ mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff);
+ mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
+
+ LocaleUtils.setSystemLocale(res, savedLocale);
+ }
+
+ // Helper functions to create member values.
+ private static SuggestedWords createSuggestPuncList(final String[] puncs) {
+ final ArrayList<SuggestedWords.SuggestedWordInfo> puncList =
+ new ArrayList<SuggestedWords.SuggestedWordInfo>();
+ if (puncs != null) {
+ for (final String puncSpec : puncs) {
+ puncList.add(new SuggestedWords.SuggestedWordInfo(
+ KeySpecParser.getLabel(puncSpec)));
+ }
+ }
+ return new SuggestedWords(puncList,
+ false /* typedWordValid */,
+ false /* hasAutoCorrectionCandidate */,
+ false /* allowsToBeAutoCorrected */,
+ true /* isPunctuationSuggestions */,
+ false /* isObsoleteSuggestions */);
+ }
+
+ private static String createWordSeparators(final String weakSpaceStrippers,
+ final String weakSpaceSwappers, final String symbolsExcludedFromWordSeparators,
+ final Resources res) {
+ String wordSeparators = weakSpaceStrippers + weakSpaceSwappers
+ + res.getString(R.string.phantom_space_promoting_symbols);
+ for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) {
+ wordSeparators = wordSeparators.replace(
+ symbolsExcludedFromWordSeparators.substring(i, i + 1), "");
+ }
+ return wordSeparators;
+ }
+
+ private static boolean isVibrateOn(final Context context, final SharedPreferences prefs,
+ final Resources res) {
+ final boolean hasVibrator = VibratorCompatWrapper.getInstance(context).hasVibrator();
+ return hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON,
+ res.getBoolean(R.bool.config_default_vibration_enabled));
+ }
+
+ public boolean isWordSeparator(int code) {
+ return mWordSeparators.contains(String.valueOf((char)code));
+ }
+
+ public boolean isSymbolExcludedFromWordSeparators(int code) {
+ return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code));
+ }
+
+ public boolean isWeakSpaceStripper(int code) {
+ // TODO: this does not work if the code does not fit in a char
+ return mWeakSpaceStrippers.contains(String.valueOf((char)code));
+ }
+
+ public boolean isWeakSpaceSwapper(int code) {
+ // TODO: this does not work if the code does not fit in a char
+ return mWeakSpaceSwappers.contains(String.valueOf((char)code));
+ }
+
+ public boolean isPhantomSpacePromotingSymbol(int code) {
+ // TODO: this does not work if the code does not fit in a char
+ return mPhantomSpacePromotingSymbols.contains(String.valueOf((char)code));
+ }
+
+ private static boolean isAutoCorrectEnabled(final Resources resources,
+ final String currentAutoCorrectionSetting) {
+ final String autoCorrectionOff = resources.getString(
+ R.string.auto_correction_threshold_mode_index_off);
+ return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
+ }
+
+ // Public to access from KeyboardSwitcher. Should it have access to some
+ // process-global instance instead?
+ public static boolean isKeyPreviewPopupEnabled(SharedPreferences sp, Resources resources) {
+ final boolean showPopupOption = resources.getBoolean(
+ R.bool.config_enable_show_popup_on_keypress_option);
+ if (!showPopupOption) return resources.getBoolean(R.bool.config_default_popup_preview);
+ return sp.getBoolean(Settings.PREF_POPUP_ON,
+ resources.getBoolean(R.bool.config_default_popup_preview));
+ }
+
+ // Likewise
+ public static int getKeyPreviewPopupDismissDelay(SharedPreferences sp,
+ Resources resources) {
+ // TODO: use mKeyPreviewPopupDismissDelayRawValue instead of reading it again here.
+ return Integer.parseInt(sp.getString(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
+ Integer.toString(resources.getInteger(
+ R.integer.config_key_preview_linger_timeout))));
+ }
+
+ private static boolean isBigramSuggestionEnabled(final SharedPreferences sp,
+ final Resources resources, final boolean autoCorrectEnabled) {
+ final boolean showBigramSuggestionsOption = resources.getBoolean(
+ R.bool.config_enable_bigram_suggestions_option);
+ if (!showBigramSuggestionsOption) {
+ return autoCorrectEnabled;
+ }
+ return sp.getBoolean(Settings.PREF_BIGRAM_SUGGESTION, resources.getBoolean(
+ R.bool.config_default_bigram_suggestions));
+ }
+
+ private static boolean isBigramPredictionEnabled(final SharedPreferences sp,
+ final Resources resources) {
+ return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, resources.getBoolean(
+ R.bool.config_default_bigram_prediction));
+ }
+
+ private static double getAutoCorrectionThreshold(final Resources resources,
+ final String currentAutoCorrectionSetting) {
+ final String[] autoCorrectionThresholdValues = resources.getStringArray(
+ R.array.auto_correction_threshold_values);
+ // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off.
+ double autoCorrectionThreshold = Double.MAX_VALUE;
+ try {
+ final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting);
+ if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) {
+ autoCorrectionThreshold = Double.parseDouble(
+ autoCorrectionThresholdValues[arrayIndex]);
+ }
+ } catch (NumberFormatException e) {
+ // Whenever the threshold settings are correct, never come here.
+ autoCorrectionThreshold = Double.MAX_VALUE;
+ Log.w(TAG, "Cannot load auto correction threshold setting."
+ + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting
+ + ", autoCorrectionThresholdValues: "
+ + Arrays.toString(autoCorrectionThresholdValues));
+ }
+ return autoCorrectionThreshold;
+ }
+
+ public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) {
+ final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
+ final int inputType = (editorInfo != null) ? editorInfo.inputType : 0;
+ return shortcutImeEnabled && mVoiceKeyEnabled
+ && !InputTypeCompatUtils.isPasswordInputType(inputType);
+ }
+
+ public boolean isVoiceKeyOnMain() {
+ return mVoiceKeyOnMain;
+ }
+
+ public static boolean isLanguageSwitchKeySupressed(SharedPreferences sp) {
+ return sp.getBoolean(Settings.PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false);
+ }
+
+ public boolean isLanguageSwitchKeyEnabled(Context context) {
+ if (mIsLanguageSwitchKeySuppressed) {
+ return false;
+ }
+ if (mIncludesOtherImesInLanguageSwitchList) {
+ return SubtypeUtils.hasMultipleEnabledIMEsOrSubtypes(/* include aux subtypes */false);
+ } else {
+ return SubtypeUtils.hasMultipleEnabledSubtypesInThisIme(
+ context, /* include aux subtypes */false);
+ }
+ }
+
+ public boolean isFullscreenModeAllowed(Resources res) {
+ return res.getBoolean(R.bool.config_use_fullscreen_mode);
+ }
+
+ // Accessed from the settings interface, hence public
+ public static float getCurrentKeypressSoundVolume(final SharedPreferences sp,
+ final Resources res) {
+ // TODO: use mVibrationDurationSettingsRawValue instead of reading it again here
+ final float volume = sp.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
+ if (volume >= 0) {
+ return volume;
+ }
+
+ final String[] volumePerHardwareList = res.getStringArray(R.array.keypress_volumes);
+ final String hardwarePrefix = Build.HARDWARE + ",";
+ for (final String element : volumePerHardwareList) {
+ if (element.startsWith(hardwarePrefix)) {
+ return Float.parseFloat(element.substring(element.lastIndexOf(',') + 1));
+ }
+ }
+ return -1.0f;
+ }
+
+ // Likewise
+ public static int getCurrentVibrationDuration(final SharedPreferences sp,
+ final Resources res) {
+ // TODO: use mKeypressVibrationDuration instead of reading it again here
+ final int ms = sp.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1);
+ if (ms >= 0) {
+ return ms;
+ }
+ final String[] durationPerHardwareList = res.getStringArray(
+ R.array.keypress_vibration_durations);
+ final String hardwarePrefix = Build.HARDWARE + ",";
+ for (final String element : durationPerHardwareList) {
+ if (element.startsWith(hardwarePrefix)) {
+ return (int)Long.parseLong(element.substring(element.lastIndexOf(',') + 1));
+ }
+ }
+ return -1;
+ }
+
+ // Likewise
+ public static boolean getUsabilityStudyMode(final SharedPreferences prefs) {
+ // TODO: use mUsabilityStudyMode instead of reading it again here
+ return prefs.getBoolean(Settings.PREF_USABILITY_STUDY_MODE, true);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/StringBuilderPool.java b/java/src/com/android/inputmethod/latin/StringBuilderPool.java
deleted file mode 100644
index a663ed43e..000000000
--- a/java/src/com/android/inputmethod/latin/StringBuilderPool.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2011 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 java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A pool of string builders to be used from anywhere.
- */
-public class StringBuilderPool {
- // Singleton
- private static final StringBuilderPool sInstance = new StringBuilderPool();
- private static final boolean DEBUG = false;
- private StringBuilderPool() {}
- // TODO: Make this a normal array with a size of 20, or a ConcurrentQueue
- private final List<StringBuilder> mPool =
- Collections.synchronizedList(new ArrayList<StringBuilder>());
-
- public static StringBuilder getStringBuilder(final int initialSize) {
- // TODO: although the pool is synchronized, the following is not thread-safe.
- // Two threads entering this at the same time could take the same size of the pool and the
- // second to attempt removing this index from the pool would crash with an
- // IndexOutOfBoundsException.
- // At the moment this pool is only used in Suggest.java and only in one thread so it's
- // okay. The simplest thing to do here is probably to replace the ArrayList with a
- // ConcurrentQueue.
- final int poolSize = sInstance.mPool.size();
- final StringBuilder sb = poolSize > 0 ? (StringBuilder) sInstance.mPool.remove(poolSize - 1)
- : new StringBuilder(initialSize);
- sb.setLength(0);
- return sb;
- }
-
- public static void recycle(final StringBuilder garbage) {
- if (DEBUG) {
- final int gid = garbage.hashCode();
- for (final StringBuilder q : sInstance.mPool) {
- if (gid == q.hashCode()) throw new RuntimeException("Duplicate id " + gid);
- }
- }
- sInstance.mPool.add(garbage);
- }
-
- public static void ensureCapacity(final int capacity, final int initialSize) {
- for (int i = sInstance.mPool.size(); i < capacity; ++i) {
- final StringBuilder sb = new StringBuilder(initialSize);
- sInstance.mPool.add(sb);
- }
- }
-
- public static int getSize() {
- return sInstance.mPool.size();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
new file mode 100644
index 000000000..7b34cae63
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2012 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.EditorInfo;
+
+import com.android.inputmethod.keyboard.Keyboard;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class StringUtils {
+ private StringUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static boolean canBeFollowedByPeriod(final int codePoint) {
+ // TODO: Check again whether there really ain't a better way to check this.
+ // TODO: This should probably be language-dependant...
+ return Character.isLetterOrDigit(codePoint)
+ || codePoint == Keyboard.CODE_SINGLE_QUOTE
+ || codePoint == Keyboard.CODE_DOUBLE_QUOTE
+ || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
+ || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
+ || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
+ || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
+ }
+
+ public static int codePointCount(String text) {
+ if (TextUtils.isEmpty(text)) return 0;
+ return text.codePointCount(0, text.length());
+ }
+
+ public static boolean containsInCsv(String key, String csv) {
+ if (csv == null)
+ return false;
+ for (String option : csv.split(",")) {
+ if (option.equals(key))
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean inPrivateImeOptions(String packageName, String key,
+ EditorInfo editorInfo) {
+ if (editorInfo == null)
+ return false;
+ return containsInCsv(packageName != null ? packageName + "." + key : key,
+ editorInfo.privateImeOptions);
+ }
+
+ /**
+ * Returns true if a and b are equal ignoring the case of the character.
+ * @param a first character to check
+ * @param b second character to check
+ * @return {@code true} if a and b are equal, {@code false} otherwise.
+ */
+ public static boolean equalsIgnoreCase(char a, char b) {
+ // Some language, such as Turkish, need testing both cases.
+ return a == b
+ || Character.toLowerCase(a) == Character.toLowerCase(b)
+ || Character.toUpperCase(a) == Character.toUpperCase(b);
+ }
+
+ /**
+ * Returns true if a and b are equal ignoring the case of the characters, including if they are
+ * both null.
+ * @param a first CharSequence to check
+ * @param b second CharSequence to check
+ * @return {@code true} if a and b are equal, {@code false} otherwise.
+ */
+ public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
+ if (a == b)
+ return true; // including both a and b are null.
+ if (a == null || b == null)
+ return false;
+ final int length = a.length();
+ if (length != b.length())
+ return false;
+ for (int i = 0; i < length; i++) {
+ if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns true if a and b are equal ignoring the case of the characters, including if a is null
+ * and b is zero length.
+ * @param a CharSequence to check
+ * @param b character array to check
+ * @param offset start offset of array b
+ * @param length length of characters in array b
+ * @return {@code true} if a and b are equal, {@code false} otherwise.
+ * @throws IndexOutOfBoundsException
+ * if {@code offset < 0 || length < 0 || offset + length > data.length}.
+ * @throws NullPointerException if {@code b == null}.
+ */
+ public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
+ if (offset < 0 || length < 0 || length > b.length - offset)
+ throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
+ + " length=" + length);
+ if (a == null)
+ return length == 0; // including a is null and b is zero length.
+ if (a.length() != length)
+ return false;
+ for (int i = 0; i < length; i++) {
+ if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Remove duplicates from an array of strings.
+ *
+ * This method will always keep the first occurence of all strings at their position
+ * in the array, removing the subsequent ones.
+ */
+ public static void removeDupes(final ArrayList<CharSequence> suggestions) {
+ if (suggestions.size() < 2) return;
+ int i = 1;
+ // Don't cache suggestions.size(), since we may be removing items
+ while (i < suggestions.size()) {
+ final CharSequence cur = suggestions.get(i);
+ // Compare each suggestion with each previous suggestion
+ for (int j = 0; j < i; j++) {
+ CharSequence previous = suggestions.get(j);
+ if (TextUtils.equals(cur, previous)) {
+ suggestions.remove(i);
+ i--;
+ break;
+ }
+ }
+ i++;
+ }
+ }
+
+ public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
+ if (returnsNameInThisLocale) {
+ return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
+ } else {
+ return toTitleCase(locale.getDisplayName(), locale);
+ }
+ }
+
+ public static String getDisplayLanguage(Locale locale) {
+ return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
+ }
+
+ public static String getMiddleDisplayLanguage(Locale locale) {
+ return toTitleCase((LocaleUtils.constructLocaleFromString(
+ locale.getLanguage()).getDisplayLanguage(locale)), locale);
+ }
+
+ public static String getShortDisplayLanguage(Locale locale) {
+ return toTitleCase(locale.getLanguage(), locale);
+ }
+
+ public static String toTitleCase(String s, Locale locale) {
+ if (s.length() <= 1) {
+ // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
+ return s;
+ }
+ // TODO: fix the bugs below
+ // - This does not work for Greek, because it returns upper case instead of title case.
+ // - It does not work for Serbian, because it fails to account for the "lj" character,
+ // which should be "Lj" in title case and "LJ" in upper case.
+ // - It does not work for Dutch, because it fails to account for the "ij" digraph, which
+ // are two different characters but both should be capitalized as "IJ" as if they were
+ // a single letter.
+ // - It also does not work with unicode surrogate code points.
+ return s.toUpperCase(locale).charAt(0) + s.substring(1);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 8a4862094..ffbbf9bb8 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -35,7 +35,6 @@ import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
import com.android.inputmethod.deprecated.VoiceProxy;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.keyboard.LatinKeyboard;
import java.util.ArrayList;
import java.util.Arrays;
@@ -47,8 +46,8 @@ public class SubtypeSwitcher {
private static boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = SubtypeSwitcher.class.getSimpleName();
+ public static final String KEYBOARD_MODE = "keyboard";
private static final char LOCALE_SEPARATER = '_';
- private static final String KEYBOARD_MODE = "keyboard";
private static final String VOICE_MODE = "voice";
private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY =
"requireNetworkConnectivity";
@@ -421,11 +420,7 @@ public class SubtypeSwitcher {
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
mIsNetworkConnected = !noConnection;
- final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
- final LatinKeyboard keyboard = switcher.getLatinKeyboard();
- if (keyboard != null) {
- keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getKeyboardView());
- }
+ KeyboardSwitcher.getInstance().onNetworkStateChanged();
}
//////////////////////////////////
@@ -517,7 +512,7 @@ public class SubtypeSwitcher {
}
public String getInputLanguageName() {
- return Utils.getDisplayLanguage(getInputLocale());
+ return StringUtils.getDisplayLanguage(getInputLocale());
}
/////////////////////////////
diff --git a/java/src/com/android/inputmethod/latin/SubtypeUtils.java b/java/src/com/android/inputmethod/latin/SubtypeUtils.java
new file mode 100644
index 000000000..cb2bcf43f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/SubtypeUtils.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2012 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;
+
+import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
+
+import java.util.Collections;
+import java.util.List;
+
+public class SubtypeUtils {
+ private SubtypeUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ // TODO: Cache my InputMethodInfo and/or InputMethodSubtype list.
+ public static boolean checkIfSubtypeBelongsToThisIme(Context context,
+ InputMethodSubtypeCompatWrapper ims) {
+ final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+ if (imm == null) return false;
+
+ final InputMethodInfoCompatWrapper myImi = getInputMethodInfo(context.getPackageName());
+ final List<InputMethodSubtypeCompatWrapper> subtypes =
+ imm.getEnabledInputMethodSubtypeList(myImi, true);
+ for (final InputMethodSubtypeCompatWrapper subtype : subtypes) {
+ if (subtype.equals(ims)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean hasMultipleEnabledIMEsOrSubtypes(
+ final boolean shouldIncludeAuxiliarySubtypes) {
+ final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+ if (imm == null) return false;
+
+ final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
+ return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis);
+ }
+
+ public static boolean hasMultipleEnabledSubtypesInThisIme(Context context,
+ final boolean shouldIncludeAuxiliarySubtypes) {
+ final InputMethodInfoCompatWrapper myImi = getInputMethodInfo(context.getPackageName());
+ final List<InputMethodInfoCompatWrapper> imiList = Collections.singletonList(myImi);
+ return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList);
+ }
+
+ private static boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes,
+ List<InputMethodInfoCompatWrapper> imiList) {
+ final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+ if (imm == null) return false;
+
+ // Number of the filtered IMEs
+ int filteredImisCount = 0;
+
+ for (InputMethodInfoCompatWrapper imi : imiList) {
+ // We can return true immediately after we find two or more filtered IMEs.
+ if (filteredImisCount > 1) return true;
+ final List<InputMethodSubtypeCompatWrapper> subtypes =
+ imm.getEnabledInputMethodSubtypeList(imi, true);
+ // IMEs that have no subtypes should be counted.
+ if (subtypes.isEmpty()) {
+ ++filteredImisCount;
+ continue;
+ }
+
+ int auxCount = 0;
+ for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
+ if (subtype.isAuxiliary()) {
+ ++auxCount;
+ }
+ }
+ final int nonAuxCount = subtypes.size() - auxCount;
+
+ // IMEs that have one or more non-auxiliary subtypes should be counted.
+ // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+ // subtypes should be counted as well.
+ if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+ ++filteredImisCount;
+ continue;
+ }
+ }
+
+ if (filteredImisCount > 1) {
+ return true;
+ }
+ final List<InputMethodSubtypeCompatWrapper> subtypes =
+ imm.getEnabledInputMethodSubtypeList(null, true);
+ int keyboardCount = 0;
+ // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's
+ // both explicitly and implicitly enabled input method subtype.
+ // (The current IME should be LatinIME.)
+ for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
+ if (SubtypeSwitcher.KEYBOARD_MODE.equals(subtype.getMode())) {
+ ++keyboardCount;
+ }
+ }
+ return keyboardCount > 1;
+ }
+
+ public static String getInputMethodId(String packageName) {
+ return getInputMethodInfo(packageName).getId();
+ }
+
+ public static InputMethodInfoCompatWrapper getInputMethodInfo(String packageName) {
+ final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+ if (imm == null) {
+ throw new RuntimeException("Input method manager not found");
+ }
+
+ for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) {
+ if (imi.getPackageName().equals(packageName))
+ return imi;
+ }
+ throw new RuntimeException("Can not find input method id for " + packageName);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index caa5aac51..69754d769 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -20,7 +20,9 @@ import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.io.File;
import java.util.ArrayList;
@@ -42,9 +44,8 @@ public class Suggest implements Dictionary.WordCallback {
public static final int APPROX_MAX_WORD_LENGTH = 32;
public static final int CORRECTION_NONE = 0;
- public static final int CORRECTION_BASIC = 1;
- public static final int CORRECTION_FULL = 2;
- public static final int CORRECTION_FULL_BIGRAM = 3;
+ public static final int CORRECTION_FULL = 1;
+ public static final int CORRECTION_FULL_BIGRAM = 2;
/**
* Words that appear in both bigram and unigram data gets multiplier ranging from
@@ -83,8 +84,6 @@ public class Suggest implements Dictionary.WordCallback {
private static final boolean DBG = LatinImeLogger.sDBG;
- private AutoCorrection mAutoCorrection;
-
private Dictionary mMainDict;
private ContactsDictionary mContactsDict;
private WhitelistDictionary mWhiteListDictionary;
@@ -100,14 +99,15 @@ public class Suggest implements Dictionary.WordCallback {
private int[] mBigramScores = new int[PREF_MAX_BIGRAMS];
private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
- ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
- private CharSequence mTypedWord;
+ private ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
+ private CharSequence mConsideredWord;
// TODO: Remove these member variables by passing more context to addWord() callback method
private boolean mIsFirstCharCapitalized;
private boolean mIsAllUpperCase;
+ private int mTrailingSingleQuotesCount;
- private int mCorrectionMode = CORRECTION_BASIC;
+ private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
public Suggest(final Context context, final int dictionaryResId, final Locale locale) {
initAsynchronously(context, dictionaryResId, locale);
@@ -116,15 +116,13 @@ public class Suggest implements Dictionary.WordCallback {
/* package for test */ Suggest(final Context context, final File dictionary,
final long startOffset, final long length, final Flag[] flagArray,
final Locale locale) {
- initSynchronously(null, DictionaryFactory.createDictionaryForTest(context, dictionary,
+ initSynchronously(context, DictionaryFactory.createDictionaryForTest(context, dictionary,
startOffset, length, flagArray), locale);
}
private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
mWhiteListDictionary = new WhitelistDictionary(context, locale);
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
- mAutoCorrection = new AutoCorrection();
- StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
}
private void initAsynchronously(final Context context, final int dictionaryResId,
@@ -144,7 +142,7 @@ public class Suggest implements Dictionary.WordCallback {
initWhitelistAndAutocorrectAndPool(context, locale);
}
- private void addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key,
+ private static void addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key,
Dictionary dict) {
final Dictionary oldDict = (dict == null)
? dictionaries.remove(key)
@@ -169,14 +167,6 @@ public class Suggest implements Dictionary.WordCallback {
}.start();
}
- public int getCorrectionMode() {
- return mCorrectionMode;
- }
-
- public void setCorrectionMode(int mode) {
- mCorrectionMode = mode;
- }
-
// The main dictionary could have been loaded asynchronously. Don't cache the return value
// of this method.
public boolean hasMainDictionary() {
@@ -191,7 +181,7 @@ public class Suggest implements Dictionary.WordCallback {
return mUnigramDictionaries;
}
- public int getApproxMaxWordLength() {
+ public static int getApproxMaxWordLength() {
return APPROX_MAX_WORD_LENGTH;
}
@@ -226,44 +216,11 @@ public class Suggest implements Dictionary.WordCallback {
mAutoCorrectionThreshold = threshold;
}
- public boolean isAggressiveAutoCorrectionMode() {
- return (mAutoCorrectionThreshold == 0);
- }
-
- /**
- * Number of suggestions to generate from the input key sequence. This has
- * to be a number between 1 and 100 (inclusive).
- * @param maxSuggestions
- * @throws IllegalArgumentException if the number is out of range
- */
- public void setMaxSuggestions(int maxSuggestions) {
- if (maxSuggestions < 1 || maxSuggestions > 100) {
- throw new IllegalArgumentException("maxSuggestions must be between 1 and 100");
- }
- mPrefMaxSuggestions = maxSuggestions;
- mScores = new int[mPrefMaxSuggestions];
- mBigramScores = new int[PREF_MAX_BIGRAMS];
- collectGarbage(mSuggestions, mPrefMaxSuggestions);
- StringBuilderPool.ensureCapacity(mPrefMaxSuggestions, getApproxMaxWordLength());
- }
-
- /**
- * Returns a object which represents suggested words that match the list of character codes
- * passed in. This object contents will be overwritten the next time this function is called.
- * @param wordComposer contains what is currently being typed
- * @param prevWordForBigram previous word (used only for bigram)
- * @return suggested words object.
- */
- public SuggestedWords getSuggestions(final WordComposer wordComposer,
- final CharSequence prevWordForBigram, final ProximityInfo proximityInfo) {
- return getSuggestedWordBuilder(wordComposer, prevWordForBigram,
- proximityInfo).build();
- }
-
- private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
+ private static CharSequence capitalizeWord(final boolean all, final boolean first,
+ final CharSequence word) {
if (TextUtils.isEmpty(word) || !(all || first)) return word;
final int wordLength = word.length();
- final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+ final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
// TODO: Must pay attention to locale when changing case.
if (all) {
sb.append(word.toString().toUpperCase());
@@ -277,41 +234,73 @@ public class Suggest implements Dictionary.WordCallback {
}
protected void addBigramToSuggestions(CharSequence bigram) {
- // TODO: Try to be a little more shrewd with resource allocation.
- // At the moment we copy this object because the StringBuilders are pooled (see
- // StringBuilderPool.java) and when we are finished using mSuggestions and
- // mBigramSuggestions we will take everything from both and insert them back in the
- // pool, so we can't allow the same object to be in both lists at the same time.
- final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+ final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
sb.append(bigram);
mSuggestions.add(sb);
}
+ private static final WordComposer sEmptyWordComposer = new WordComposer();
+ public SuggestedWords getBigramPredictions(CharSequence prevWordForBigram) {
+ LatinImeLogger.onStartSuggestion(prevWordForBigram);
+ mIsFirstCharCapitalized = false;
+ mIsAllUpperCase = false;
+ mTrailingSingleQuotesCount = 0;
+ mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
+ Arrays.fill(mScores, 0);
+
+ // Treating USER_TYPED as UNIGRAM suggestion for logging now.
+ LatinImeLogger.onAddSuggestedWord("", Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
+ mConsideredWord = "";
+
+ Arrays.fill(mBigramScores, 0);
+ mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);
+
+ CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
+ if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
+ prevWordForBigram = lowerPrevWord;
+ }
+ for (final Dictionary dictionary : mBigramDictionaries.values()) {
+ dictionary.getBigrams(sEmptyWordComposer, prevWordForBigram, this);
+ }
+ // Nothing entered: return all bigrams for the previous word
+ int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
+ for (int i = 0; i < insertCount; ++i) {
+ addBigramToSuggestions(mBigramSuggestions.get(i));
+ }
+
+ StringUtils.removeDupes(mSuggestions);
+
+ return new SuggestedWords(SuggestedWords.getFromCharSequenceList(mSuggestions),
+ false /* typedWordValid */,
+ false /* hasAutoCorrectionCandidate */,
+ false /* allowsToBeAutoCorrected */,
+ false /* isPunctuationSuggestions */,
+ false /* isObsoleteSuggestions */);
+ }
+
// TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
- public SuggestedWords.Builder getSuggestedWordBuilder(
+ public SuggestedWords getSuggestedWords(
final WordComposer wordComposer, CharSequence prevWordForBigram,
- final ProximityInfo proximityInfo) {
+ final ProximityInfo proximityInfo, final int correctionMode) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
- mAutoCorrection.init();
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
mIsAllUpperCase = wordComposer.isAllUpperCase();
- collectGarbage(mSuggestions, mPrefMaxSuggestions);
+ mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
+ mSuggestions = new ArrayList<CharSequence>(mPrefMaxSuggestions);
Arrays.fill(mScores, 0);
- // Save a lowercase version of the original word
- String typedWord = wordComposer.getTypedWord();
- if (typedWord != null) {
- // Treating USER_TYPED as UNIGRAM suggestion for logging now.
- LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED,
- Dictionary.DataType.UNIGRAM);
- }
- mTypedWord = typedWord;
+ final String typedWord = wordComposer.getTypedWord();
+ final String consideredWord = mTrailingSingleQuotesCount > 0
+ ? typedWord.substring(0, typedWord.length() - mTrailingSingleQuotesCount)
+ : typedWord;
+ // Treating USER_TYPED as UNIGRAM suggestion for logging now.
+ LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
+ mConsideredWord = consideredWord;
- if (wordComposer.size() <= 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
- || mCorrectionMode == CORRECTION_BASIC)) {
+ if (wordComposer.size() <= 1 && (correctionMode == CORRECTION_FULL_BIGRAM)) {
// At first character typed, search only the bigrams
Arrays.fill(mBigramScores, 0);
- collectGarbage(mBigramSuggestions, PREF_MAX_BIGRAMS);
+ mBigramSuggestions = new ArrayList<CharSequence>(PREF_MAX_BIGRAMS);
if (!TextUtils.isEmpty(prevWordForBigram)) {
CharSequence lowerPrevWord = prevWordForBigram.toString().toLowerCase();
@@ -321,7 +310,7 @@ public class Suggest implements Dictionary.WordCallback {
for (final Dictionary dictionary : mBigramDictionaries.values()) {
dictionary.getBigrams(wordComposer, prevWordForBigram, this);
}
- if (TextUtils.isEmpty(typedWord)) {
+ if (TextUtils.isEmpty(consideredWord)) {
// Nothing entered: return all bigrams for the previous word
int insertCount = Math.min(mBigramSuggestions.size(), mPrefMaxSuggestions);
for (int i = 0; i < insertCount; ++i) {
@@ -329,8 +318,7 @@ public class Suggest implements Dictionary.WordCallback {
}
} else {
// Word entered: return only bigrams that match the first char of the typed word
- @SuppressWarnings("null")
- final char currentChar = typedWord.charAt(0);
+ final char currentChar = consideredWord.charAt(0);
// TODO: Must pay attention to locale when changing case.
final char currentCharUpper = Character.toUpperCase(currentChar);
int count = 0;
@@ -348,70 +336,126 @@ public class Suggest implements Dictionary.WordCallback {
}
} else if (wordComposer.size() > 1) {
+ final WordComposer wordComposerForLookup;
+ if (mTrailingSingleQuotesCount > 0) {
+ wordComposerForLookup = new WordComposer(wordComposer);
+ for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+ wordComposerForLookup.deleteLast();
+ }
+ } else {
+ wordComposerForLookup = wordComposer;
+ }
// At second character typed, search the unigrams (scores being affected by bigrams)
for (final String key : mUnigramDictionaries.keySet()) {
// Skip UserUnigramDictionary and WhitelistDictionary to lookup
if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
continue;
final Dictionary dictionary = mUnigramDictionaries.get(key);
- dictionary.getWords(wordComposer, this, proximityInfo);
+ dictionary.getWords(wordComposerForLookup, this, proximityInfo);
}
}
- final String typedWordString = typedWord == null ? null : typedWord.toString();
- CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized,
- mWhiteListDictionary.getWhitelistedWord(typedWordString));
+ final CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase,
+ mIsFirstCharCapitalized, mWhiteListDictionary.getWhitelistedWord(consideredWord));
- mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
- mSuggestions, mScores, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
- whitelistedWord);
+ final boolean hasAutoCorrection;
+ if (CORRECTION_FULL == correctionMode || CORRECTION_FULL_BIGRAM == correctionMode) {
+ final CharSequence autoCorrection =
+ AutoCorrection.computeAutoCorrectionWord(mUnigramDictionaries, wordComposer,
+ mSuggestions, mScores, consideredWord, mAutoCorrectionThreshold,
+ whitelistedWord);
+ hasAutoCorrection = (null != autoCorrection);
+ } else {
+ hasAutoCorrection = false;
+ }
if (whitelistedWord != null) {
- mSuggestions.add(0, whitelistedWord);
+ if (mTrailingSingleQuotesCount > 0) {
+ final StringBuilder sb = new StringBuilder(whitelistedWord);
+ for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+ sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
+ }
+ mSuggestions.add(0, sb.toString());
+ } else {
+ mSuggestions.add(0, whitelistedWord);
+ }
}
- if (typedWord != null) {
- mSuggestions.add(0, typedWordString);
- }
- Utils.removeDupes(mSuggestions);
+ mSuggestions.add(0, typedWord);
+ StringUtils.removeDupes(mSuggestions);
+ final ArrayList<SuggestedWordInfo> suggestionsList;
if (DBG) {
- double normalizedScore = mAutoCorrection.getNormalizedScore();
- ArrayList<SuggestedWords.SuggestedWordInfo> scoreInfoList =
- new ArrayList<SuggestedWords.SuggestedWordInfo>();
- scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
- for (int i = 0; i < mScores.length; ++i) {
- if (normalizedScore > 0) {
- final String scoreThreshold = String.format("%d (%4.2f)", mScores[i],
- normalizedScore);
- scoreInfoList.add(
- new SuggestedWords.SuggestedWordInfo(scoreThreshold, false));
- normalizedScore = 0.0;
- } else {
- final String score = Integer.toString(mScores[i]);
- scoreInfoList.add(new SuggestedWords.SuggestedWordInfo(score, false));
- }
- }
- for (int i = mScores.length; i < mSuggestions.size(); ++i) {
- scoreInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
- }
- return new SuggestedWords.Builder().addWords(mSuggestions, scoreInfoList);
+ suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, mSuggestions, mScores);
+ } else {
+ suggestionsList = SuggestedWords.getFromCharSequenceList(mSuggestions);
+ }
+
+ // TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
+ // but still autocorrected from - in the case the whitelist only capitalizes the word.
+ // The whitelist should be case-insensitive, so it's not possible to be consistent with
+ // a boolean flag. Right now this is handled with a slight hack in
+ // WhitelistDictionary#shouldForciblyAutoCorrectFrom.
+ final boolean allowsToBeAutoCorrected = AutoCorrection.allowsToBeAutoCorrected(
+ getUnigramDictionaries(), consideredWord, wordComposer.isFirstCharCapitalized());
+
+ boolean autoCorrectionAvailable = hasAutoCorrection;
+ if (correctionMode == CORRECTION_FULL || correctionMode == CORRECTION_FULL_BIGRAM) {
+ autoCorrectionAvailable |= !allowsToBeAutoCorrected;
+ }
+ // Don't auto-correct words with multiple capital letter
+ autoCorrectionAvailable &= !wordComposer.isMostlyCaps();
+ if (allowsToBeAutoCorrected && suggestionsList.size() > 1 && mAutoCorrectionThreshold > 0
+ && Suggest.shouldBlockAutoCorrectionBySafetyNet(typedWord,
+ suggestionsList.get(1).mWord)) {
+ autoCorrectionAvailable = false;
}
- return new SuggestedWords.Builder().addWords(mSuggestions, null);
+ return new SuggestedWords(suggestionsList,
+ !allowsToBeAutoCorrected /* typedWordValid */,
+ autoCorrectionAvailable /* hasAutoCorrectionCandidate */,
+ allowsToBeAutoCorrected /* allowsToBeAutoCorrected */,
+ false /* isPunctuationSuggestions */,
+ false /* isObsoleteSuggestions */);
}
- public boolean hasAutoCorrection() {
- return mAutoCorrection.hasAutoCorrection();
+ // This assumes the scores[] array is at least as long as suggestions.size() - 1.
+ private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
+ final String typedWord, final ArrayList<CharSequence> suggestions, final int[] scores) {
+ // TODO: this doesn't take into account the fact that removing dupes from mSuggestions
+ // may have made mScores[] and mSuggestions out of sync.
+ final CharSequence autoCorrectionSuggestion = suggestions.get(0);
+ double normalizedScore = BinaryDictionary.calcNormalizedScore(
+ typedWord, autoCorrectionSuggestion.toString(), scores[0]);
+ final int suggestionsSize = suggestions.size();
+ final ArrayList<SuggestedWordInfo> suggestionsList =
+ new ArrayList<SuggestedWordInfo>(suggestionsSize);
+ suggestionsList.add(new SuggestedWordInfo(autoCorrectionSuggestion, "+"));
+ // Note: i here is the index in mScores[], but the index in mSuggestions is one more
+ // than i because we added the typed word to mSuggestions without touching mScores.
+ for (int i = 0; i < scores.length && i < suggestionsSize - 1; ++i) {
+ final String scoreInfoString;
+ if (normalizedScore > 0) {
+ scoreInfoString = String.format("%d (%4.2f)", scores[i], normalizedScore);
+ normalizedScore = 0.0;
+ } else {
+ scoreInfoString = Integer.toString(scores[i]);
+ }
+ suggestionsList.add(new SuggestedWordInfo(suggestions.get(i + 1), scoreInfoString));
+ }
+ for (int i = scores.length; i < suggestionsSize; ++i) {
+ suggestionsList.add(new SuggestedWordInfo(suggestions.get(i), "--"));
+ }
+ return suggestionsList;
}
@Override
public boolean addWord(final char[] word, final int offset, final int length, int score,
- final int dicTypeId, final Dictionary.DataType dataType) {
- Dictionary.DataType dataTypeForLog = dataType;
+ final int dicTypeId, final int dataType) {
+ int dataTypeForLog = dataType;
final ArrayList<CharSequence> suggestions;
final int[] sortedScores;
final int prefMaxSuggestions;
- if(dataType == Dictionary.DataType.BIGRAM) {
+ if (dataType == Dictionary.BIGRAM) {
suggestions = mBigramSuggestions;
sortedScores = mBigramScores;
prefMaxSuggestions = PREF_MAX_BIGRAMS;
@@ -424,7 +468,7 @@ public class Suggest implements Dictionary.WordCallback {
int pos = 0;
// Check if it's the same word, only caps are different
- if (Utils.equalsIgnoreCase(mTypedWord, word, offset, length)) {
+ if (StringUtils.equalsIgnoreCase(mConsideredWord, word, offset, length)) {
// TODO: remove this surrounding if clause and move this logic to
// getSuggestedWordBuilder.
if (suggestions.size() > 0) {
@@ -433,17 +477,17 @@ public class Suggest implements Dictionary.WordCallback {
// frequency to determine the insertion position. This does not ensure strictly
// correct ordering, but ensures the top score is on top which is enough for
// removing duplicates correctly.
- if (Utils.equalsIgnoreCase(currentHighestWord, word, offset, length)
+ if (StringUtils.equalsIgnoreCase(currentHighestWord, word, offset, length)
&& score <= sortedScores[0]) {
pos = 1;
}
}
} else {
- if (dataType == Dictionary.DataType.UNIGRAM) {
+ if (dataType == Dictionary.UNIGRAM) {
// Check if the word was already added before (by bigram data)
int bigramSuggestion = searchBigramSuggestion(word,offset,length);
if(bigramSuggestion >= 0) {
- dataTypeForLog = Dictionary.DataType.BIGRAM;
+ dataTypeForLog = Dictionary.BIGRAM;
// turn freq from bigram into multiplier specified above
double multiplier = (((double) mBigramScores[bigramSuggestion])
/ MAXIMUM_BIGRAM_FREQUENCY)
@@ -474,7 +518,7 @@ public class Suggest implements Dictionary.WordCallback {
System.arraycopy(sortedScores, pos, sortedScores, pos + 1, prefMaxSuggestions - pos - 1);
sortedScores[pos] = score;
- final StringBuilder sb = StringBuilderPool.getStringBuilder(getApproxMaxWordLength());
+ final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
// TODO: Must pay attention to locale when changing case.
if (mIsAllUpperCase) {
sb.append(new String(word, offset, length).toUpperCase());
@@ -486,12 +530,12 @@ public class Suggest implements Dictionary.WordCallback {
} else {
sb.append(word, offset, length);
}
+ for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+ sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
+ }
suggestions.add(pos, sb);
if (suggestions.size() > prefMaxSuggestions) {
- final CharSequence garbage = suggestions.remove(prefMaxSuggestions);
- if (garbage instanceof StringBuilder) {
- StringBuilderPool.recycle((StringBuilder)garbage);
- }
+ suggestions.remove(prefMaxSuggestions);
} else {
LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
}
@@ -502,39 +546,22 @@ public class Suggest implements Dictionary.WordCallback {
// TODO This is almost O(n^2). Might need fix.
// search whether the word appeared in bigram data
int bigramSuggestSize = mBigramSuggestions.size();
- for(int i = 0; i < bigramSuggestSize; i++) {
- if(mBigramSuggestions.get(i).length() == length) {
+ for (int i = 0; i < bigramSuggestSize; i++) {
+ if (mBigramSuggestions.get(i).length() == length) {
boolean chk = true;
- for(int j = 0; j < length; j++) {
- if(mBigramSuggestions.get(i).charAt(j) != word[offset+j]) {
+ for (int j = 0; j < length; j++) {
+ if (mBigramSuggestions.get(i).charAt(j) != word[offset+j]) {
chk = false;
break;
}
}
- if(chk) return i;
+ if (chk) return i;
}
}
return -1;
}
- private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) {
- int poolSize = StringBuilderPool.getSize();
- int garbageSize = suggestions.size();
- while (poolSize < prefMaxSuggestions && garbageSize > 0) {
- final CharSequence garbage = suggestions.get(garbageSize - 1);
- if (garbage instanceof StringBuilder) {
- StringBuilderPool.recycle((StringBuilder)garbage);
- poolSize++;
- }
- garbageSize--;
- }
- if (poolSize == prefMaxSuggestions + 1) {
- Log.w("Suggest", "String pool got too big: " + poolSize);
- }
- suggestions.clear();
- }
-
public void close() {
final Set<Dictionary> dictionaries = new HashSet<Dictionary>();
dictionaries.addAll(mUnigramDictionaries.values());
@@ -544,4 +571,37 @@ public class Suggest implements Dictionary.WordCallback {
}
mMainDict = null;
}
+
+ // TODO: Resolve the inconsistencies between the native auto correction algorithms and
+ // this safety net
+ public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord,
+ final CharSequence suggestion) {
+ // Safety net for auto correction.
+ // Actually if we hit this safety net, it's a bug.
+ // If user selected aggressive auto correction mode, there is no need to use the safety
+ // net.
+ // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
+ // we should not use net because relatively edit distance can be big.
+ final int typedWordLength = typedWord.length();
+ if (typedWordLength < Suggest.MINIMUM_SAFETY_NET_CHAR_LENGTH) {
+ return false;
+ }
+ final int maxEditDistanceOfNativeDictionary =
+ (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
+ final int distance = BinaryDictionary.editDistance(typedWord, suggestion.toString());
+ if (DBG) {
+ Log.d(TAG, "Autocorrected edit distance = " + distance
+ + ", " + maxEditDistanceOfNativeDictionary);
+ }
+ if (distance > maxEditDistanceOfNativeDictionary) {
+ if (DBG) {
+ Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestion);
+ Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
+ + "Turning off auto-correction.");
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index ed6359cfa..b63bc6c29 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -20,211 +20,131 @@ import android.text.TextUtils;
import android.view.inputmethod.CompletionInfo;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
public class SuggestedWords {
- public static final SuggestedWords EMPTY = new SuggestedWords(null, false, false, false, null);
+ public static final SuggestedWords EMPTY = new SuggestedWords(
+ Collections.<SuggestedWordInfo>emptyList(), false, false, false, false, false);
- public final List<CharSequence> mWords;
public final boolean mTypedWordValid;
public final boolean mHasAutoCorrectionCandidate;
public final boolean mIsPunctuationSuggestions;
+ public final boolean mAllowsToBeAutoCorrected;
+ public final boolean mIsObsoleteSuggestions;
private final List<SuggestedWordInfo> mSuggestedWordInfoList;
- private boolean mShouldBlockAutoCorrection;
-
- private SuggestedWords(List<CharSequence> words, boolean typedWordValid,
- boolean hasAutoCorrectionCandidate, boolean isPunctuationSuggestions,
- List<SuggestedWordInfo> suggestedWordInfoList) {
- if (words != null) {
- mWords = words;
- } else {
- mWords = Collections.emptyList();
- }
+
+ public SuggestedWords(final List<SuggestedWordInfo> suggestedWordInfoList,
+ final boolean typedWordValid,
+ final boolean hasAutoCorrectionCandidate,
+ final boolean allowsToBeAutoCorrected,
+ final boolean isPunctuationSuggestions,
+ final boolean isObsoleteSuggestions) {
+ mSuggestedWordInfoList = suggestedWordInfoList;
mTypedWordValid = typedWordValid;
mHasAutoCorrectionCandidate = hasAutoCorrectionCandidate;
+ mAllowsToBeAutoCorrected = allowsToBeAutoCorrected;
mIsPunctuationSuggestions = isPunctuationSuggestions;
- mSuggestedWordInfoList = suggestedWordInfoList;
- mShouldBlockAutoCorrection = false;
+ mIsObsoleteSuggestions = isObsoleteSuggestions;
}
public int size() {
- return mWords.size();
+ return mSuggestedWordInfoList.size();
}
public CharSequence getWord(int pos) {
- return mWords.get(pos);
+ return mSuggestedWordInfoList.get(pos).mWord;
}
public SuggestedWordInfo getInfo(int pos) {
- return mSuggestedWordInfoList != null ? mSuggestedWordInfoList.get(pos) : null;
+ return mSuggestedWordInfoList.get(pos);
}
public boolean hasAutoCorrectionWord() {
return mHasAutoCorrectionCandidate && size() > 1 && !mTypedWordValid;
}
- public boolean hasWordAboveAutoCorrectionScoreThreshold() {
- return mHasAutoCorrectionCandidate && ((size() > 1 && !mTypedWordValid) || mTypedWordValid);
- }
-
- public boolean isPunctuationSuggestions() {
- return mIsPunctuationSuggestions;
+ public boolean willAutoCorrect() {
+ return !mTypedWordValid && mHasAutoCorrectionCandidate;
}
- public void setShouldBlockAutoCorrection() {
- mShouldBlockAutoCorrection = true;
+ @Override
+ public String toString() {
+ // Pretty-print method to help debug
+ return "SuggestedWords:"
+ + " mTypedWordValid=" + mTypedWordValid
+ + " mHasAutoCorrectionCandidate=" + mHasAutoCorrectionCandidate
+ + " mAllowsToBeAutoCorrected=" + mAllowsToBeAutoCorrected
+ + " mIsPunctuationSuggestions=" + mIsPunctuationSuggestions
+ + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray());
}
- public boolean shouldBlockAutoCorrection() {
- return mShouldBlockAutoCorrection;
- }
-
- public static class Builder {
- private List<CharSequence> mWords = new ArrayList<CharSequence>();
- private boolean mTypedWordValid;
- private boolean mHasMinimalSuggestion;
- private boolean mIsPunctuationSuggestions;
- private List<SuggestedWordInfo> mSuggestedWordInfoList =
- new ArrayList<SuggestedWordInfo>();
-
- public Builder() {
- // Nothing to do here.
- }
-
- public Builder addWords(List<CharSequence> words,
- List<SuggestedWordInfo> suggestedWordInfoList) {
- final int N = words.size();
- for (int i = 0; i < N; ++i) {
- SuggestedWordInfo suggestedWordInfo = null;
- if (suggestedWordInfoList != null) {
- suggestedWordInfo = suggestedWordInfoList.get(i);
- }
- if (suggestedWordInfo == null) {
- suggestedWordInfo = new SuggestedWordInfo();
- }
- addWord(words.get(i), suggestedWordInfo);
- }
- return this;
- }
-
- public Builder addWord(CharSequence word) {
- return addWord(word, null, false);
- }
-
- public Builder addWord(CharSequence word, CharSequence debugString,
- boolean isPreviousSuggestedWord) {
- SuggestedWordInfo info = new SuggestedWordInfo(debugString, isPreviousSuggestedWord);
- return addWord(word, info);
- }
-
- private Builder addWord(CharSequence word, SuggestedWordInfo suggestedWordInfo) {
- if (!TextUtils.isEmpty(word)) {
- mWords.add(word);
- // It's okay if suggestedWordInfo is null since it's checked where it's used.
- mSuggestedWordInfoList.add(suggestedWordInfo);
- }
- return this;
- }
-
- public Builder setApplicationSpecifiedCompletions(CompletionInfo[] infos) {
- for (CompletionInfo info : infos) {
- if (null != info) addWord(info.getText());
- }
- return this;
- }
-
- public Builder setTypedWordValid(boolean typedWordValid) {
- mTypedWordValid = typedWordValid;
- return this;
- }
-
- public Builder setHasMinimalSuggestion(boolean hasMinimalSuggestion) {
- mHasMinimalSuggestion = hasMinimalSuggestion;
- return this;
- }
-
- public Builder setIsPunctuationSuggestions() {
- mIsPunctuationSuggestions = true;
- return this;
- }
-
- // Should get rid of the first one (what the user typed previously) from suggestions
- // and replace it with what the user currently typed.
- public Builder addTypedWordAndPreviousSuggestions(CharSequence typedWord,
- SuggestedWords previousSuggestions) {
- mWords.clear();
- mSuggestedWordInfoList.clear();
- final HashSet<String> alreadySeen = new HashSet<String>();
- addWord(typedWord, null, false);
- alreadySeen.add(typedWord.toString());
- final int previousSize = previousSuggestions.size();
- for (int pos = 1; pos < previousSize; pos++) {
- final String prevWord = previousSuggestions.getWord(pos).toString();
- // Filter out duplicate suggestion.
- if (!alreadySeen.contains(prevWord)) {
- addWord(prevWord, null, true);
- alreadySeen.add(prevWord);
- }
- }
- mTypedWordValid = false;
- mHasMinimalSuggestion = false;
- return this;
- }
-
- public SuggestedWords build() {
- return new SuggestedWords(mWords, mTypedWordValid, mHasMinimalSuggestion,
- mIsPunctuationSuggestions, mSuggestedWordInfoList);
- }
-
- public int size() {
- return mWords.size();
+ public static ArrayList<SuggestedWordInfo> getFromCharSequenceList(
+ final List<CharSequence> wordList) {
+ final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
+ for (CharSequence word : wordList) {
+ if (null != word) result.add(new SuggestedWordInfo(word));
}
+ return result;
+ }
- public CharSequence getWord(int pos) {
- return mWords.get(pos);
+ public static List<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
+ final CompletionInfo[] infos) {
+ final ArrayList<SuggestedWordInfo> result = new ArrayList<SuggestedWordInfo>();
+ for (CompletionInfo info : infos) {
+ if (null != info) result.add(new SuggestedWordInfo(info.getText()));
}
+ return result;
+ }
- @Override
- public String toString() {
- // Pretty-print method to help debug
- final StringBuilder sb = new StringBuilder("StringBuilder: mTypedWordValid = "
- + mTypedWordValid + " ; mHasMinimalSuggestion = " + mHasMinimalSuggestion
- + " ; mIsPunctuationSuggestions = " + mIsPunctuationSuggestions
- + " --- ");
- for (CharSequence s : mWords) {
- sb.append(s);
- sb.append(" ; ");
+ // Should get rid of the first one (what the user typed previously) from suggestions
+ // and replace it with what the user currently typed.
+ public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
+ final CharSequence typedWord, final SuggestedWords previousSuggestions) {
+ final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<SuggestedWordInfo>();
+ final HashSet<String> alreadySeen = new HashSet<String>();
+ suggestionsList.add(new SuggestedWordInfo(typedWord));
+ alreadySeen.add(typedWord.toString());
+ final int previousSize = previousSuggestions.size();
+ for (int pos = 1; pos < previousSize; pos++) {
+ final String prevWord = previousSuggestions.getWord(pos).toString();
+ // Filter out duplicate suggestion.
+ if (!alreadySeen.contains(prevWord)) {
+ suggestionsList.add(new SuggestedWordInfo(prevWord));
+ alreadySeen.add(prevWord);
}
- return sb.toString();
}
+ return suggestionsList;
}
public static class SuggestedWordInfo {
- private final CharSequence mDebugString;
- private final boolean mPreviousSuggestedWord;
+ public final CharSequence mWord;
+ private final String mDebugString;
- public SuggestedWordInfo() {
+ public SuggestedWordInfo(final CharSequence word) {
+ mWord = word;
mDebugString = "";
- mPreviousSuggestedWord = false;
}
- public SuggestedWordInfo(CharSequence debugString, boolean previousSuggestedWord) {
+ public SuggestedWordInfo(final CharSequence word, final String debugString) {
+ mWord = word;
+ if (null == debugString) throw new NullPointerException("");
mDebugString = debugString;
- mPreviousSuggestedWord = previousSuggestedWord;
}
public String getDebugString() {
- if (mDebugString == null) {
- return "";
- } else {
- return mDebugString.toString();
- }
+ return mDebugString;
}
- public boolean isObsoleteSuggestedWord () {
- return mPreviousSuggestedWord;
+ @Override
+ public String toString() {
+ if (TextUtils.isEmpty(mDebugString)) {
+ return mWord.toString();
+ } else {
+ return mWord.toString() + " (" + mDebugString.toString() + ")";
+ }
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
deleted file mode 100644
index 79b3bdebb..000000000
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2008 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.keyboard.Keyboard;
-import com.android.inputmethod.latin.Utils.RingCharBuffer;
-
-import android.util.Log;
-
-public class TextEntryState {
- private static final String TAG = TextEntryState.class.getSimpleName();
- private static final boolean DEBUG = false;
-
- private static final int UNKNOWN = 0;
- private static final int START = 1;
- private static final int IN_WORD = 2;
- private static final int ACCEPTED_DEFAULT = 3;
- private static final int PICKED_SUGGESTION = 4;
- private static final int PUNCTUATION_AFTER_WORD = 5;
- private static final int PUNCTUATION_AFTER_ACCEPTED = 6;
- private static final int SPACE_AFTER_ACCEPTED = 7;
- private static final int SPACE_AFTER_PICKED = 8;
- private static final int UNDO_COMMIT = 9;
- private static final int RECORRECTING = 10;
- private static final int PICKED_RECORRECTION = 11;
-
- private static int sState = UNKNOWN;
- private static int sPreviousState = UNKNOWN;
-
- private static void setState(final int newState) {
- sPreviousState = sState;
- sState = newState;
- }
-
- public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord,
- int separatorCode) {
- if (typedWord == null) return;
- setState(ACCEPTED_DEFAULT);
- LatinImeLogger.logOnAutoCorrection(
- typedWord.toString(), actualWord.toString(), separatorCode);
- if (DEBUG)
- displayState("acceptedDefault", "typedWord", typedWord, "actualWord", actualWord);
- }
-
- // State.ACCEPTED_DEFAULT will be changed to other sub-states
- // (see "case ACCEPTED_DEFAULT" in typedCharacter() below),
- // and should be restored back to State.ACCEPTED_DEFAULT after processing for each sub-state.
- public static void backToAcceptedDefault(CharSequence typedWord) {
- if (typedWord == null) return;
- switch (sState) {
- case SPACE_AFTER_ACCEPTED:
- case PUNCTUATION_AFTER_ACCEPTED:
- case IN_WORD:
- setState(ACCEPTED_DEFAULT);
- break;
- default:
- break;
- }
- if (DEBUG) displayState("backToAcceptedDefault", "typedWord", typedWord);
- }
-
- public static void acceptedTyped(CharSequence typedWord) {
- setState(PICKED_SUGGESTION);
- if (DEBUG) displayState("acceptedTyped", "typedWord", typedWord);
- }
-
- public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
- if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
- setState(PICKED_RECORRECTION);
- } else {
- setState(PICKED_SUGGESTION);
- }
- if (DEBUG)
- displayState("acceptedSuggestion", "typedWord", typedWord, "actualWord", actualWord);
- }
-
- public static void selectedForRecorrection() {
- setState(RECORRECTING);
- if (DEBUG) displayState("selectedForRecorrection");
- }
-
- public static void onAbortRecorrection() {
- if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
- setState(START);
- }
- if (DEBUG) displayState("onAbortRecorrection");
- }
-
- public static void typedCharacter(char c, boolean isSeparator, int x, int y) {
- final boolean isSpace = (c == Keyboard.CODE_SPACE);
- switch (sState) {
- case IN_WORD:
- if (isSpace || isSeparator) {
- setState(START);
- } else {
- // State hasn't changed.
- }
- break;
- case ACCEPTED_DEFAULT:
- case SPACE_AFTER_PICKED:
- case PUNCTUATION_AFTER_ACCEPTED:
- if (isSpace) {
- setState(SPACE_AFTER_ACCEPTED);
- } else if (isSeparator) {
- // Swap
- setState(PUNCTUATION_AFTER_ACCEPTED);
- } else {
- setState(IN_WORD);
- }
- break;
- case PICKED_SUGGESTION:
- case PICKED_RECORRECTION:
- if (isSpace) {
- setState(SPACE_AFTER_PICKED);
- } else if (isSeparator) {
- // Swap
- setState(PUNCTUATION_AFTER_ACCEPTED);
- } else {
- setState(IN_WORD);
- }
- break;
- case START:
- case UNKNOWN:
- case SPACE_AFTER_ACCEPTED:
- case PUNCTUATION_AFTER_WORD:
- if (!isSpace && !isSeparator) {
- setState(IN_WORD);
- } else {
- setState(START);
- }
- break;
- case UNDO_COMMIT:
- if (isSpace || isSeparator) {
- setState(START);
- } else {
- setState(IN_WORD);
- }
- break;
- case RECORRECTING:
- setState(START);
- break;
- }
- RingCharBuffer.getInstance().push(c, x, y);
- if (isSeparator) {
- LatinImeLogger.logOnInputSeparator();
- } else {
- LatinImeLogger.logOnInputChar();
- }
- if (DEBUG) displayState("typedCharacter", "char", c, "isSeparator", isSeparator);
- }
-
- public static void backspace() {
- if (sState == ACCEPTED_DEFAULT) {
- setState(UNDO_COMMIT);
- LatinImeLogger.logOnAutoCorrectionCancelled();
- } else if (sState == UNDO_COMMIT) {
- setState(IN_WORD);
- }
- if (DEBUG) displayState("backspace");
- }
-
- public static void reset() {
- setState(START);
- if (DEBUG) displayState("reset");
- }
-
- public static boolean isAcceptedDefault() {
- return sState == ACCEPTED_DEFAULT;
- }
-
- public static boolean isSpaceAfterPicked() {
- return sState == SPACE_AFTER_PICKED;
- }
-
- public static boolean isUndoCommit() {
- return sState == UNDO_COMMIT;
- }
-
- public static boolean isPunctuationAfterAccepted() {
- return sState == PUNCTUATION_AFTER_ACCEPTED;
- }
-
- public static boolean isRecorrecting() {
- return sState == RECORRECTING || sState == PICKED_RECORRECTION;
- }
-
- public static String getState() {
- return stateName(sState);
- }
-
- private static String stateName(int state) {
- switch (state) {
- case START: return "START";
- case IN_WORD: return "IN_WORD";
- case ACCEPTED_DEFAULT: return "ACCEPTED_DEFAULT";
- case PICKED_SUGGESTION: return "PICKED_SUGGESTION";
- case PUNCTUATION_AFTER_WORD: return "PUNCTUATION_AFTER_WORD";
- case PUNCTUATION_AFTER_ACCEPTED: return "PUNCTUATION_AFTER_ACCEPTED";
- case SPACE_AFTER_ACCEPTED: return "SPACE_AFTER_ACCEPTED";
- case SPACE_AFTER_PICKED: return "SPACE_AFTER_PICKED";
- case UNDO_COMMIT: return "UNDO_COMMIT";
- case RECORRECTING: return "RECORRECTING";
- case PICKED_RECORRECTION: return "PICKED_RECORRECTION";
- default: return "UNKNOWN";
- }
- }
-
- private static void displayState(String title, Object ... args) {
- final StringBuilder sb = new StringBuilder(title);
- sb.append(':');
- for (int i = 0; i < args.length; i += 2) {
- sb.append(' ');
- sb.append(args[i]);
- sb.append('=');
- sb.append(args[i+1].toString());
- }
- sb.append(" state=");
- sb.append(stateName(sState));
- sb.append(" previous=");
- sb.append(stateName(sPreviousState));
- Log.d(TAG, sb.toString());
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
index 9e656675e..e6a59d0ab 100644
--- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java
@@ -159,7 +159,7 @@ public class UserBigramDictionary extends ExpandableDictionary {
*/
public int addBigrams(String word1, String word2) {
// remove caps if second word is autocapitalized
- if (mIme != null && mIme.getCurrentWord().isAutoCapitalized()) {
+ if (mIme != null && mIme.isAutoCapitalized()) {
word2 = Character.toLowerCase(word2.charAt(0)) + word2.substring(1);
}
// Do not insert a word as a bigram of itself
@@ -212,7 +212,8 @@ public class UserBigramDictionary extends ExpandableDictionary {
@Override
public void loadDictionaryAsync() {
// Load the words that correspond to the current input locale
- Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale });
+ final Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale });
+ if (null == cursor) return;
try {
if (cursor.moveToFirst()) {
int word1Index = cursor.getColumnIndex(MAIN_COLUMN_WORD1);
@@ -238,7 +239,7 @@ public class UserBigramDictionary extends ExpandableDictionary {
/**
* Query the database
*/
- private Cursor query(String selection, String[] selectionArgs) {
+ private static Cursor query(String selection, String[] selectionArgs) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
// main INNER JOIN frequency ON (main._id=freq.pair_id)
@@ -249,11 +250,17 @@ public class UserBigramDictionary extends ExpandableDictionary {
qb.setProjectionMap(sDictProjectionMap);
// Get the database and run the query
- SQLiteDatabase db = sOpenHelper.getReadableDatabase();
- Cursor c = qb.query(db,
- new String[] { MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD2, FREQ_COLUMN_FREQUENCY },
- selection, selectionArgs, null, null, null);
- return c;
+ try {
+ SQLiteDatabase db = sOpenHelper.getReadableDatabase();
+ Cursor c = qb.query(db,
+ new String[] { MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD2, FREQ_COLUMN_FREQUENCY },
+ selection, selectionArgs, null, null, null);
+ return c;
+ } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
+ // Can't open the database : presumably we can't access storage. That may happen
+ // when the device is wedged; do a best effort to still start the keyboard.
+ return null;
+ }
}
/**
@@ -310,7 +317,7 @@ public class UserBigramDictionary extends ExpandableDictionary {
}
/** Prune any old data if the database is getting too big. */
- private void checkPruneData(SQLiteDatabase db) {
+ private static void checkPruneData(SQLiteDatabase db) {
db.execSQL("PRAGMA foreign_keys = ON;");
Cursor c = db.query(FREQ_TABLE_NAME, new String[] { FREQ_COLUMN_PAIR_ID },
null, null, null, null, null);
@@ -344,7 +351,18 @@ public class UserBigramDictionary extends ExpandableDictionary {
@Override
protected Void doInBackground(Void... v) {
- SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ SQLiteDatabase db = null;
+ try {
+ db = mDbHelper.getWritableDatabase();
+ } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
+ // If we can't open the db, don't do anything. Exit through the next test
+ // for non-nullity of the db variable.
+ }
+ if (null == db) {
+ // Not much we can do. Just exit.
+ sUpdatingDB = false;
+ return null;
+ }
db.execSQL("PRAGMA foreign_keys = ON;");
// Write all the entries to the db
Iterator<Bigram> iterator = mMap.iterator();
@@ -380,7 +398,7 @@ public class UserBigramDictionary extends ExpandableDictionary {
return null;
}
- private ContentValues getContentValues(String word1, String word2, String locale) {
+ private static ContentValues getContentValues(String word1, String word2, String locale) {
ContentValues values = new ContentValues(3);
values.put(MAIN_COLUMN_WORD1, word1);
values.put(MAIN_COLUMN_WORD2, word2);
@@ -388,7 +406,7 @@ public class UserBigramDictionary extends ExpandableDictionary {
return values;
}
- private ContentValues getFrequencyContentValues(int pairId, int frequency) {
+ private static ContentValues getFrequencyContentValues(int pairId, int frequency) {
ContentValues values = new ContentValues(2);
values.put(FREQ_COLUMN_PAIR_ID, pairId);
values.put(FREQ_COLUMN_FREQUENCY, frequency);
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index 0bbbf3995..51b993343 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -18,12 +18,10 @@ package com.android.inputmethod.latin;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
-import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
import android.database.ContentObserver;
import android.database.Cursor;
-import android.net.Uri;
-import android.os.RemoteException;
import android.provider.UserDictionary.Words;
import android.text.TextUtils;
@@ -38,11 +36,9 @@ public class UserDictionary extends ExpandableDictionary {
Words.FREQUENCY,
};
- private static final String[] PROJECTION_ADD = {
- Words._ID,
- Words.FREQUENCY,
- Words.LOCALE,
- };
+ // 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;
@@ -134,7 +130,11 @@ public class UserDictionary extends ExpandableDictionary {
final Cursor cursor = getContext().getContentResolver()
.query(Words.CONTENT_URI, PROJECTION_QUERY, request.toString(),
requestArguments, null);
- addWords(cursor);
+ try {
+ addWords(cursor);
+ } finally {
+ if (null != cursor) cursor.close();
+ }
}
public boolean isEnabled() {
@@ -160,57 +160,17 @@ public class UserDictionary extends ExpandableDictionary {
public synchronized void addWord(final String word, final int frequency) {
// Force load the dictionary here synchronously
if (getRequiresReload()) loadDictionaryAsync();
+ // 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() >= getMaxWordLength()) return;
- super.addWord(word, frequency);
-
- // Update the user dictionary provider
- final ContentValues values = new ContentValues(5);
- values.put(Words.WORD, word);
- values.put(Words.FREQUENCY, frequency);
- values.put(Words.LOCALE, mLocale);
- values.put(Words.APP_ID, 0);
-
- final ContentResolver contentResolver = getContext().getContentResolver();
- final ContentProviderClient client =
- contentResolver.acquireContentProviderClient(Words.CONTENT_URI);
- if (null == client) return;
- new Thread("addWord") {
- @Override
- public void run() {
- Cursor cursor = null;
- try {
- cursor = client.query(Words.CONTENT_URI, PROJECTION_ADD,
- "word=? and ((locale IS NULL) or (locale=?))",
- new String[] { word, mLocale }, null);
- if (cursor != null && cursor.moveToFirst()) {
- final String locale = cursor.getString(cursor.getColumnIndex(Words.LOCALE));
- // If locale is null, we will not override the entry.
- if (locale != null && locale.equals(mLocale.toString())) {
- final long id = cursor.getLong(cursor.getColumnIndex(Words._ID));
- final Uri uri =
- Uri.withAppendedPath(Words.CONTENT_URI, Long.toString(id));
- // Update the entry with new frequency value.
- client.update(uri, values, null, null);
- }
- } else {
- // Insert new entry.
- client.insert(Words.CONTENT_URI, values);
- }
- } catch (RemoteException e) {
- // If we come here, the activity is already about to be killed, and we
- // have no means of contacting the content provider any more.
- // See ContentResolver#insert, inside the catch(){}
- } finally {
- if (null != cursor) cursor.close();
- client.release();
- }
- }
- }.start();
-
- // In case the above does a synchronous callback of the change observer
- setRequiresReload(false);
+ // TODO: Add an argument to the intent to specify the frequency.
+ 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);
+ getContext().startActivity(intent);
}
@Override
@@ -242,6 +202,5 @@ public class UserDictionary extends ExpandableDictionary {
cursor.moveToNext();
}
}
- cursor.close();
}
}
diff --git a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
index e41230b3c..869865d7b 100644
--- a/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserUnigramDictionary.java
@@ -121,7 +121,8 @@ public class UserUnigramDictionary extends ExpandableDictionary {
public void loadDictionaryAsync() {
if (!ENABLE_USER_UNIGRAM_DICTIONARY) return;
// Load the words that correspond to the current input locale
- Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale });
+ final Cursor cursor = query(COLUMN_LOCALE + "=?", new String[] { mLocale });
+ if (null == cursor) return;
try {
if (cursor.moveToFirst()) {
int wordIndex = cursor.getColumnIndex(COLUMN_WORD);
@@ -149,7 +150,7 @@ public class UserUnigramDictionary extends ExpandableDictionary {
final int length = word.length();
// Don't add very short or very long words.
if (length < 2 || length > getMaxWordLength()) return;
- if (mIme.getCurrentWord().isAutoCapitalized()) {
+ if (mIme.isAutoCapitalized()) {
// Remove caps before adding
word = Character.toLowerCase(word.charAt(0)) + word.substring(1);
}
@@ -172,7 +173,7 @@ public class UserUnigramDictionary extends ExpandableDictionary {
// Nothing pending? Return
if (mPendingWrites.isEmpty()) return;
// Create a background thread to write the pending entries
- new UpdateDbTask(getContext(), sOpenHelper, mPendingWrites, mLocale).execute();
+ new UpdateDbTask(sOpenHelper, mPendingWrites, mLocale).execute();
// Create a new map for writing new entries into while the old one is written to db
mPendingWrites = new HashMap<String, Integer>();
}
@@ -206,16 +207,22 @@ public class UserUnigramDictionary extends ExpandableDictionary {
}
}
- private Cursor query(String selection, String[] selectionArgs) {
+ private static Cursor query(String selection, String[] selectionArgs) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(USER_UNIGRAM_DICT_TABLE_NAME);
qb.setProjectionMap(sDictProjectionMap);
// Get the database and run the query
- SQLiteDatabase db = sOpenHelper.getReadableDatabase();
- Cursor c = qb.query(db, null, selection, selectionArgs, null, null,
- DEFAULT_SORT_ORDER);
- return c;
+ try {
+ SQLiteDatabase db = sOpenHelper.getReadableDatabase();
+ Cursor c = qb.query(db, null, selection, selectionArgs, null, null,
+ DEFAULT_SORT_ORDER);
+ return c;
+ } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
+ // Can't open the database : presumably we can't access storage. That may happen
+ // when the device is wedged; do a best effort to still start the keyboard.
+ return null;
+ }
}
/**
@@ -227,8 +234,8 @@ public class UserUnigramDictionary extends ExpandableDictionary {
private final DatabaseHelper mDbHelper;
private final String mLocale;
- public UpdateDbTask(@SuppressWarnings("unused") Context context, DatabaseHelper openHelper,
- HashMap<String, Integer> pendingWrites, String locale) {
+ public UpdateDbTask(DatabaseHelper openHelper, HashMap<String, Integer> pendingWrites,
+ String locale) {
mMap = pendingWrites;
mLocale = locale;
mDbHelper = openHelper;
@@ -236,7 +243,14 @@ public class UserUnigramDictionary extends ExpandableDictionary {
@Override
protected Void doInBackground(Void... v) {
- SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ SQLiteDatabase db = null;
+ try {
+ db = mDbHelper.getWritableDatabase();
+ } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) {
+ // With no access to the DB, this is moot. Do nothing: we'll exit through the
+ // test for null == db.
+ }
+ if (null == db) return null;
// Write all the entries to the db
Set<Entry<String,Integer>> mEntries = mMap.entrySet();
for (Entry<String,Integer> entry : mEntries) {
@@ -251,7 +265,7 @@ public class UserUnigramDictionary extends ExpandableDictionary {
return null;
}
- private ContentValues getContentValues(String word, int frequency, String locale) {
+ private static ContentValues getContentValues(String word, int frequency, String locale) {
ContentValues values = new ContentValues(4);
values.put(COLUMN_WORD, word);
values.put(COLUMN_FREQUENCY, frequency);
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index b29ff1975..a7de47c58 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -17,48 +17,36 @@
package com.android.inputmethod.latin;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
+import android.content.Intent;
+import android.content.pm.PackageManager;
import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
import android.os.AsyncTask;
-import android.os.Build;
+import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
-import android.text.InputType;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Log;
-import android.view.inputmethod.EditorInfo;
-import com.android.inputmethod.compat.InputMethodInfoCompatWrapper;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
-import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper;
-import com.android.inputmethod.compat.InputTypeCompatUtils;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
+import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
-import java.util.ArrayList;
import java.util.Date;
-import java.util.List;
-import java.util.Locale;
public class Utils {
- private static final String TAG = Utils.class.getSimpleName();
- private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
- private static boolean DBG = LatinImeLogger.sDBG;
- private static boolean DBG_EDIT_DISTANCE = false;
-
private Utils() {
- // Intentional empty constructor for utility class.
+ // This utility class is not publicly instantiable.
}
/**
@@ -112,108 +100,6 @@ public class Utils {
}
}
- public static boolean hasMultipleEnabledIMEsOrSubtypes(
- final InputMethodManagerCompatWrapper imm,
- final boolean shouldIncludeAuxiliarySubtypes) {
- final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList();
-
- // Number of the filtered IMEs
- int filteredImisCount = 0;
-
- for (InputMethodInfoCompatWrapper imi : enabledImis) {
- // We can return true immediately after we find two or more filtered IMEs.
- if (filteredImisCount > 1) return true;
- final List<InputMethodSubtypeCompatWrapper> subtypes =
- imm.getEnabledInputMethodSubtypeList(imi, true);
- // IMEs that have no subtypes should be counted.
- if (subtypes.isEmpty()) {
- ++filteredImisCount;
- continue;
- }
-
- int auxCount = 0;
- for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
- if (subtype.isAuxiliary()) {
- ++auxCount;
- }
- }
- final int nonAuxCount = subtypes.size() - auxCount;
-
- // IMEs that have one or more non-auxiliary subtypes should be counted.
- // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
- // subtypes should be counted as well.
- if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
- ++filteredImisCount;
- continue;
- }
- }
-
- return filteredImisCount > 1
- // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled
- // input method subtype (The current IME should be LatinIME.)
- || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
- }
-
- public static String getInputMethodId(InputMethodManagerCompatWrapper imm, String packageName) {
- return getInputMethodInfo(imm, packageName).getId();
- }
-
- public static InputMethodInfoCompatWrapper getInputMethodInfo(
- InputMethodManagerCompatWrapper imm, String packageName) {
- for (final InputMethodInfoCompatWrapper imi : imm.getEnabledInputMethodList()) {
- if (imi.getPackageName().equals(packageName))
- return imi;
- }
- throw new RuntimeException("Can not find input method id for " + packageName);
- }
-
- // TODO: Resolve the inconsistencies between the native auto correction algorithms and
- // this safety net
- public static boolean shouldBlockAutoCorrectionBySafetyNet(SuggestedWords suggestions,
- Suggest suggest) {
- // Safety net for auto correction.
- // Actually if we hit this safety net, it's actually a bug.
- if (suggestions.size() <= 1 || suggestions.mTypedWordValid) return false;
- // If user selected aggressive auto correction mode, there is no need to use the safety
- // net.
- if (suggest.isAggressiveAutoCorrectionMode()) return false;
- final CharSequence typedWord = suggestions.getWord(0);
- // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
- // we should not use net because relatively edit distance can be big.
- if (typedWord.length() < MINIMUM_SAFETY_NET_CHAR_LENGTH) return false;
- final CharSequence suggestionWord = suggestions.getWord(1);
- final int typedWordLength = typedWord.length();
- final int maxEditDistanceOfNativeDictionary =
- (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
- final int distance = Utils.editDistance(typedWord, suggestionWord);
- if (DBG) {
- Log.d(TAG, "Autocorrected edit distance = " + distance
- + ", " + maxEditDistanceOfNativeDictionary);
- }
- if (distance > maxEditDistanceOfNativeDictionary) {
- if (DBG) {
- Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestionWord);
- Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
- + "Turning off auto-correction.");
- }
- return true;
- } else {
- return false;
- }
- }
-
- public static boolean canBeFollowedByPeriod(final int codePoint) {
- // TODO: Check again whether there really ain't a better way to check this.
- // TODO: This should probably be language-dependant...
- return Character.isLetterOrDigit(codePoint)
- || codePoint == Keyboard.CODE_SINGLE_QUOTE
- || codePoint == Keyboard.CODE_DOUBLE_QUOTE
- || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
- || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
- || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
- || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
- }
-
/* package */ static class RingCharBuffer {
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
@@ -242,10 +128,11 @@ public class Utils {
UsabilityStudyLogUtils.getInstance().init(context);
return sRingCharBuffer;
}
- private int normalize(int in) {
+ private static int normalize(int in) {
int ret = in % BUFSIZE;
return ret < 0 ? ret + BUFSIZE : ret;
}
+ // TODO: accept code points
public void push(char c, int x, int y) {
if (!mEnabled) return;
if (mUsabilityStudy) {
@@ -317,49 +204,6 @@ public class Utils {
}
}
-
- /* Damerau-Levenshtein distance */
- public static int editDistance(CharSequence s, CharSequence t) {
- if (s == null || t == null) {
- throw new IllegalArgumentException("editDistance: Arguments should not be null.");
- }
- final int sl = s.length();
- final int tl = t.length();
- int[][] dp = new int [sl + 1][tl + 1];
- for (int i = 0; i <= sl; i++) {
- dp[i][0] = i;
- }
- for (int j = 0; j <= tl; j++) {
- dp[0][j] = j;
- }
- for (int i = 0; i < sl; ++i) {
- for (int j = 0; j < tl; ++j) {
- final char sc = Character.toLowerCase(s.charAt(i));
- final char tc = Character.toLowerCase(t.charAt(j));
- final int cost = sc == tc ? 0 : 1;
- dp[i + 1][j + 1] = Math.min(
- dp[i][j + 1] + 1, Math.min(dp[i + 1][j] + 1, dp[i][j] + cost));
- // Overwrite for transposition cases
- if (i > 0 && j > 0
- && sc == Character.toLowerCase(t.charAt(j - 1))
- && tc == Character.toLowerCase(s.charAt(i - 1))) {
- dp[i + 1][j + 1] = Math.min(dp[i + 1][j + 1], dp[i - 1][j - 1] + cost);
- }
- }
- }
- if (DBG_EDIT_DISTANCE) {
- Log.d(TAG, "editDistance:" + s + "," + t);
- for (int i = 0; i < dp.length; ++i) {
- StringBuffer sb = new StringBuffer();
- for (int j = 0; j < dp[i].length; ++j) {
- sb.append(dp[i][j]).append(',');
- }
- Log.d(TAG, i + ":" + sb.toString());
- }
- }
- return dp[sl][tl];
- }
-
// Get the current stack trace
public static String getStackTrace() {
StringBuilder sb = new StringBuilder();
@@ -373,55 +217,6 @@ public class Utils {
return sb.toString();
}
- // In dictionary.cpp, getSuggestion() method,
- // suggestion scores are computed using the below formula.
- // original score
- // := pow(mTypedLetterMultiplier (this is defined 2),
- // (the number of matched characters between typed word and suggested word))
- // * (individual word's score which defined in the unigram dictionary,
- // and this score is defined in range [0, 255].)
- // Then, the following processing is applied.
- // - If the dictionary word is matched up to the point of the user entry
- // (full match up to min(before.length(), after.length())
- // => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2)
- // - If the word is a true full match except for differences in accents or
- // capitalization, then treat it as if the score was 255.
- // - If before.length() == after.length()
- // => multiply by mFullWordMultiplier (this is defined 2))
- // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
- // For historical reasons we ignore the 1.2 modifier (because the measure for a good
- // autocorrection threshold was done at a time when it didn't exist). This doesn't change
- // the result.
- // So, we can normalize original score by dividing pow(2, min(b.l(),a.l())) * 255 * 2.
- private static final int MAX_INITIAL_SCORE = 255;
- private static final int TYPED_LETTER_MULTIPLIER = 2;
- private static final int FULL_WORD_MULTIPLIER = 2;
- private static final int S_INT_MAX = 2147483647;
- public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) {
- final int beforeLength = before.length();
- final int afterLength = after.length();
- if (beforeLength == 0 || afterLength == 0) return 0;
- final int distance = editDistance(before, after);
- // If afterLength < beforeLength, the algorithm is suggesting a word by excessive character
- // correction.
- int spaceCount = 0;
- for (int i = 0; i < afterLength; ++i) {
- if (after.charAt(i) == Keyboard.CODE_SPACE) {
- ++spaceCount;
- }
- }
- if (spaceCount == afterLength) return 0;
- final double maximumScore = score == S_INT_MAX ? S_INT_MAX : MAX_INITIAL_SCORE
- * Math.pow(
- TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength - spaceCount))
- * FULL_WORD_MULTIPLIER;
- // add a weight based on edit distance.
- // distance <= max(afterLength, beforeLength) == afterLength,
- // so, 0 <= distance / afterLength <= 1
- final double weight = 1.0 - (double) distance / afterLength;
- return (score / maximumScore) * weight;
- }
-
public static class UsabilityStudyLogUtils {
private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
private static final String FILENAME = "log.txt";
@@ -437,7 +232,7 @@ public class Utils {
private UsabilityStudyLogUtils() {
mDate = new Date();
- mDateFormat = new SimpleDateFormat("dd MMM HH:mm:ss.SSS");
+ mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ");
HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
Process.THREAD_PRIORITY_BACKGROUND);
@@ -465,7 +260,7 @@ public class Utils {
}
}
- public void writeBackSpace() {
+ public static void writeBackSpace() {
UsabilityStudyLogUtils.getInstance().write("<backspace>\t0\t0");
}
@@ -504,32 +299,89 @@ public class Utils {
});
}
- public void printAll() {
+ private synchronized String getBufferedLogs() {
+ mWriter.flush();
+ StringBuilder sb = new StringBuilder();
+ BufferedReader br = getBufferedReader();
+ String line;
+ try {
+ while ((line = br.readLine()) != null) {
+ sb.append('\n');
+ sb.append(line);
+ }
+ } catch (IOException e) {
+ Log.e(USABILITY_TAG, "Can't read log file.");
+ } finally {
+ if (LatinImeLogger.sDBG) {
+ Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
+ }
+ try {
+ br.close();
+ } catch (IOException e) {
+ // ignore.
+ }
+ }
+ return sb.toString();
+ }
+
+ public void emailResearcherLogsAll() {
mLoggingHandler.post(new Runnable() {
@Override
public void run() {
+ final Date date = new Date();
+ date.setTime(System.currentTimeMillis());
+ final String currentDateTimeString =
+ new SimpleDateFormat("yyyyMMdd-HHmmssZ").format(date);
+ if (mFile == null) {
+ Log.w(USABILITY_TAG, "No internal log file found.");
+ return;
+ }
+ if (mIms.checkCallingOrSelfPermission(
+ android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
+ return;
+ }
mWriter.flush();
- StringBuilder sb = new StringBuilder();
- BufferedReader br = getBufferedReader();
- String line;
+ final String destPath = Environment.getExternalStorageDirectory()
+ + "/research-" + currentDateTimeString + ".log";
+ final File destFile = new File(destPath);
try {
- while ((line = br.readLine()) != null) {
- sb.append('\n');
- sb.append(line);
- }
- } catch (IOException e) {
- Log.e(USABILITY_TAG, "Can't read log file.");
- } finally {
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "output all logs\n" + sb.toString());
- }
- mIms.getCurrentInputConnection().commitText(sb.toString(), 0);
- try {
- br.close();
- } catch (IOException e) {
- // ignore.
- }
+ final FileChannel src = (new FileInputStream(mFile)).getChannel();
+ final FileChannel dest = (new FileOutputStream(destFile)).getChannel();
+ src.transferTo(0, src.size(), dest);
+ src.close();
+ dest.close();
+ } catch (FileNotFoundException e1) {
+ Log.w(USABILITY_TAG, e1);
+ return;
+ } catch (IOException e2) {
+ Log.w(USABILITY_TAG, e2);
+ return;
}
+ if (destFile == null || !destFile.exists()) {
+ Log.w(USABILITY_TAG, "Dest file doesn't exist.");
+ return;
+ }
+ final Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (LatinImeLogger.sDBG) {
+ Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
+ }
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
+ intent.putExtra(Intent.EXTRA_SUBJECT,
+ "[Research Logs] " + currentDateTimeString);
+ mIms.startActivity(intent);
+ }
+ });
+ }
+
+ public void printAll() {
+ mLoggingHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
}
});
}
@@ -570,134 +422,6 @@ public class Utils {
}
}
- public static int getKeyboardMode(EditorInfo editorInfo) {
- if (editorInfo == null)
- return KeyboardId.MODE_TEXT;
-
- final int inputType = editorInfo.inputType;
- final int variation = inputType & InputType.TYPE_MASK_VARIATION;
-
- switch (inputType & InputType.TYPE_MASK_CLASS) {
- case InputType.TYPE_CLASS_NUMBER:
- case InputType.TYPE_CLASS_DATETIME:
- return KeyboardId.MODE_NUMBER;
- case InputType.TYPE_CLASS_PHONE:
- return KeyboardId.MODE_PHONE;
- case InputType.TYPE_CLASS_TEXT:
- if (InputTypeCompatUtils.isEmailVariation(variation)) {
- return KeyboardId.MODE_EMAIL;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
- return KeyboardId.MODE_URL;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
- return KeyboardId.MODE_IM;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
- return KeyboardId.MODE_TEXT;
- } else {
- return KeyboardId.MODE_TEXT;
- }
- default:
- return KeyboardId.MODE_TEXT;
- }
- }
-
- public static boolean containsInCsv(String key, String csv) {
- if (csv == null)
- return false;
- for (String option : csv.split(",")) {
- if (option.equals(key))
- return true;
- }
- return false;
- }
-
- public static boolean inPrivateImeOptions(String packageName, String key,
- EditorInfo editorInfo) {
- if (editorInfo == null)
- return false;
- return containsInCsv(packageName != null ? packageName + "." + key : key,
- editorInfo.privateImeOptions);
- }
-
- /**
- * Returns a main dictionary resource id
- * @return main dictionary resource id
- */
- public static int getMainDictionaryResourceId(Resources res) {
- final String MAIN_DIC_NAME = "main";
- String packageName = LatinIME.class.getPackage().getName();
- return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
- }
-
- public static void loadNativeLibrary() {
- try {
- System.loadLibrary("jni_latinime");
- } catch (UnsatisfiedLinkError ule) {
- Log.e(TAG, "Could not load native library jni_latinime");
- }
- }
-
- /**
- * Returns true if a and b are equal ignoring the case of the character.
- * @param a first character to check
- * @param b second character to check
- * @return {@code true} if a and b are equal, {@code false} otherwise.
- */
- public static boolean equalsIgnoreCase(char a, char b) {
- // Some language, such as Turkish, need testing both cases.
- return a == b
- || Character.toLowerCase(a) == Character.toLowerCase(b)
- || Character.toUpperCase(a) == Character.toUpperCase(b);
- }
-
- /**
- * Returns true if a and b are equal ignoring the case of the characters, including if they are
- * both null.
- * @param a first CharSequence to check
- * @param b second CharSequence to check
- * @return {@code true} if a and b are equal, {@code false} otherwise.
- */
- public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) {
- if (a == b)
- return true; // including both a and b are null.
- if (a == null || b == null)
- return false;
- final int length = a.length();
- if (length != b.length())
- return false;
- for (int i = 0; i < length; i++) {
- if (!equalsIgnoreCase(a.charAt(i), b.charAt(i)))
- return false;
- }
- return true;
- }
-
- /**
- * Returns true if a and b are equal ignoring the case of the characters, including if a is null
- * and b is zero length.
- * @param a CharSequence to check
- * @param b character array to check
- * @param offset start offset of array b
- * @param length length of characters in array b
- * @return {@code true} if a and b are equal, {@code false} otherwise.
- * @throws IndexOutOfBoundsException
- * if {@code offset < 0 || length < 0 || offset + length > data.length}.
- * @throws NullPointerException if {@code b == null}.
- */
- public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) {
- if (offset < 0 || length < 0 || length > b.length - offset)
- throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset
- + " length=" + length);
- if (a == null)
- return length == 0; // including a is null and b is zero length.
- if (a.length() != length)
- return false;
- for (int i = 0; i < length; i++) {
- if (!equalsIgnoreCase(a.charAt(i), b[offset + i]))
- return false;
- }
- return true;
- }
-
public static float getDipScale(Context context) {
final float scale = context.getResources().getDisplayMetrics().density;
return scale;
@@ -708,110 +432,37 @@ public class Utils {
return (int) (dip * scale + 0.5);
}
- /**
- * Remove duplicates from an array of strings.
- *
- * This method will always keep the first occurence of all strings at their position
- * in the array, removing the subsequent ones.
- */
- public static void removeDupes(final ArrayList<CharSequence> suggestions) {
- if (suggestions.size() < 2) return;
- int i = 1;
- // Don't cache suggestions.size(), since we may be removing items
- while (i < suggestions.size()) {
- final CharSequence cur = suggestions.get(i);
- // Compare each suggestion with each previous suggestion
- for (int j = 0; j < i; j++) {
- CharSequence previous = suggestions.get(j);
- if (TextUtils.equals(cur, previous)) {
- removeFromSuggestions(suggestions, i);
- i--;
- break;
- }
- }
- i++;
- }
- }
-
- private static void removeFromSuggestions(final ArrayList<CharSequence> suggestions,
- final int index) {
- final CharSequence garbage = suggestions.remove(index);
- if (garbage instanceof StringBuilder) {
- StringBuilderPool.recycle((StringBuilder)garbage);
+ public static class Stats {
+ public static void onNonSeparator(final char code, final int x,
+ final int y) {
+ RingCharBuffer.getInstance().push(code, x, y);
+ LatinImeLogger.logOnInputChar();
}
- }
- public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) {
- if (returnsNameInThisLocale) {
- return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
- } else {
- return toTitleCase(locale.getDisplayName(), locale);
+ public static void onSeparator(final int code, final int x,
+ final int y) {
+ // TODO: accept code points
+ RingCharBuffer.getInstance().push((char)code, x, y);
+ LatinImeLogger.logOnInputSeparator();
}
- }
-
- public static String getDisplayLanguage(Locale locale) {
- return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale);
- }
-
- public static String getMiddleDisplayLanguage(Locale locale) {
- return toTitleCase((LocaleUtils.constructLocaleFromString(
- locale.getLanguage()).getDisplayLanguage(locale)), locale);
- }
-
- public static String getShortDisplayLanguage(Locale locale) {
- return toTitleCase(locale.getLanguage(), locale);
- }
- public static String toTitleCase(String s, Locale locale) {
- if (s.length() <= 1) {
- // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
- return s;
- }
- // TODO: fix the bugs below
- // - This does not work for Greek, because it returns upper case instead of title case.
- // - It does not work for Serbian, because it fails to account for the "lj" character,
- // which should be "Lj" in title case and "LJ" in upper case.
- // - It does not work for Dutch, because it fails to account for the "ij" digraph, which
- // are two different characters but both should be capitalized as "IJ" as if they were
- // a single letter.
- // - It also does not work with unicode surrogate code points.
- return s.toUpperCase(locale).charAt(0) + s.substring(1);
- }
-
- public static int getCurrentVibrationDuration(SharedPreferences sp, Resources res) {
- final int ms = sp.getInt(Settings.PREF_KEYPRESS_VIBRATION_DURATION_SETTINGS, -1);
- if (ms >= 0) {
- return ms;
- }
- final String[] durationPerHardwareList = res.getStringArray(
- R.array.keypress_vibration_durations);
- final String hardwarePrefix = Build.HARDWARE + ",";
- for (final String element : durationPerHardwareList) {
- if (element.startsWith(hardwarePrefix)) {
- return (int)Long.parseLong(element.substring(element.lastIndexOf(',') + 1));
- }
+ public static void onAutoCorrection(final String typedWord, final String correctedWord,
+ final int separatorCode) {
+ if (TextUtils.isEmpty(typedWord)) return;
+ LatinImeLogger.logOnAutoCorrection(typedWord, correctedWord, separatorCode);
}
- return -1;
- }
- public static float getCurrentKeypressSoundVolume(SharedPreferences sp, Resources res) {
- final float volume = sp.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f);
- if (volume >= 0) {
- return volume;
- }
-
- final String[] volumePerHardwareList = res.getStringArray(R.array.keypress_volumes);
- final String hardwarePrefix = Build.HARDWARE + ",";
- for (final String element : volumePerHardwareList) {
- if (element.startsWith(hardwarePrefix)) {
- return Float.parseFloat(element.substring(element.lastIndexOf(',') + 1));
- }
+ public static void onAutoCorrectionCancellation() {
+ LatinImeLogger.logOnAutoCorrectionCancelled();
}
- return -1.0f;
}
- public static boolean willAutoCorrect(SuggestedWords suggestions) {
- return !suggestions.mTypedWordValid && suggestions.mHasAutoCorrectionCandidate
- && !suggestions.shouldBlockAutoCorrection();
+ public static String getDebugInfo(final SuggestedWords suggestions, final int pos) {
+ if (!LatinImeLogger.sDBG) return null;
+ final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
+ if (wordInfo == null) return null;
+ final String info = wordInfo.getDebugString();
+ if (TextUtils.isEmpty(info)) return null;
+ return info;
}
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index adc5637f6..126ac47e7 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2008 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
@@ -16,9 +16,15 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardActionListener;
+import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService;
+import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
import java.util.ArrayList;
+import java.util.Arrays;
/**
* A place to store the currently composing word with information such as adjacent key codes as well
@@ -28,31 +34,31 @@ public class WordComposer {
public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
public static final int NOT_A_COORDINATE = -1;
- /**
- * The list of unicode values for each keystroke (including surrounding keys)
- */
- private ArrayList<int[]> mCodes;
+ final static int N = BinaryDictionary.MAX_WORD_LENGTH;
+ private ArrayList<int[]> mCodes;
private int[] mXCoordinates;
private int[] mYCoordinates;
-
private StringBuilder mTypedWord;
+ private CharSequence mAutoCorrection;
+ // Cache these values for performance
private int mCapsCount;
-
private boolean mAutoCapitalized;
-
+ private int mTrailingSingleQuotesCount;
+
/**
* Whether the user chose to capitalize the first char of the word.
*/
private boolean mIsFirstCharCapitalized;
public WordComposer() {
- final int N = BinaryDictionary.MAX_WORD_LENGTH;
mCodes = new ArrayList<int[]>(N);
mTypedWord = new StringBuilder(N);
mXCoordinates = new int[N];
mYCoordinates = new int[N];
+ mAutoCorrection = null;
+ mTrailingSingleQuotesCount = 0;
}
public WordComposer(WordComposer source) {
@@ -62,11 +68,12 @@ public class WordComposer {
public void init(WordComposer source) {
mCodes = new ArrayList<int[]>(source.mCodes);
mTypedWord = new StringBuilder(source.mTypedWord);
- mXCoordinates = source.mXCoordinates;
- mYCoordinates = source.mYCoordinates;
+ mXCoordinates = Arrays.copyOf(source.mXCoordinates, source.mXCoordinates.length);
+ mYCoordinates = Arrays.copyOf(source.mYCoordinates, source.mYCoordinates.length);
mCapsCount = source.mCapsCount;
mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
mAutoCapitalized = source.mAutoCapitalized;
+ mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
}
/**
@@ -75,8 +82,10 @@ public class WordComposer {
public void reset() {
mCodes.clear();
mTypedWord.setLength(0);
+ mAutoCorrection = null;
mCapsCount = 0;
mIsFirstCharCapitalized = false;
+ mTrailingSingleQuotesCount = 0;
}
/**
@@ -84,7 +93,11 @@ public class WordComposer {
* @return the number of keystrokes
*/
public final int size() {
- return mTypedWord.length();
+ return mCodes.size();
+ }
+
+ public final boolean isComposingWord() {
+ return mCodes.size() > 0;
}
/**
@@ -109,23 +122,120 @@ public class WordComposer {
return previous && !Character.isUpperCase(codePoint);
}
+ // TODO: remove input keyDetector
+ public void add(int primaryCode, int x, int y, KeyDetector keyDetector) {
+ final int[] codes;
+ final int keyX;
+ final int keyY;
+ if (x == KeyboardActionListener.SPELL_CHECKER_COORDINATE
+ || y == KeyboardActionListener.SPELL_CHECKER_COORDINATE) {
+ // only used for tests in InputLogicTests
+ addKeyForSpellChecker(primaryCode, AndroidSpellCheckerService.SCRIPT_LATIN);
+ return;
+ } else if (x == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
+ || y == KeyboardActionListener.SUGGESTION_STRIP_COORDINATE
+ || x == KeyboardActionListener.NOT_A_TOUCH_COORDINATE
+ || y == KeyboardActionListener.NOT_A_TOUCH_COORDINATE) {
+ codes = new int[] { primaryCode };
+ keyX = x;
+ keyY = y;
+ } else {
+ codes = keyDetector.newCodeArray();
+ keyDetector.getNearbyCodes(x, y, codes);
+ keyX = keyDetector.getTouchX(x);
+ keyY = keyDetector.getTouchY(y);
+ }
+ add(primaryCode, codes, keyX, keyY);
+ }
+
+ // TODO: remove this function
+ public void addKeyForSpellChecker(int primaryCode, int script) {
+ final int[] proximities;
+ final int proximityIndex =
+ SpellCheckerProximityInfo.getIndexOfCodeForScript(primaryCode, script);
+ if (-1 == proximityIndex) {
+ proximities = new int[] { primaryCode };
+ } else {
+ // TODO: an initial examination seems to reveal this is actually used
+ // read-only. It should be possible to compute the arrays statically once
+ // and skip doing a copy each time here.
+ proximities = Arrays.copyOfRange(
+ SpellCheckerProximityInfo.getProximityForScript(script),
+ proximityIndex,
+ proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
+ }
+ add(primaryCode, proximities,
+ KeyboardActionListener.NOT_A_TOUCH_COORDINATE,
+ KeyboardActionListener.NOT_A_TOUCH_COORDINATE);
+ }
+
/**
* Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of
* the array containing unicode for adjacent keys, sorted by reducing probability/proximity.
* @param codes the array of unicode values
*/
- public void add(int primaryCode, int[] codes, int x, int y) {
- final int newIndex = size();
- mTypedWord.append((char) primaryCode);
+ private void add(int primaryCode, int[] codes, int keyX, int keyY) {
+ final int newIndex = mCodes.size();
+ mTypedWord.appendCodePoint(primaryCode);
correctPrimaryJuxtapos(primaryCode, codes);
mCodes.add(codes);
if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) {
- mXCoordinates[newIndex] = x;
- mYCoordinates[newIndex] = y;
+ mXCoordinates[newIndex] = keyX;
+ mYCoordinates[newIndex] = keyY;
}
mIsFirstCharCapitalized = isFirstCharCapitalized(
newIndex, primaryCode, mIsFirstCharCapitalized);
if (Character.isUpperCase(primaryCode)) mCapsCount++;
+ if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) {
+ ++mTrailingSingleQuotesCount;
+ } else {
+ mTrailingSingleQuotesCount = 0;
+ }
+ mAutoCorrection = null;
+ }
+
+ /**
+ * Internal method to retrieve reasonable proximity info for a character.
+ */
+ private void addKeyInfo(final int codePoint, final Keyboard keyboard,
+ final KeyDetector keyDetector) {
+ for (final Key key : keyboard.mKeys) {
+ if (key.mCode == codePoint) {
+ final int x = key.mX + key.mWidth / 2;
+ final int y = key.mY + key.mHeight / 2;
+ final int[] codes = keyDetector.newCodeArray();
+ keyDetector.getNearbyCodes(x, y, codes);
+ add(codePoint, codes, x, y);
+ return;
+ }
+ }
+ add(codePoint, new int[] { codePoint },
+ WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+ }
+
+ /**
+ * Set the currently composing word to the one passed as an argument.
+ * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
+ */
+ public void setComposingWord(final CharSequence word, final Keyboard keyboard,
+ final KeyDetector keyDetector) {
+ reset();
+ final int length = word.length();
+ for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
+ int codePoint = Character.codePointAt(word, i);
+ addKeyInfo(codePoint, keyboard, keyDetector);
+ }
+ }
+
+ /**
+ * Shortcut for the above method, this will create a new KeyDetector for the passed keyboard.
+ */
+ public void setComposingWord(final CharSequence word, final Keyboard keyboard) {
+ final KeyDetector keyDetector = new KeyDetector(0);
+ keyDetector.setKeyboard(keyboard, 0, 0);
+ keyDetector.setProximityCorrectionEnabled(true);
+ keyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
+ setComposingWord(word, keyboard, keyDetector);
}
/**
@@ -135,7 +245,7 @@ public class WordComposer {
* @param primaryCode the preferred character
* @param codes array of codes based on distance from touch point
*/
- private void correctPrimaryJuxtapos(int primaryCode, int[] codes) {
+ private static void correctPrimaryJuxtapos(int primaryCode, int[] codes) {
if (codes.length < 2) return;
if (codes[0] > 0 && codes[1] > 0 && codes[0] != primaryCode && codes[1] == primaryCode) {
codes[1] = codes[0];
@@ -147,27 +257,45 @@ public class WordComposer {
* Delete the last keystroke as a result of hitting backspace.
*/
public void deleteLast() {
- final int size = size();
+ final int size = mCodes.size();
if (size > 0) {
- final int lastPos = size - 1;
- char lastChar = mTypedWord.charAt(lastPos);
- mCodes.remove(lastPos);
- mTypedWord.deleteCharAt(lastPos);
+ mCodes.remove(size - 1);
+ // Note: mTypedWord.length() and mCodes.length differ when there are surrogate pairs
+ final int stringBuilderLength = mTypedWord.length();
+ if (stringBuilderLength < size) {
+ throw new RuntimeException(
+ "In WordComposer: mCodes and mTypedWords have non-matching lengths");
+ }
+ final int lastChar = mTypedWord.codePointBefore(stringBuilderLength);
+ if (Character.isSupplementaryCodePoint(lastChar)) {
+ mTypedWord.delete(stringBuilderLength - 2, stringBuilderLength);
+ } else {
+ mTypedWord.deleteCharAt(stringBuilderLength - 1);
+ }
if (Character.isUpperCase(lastChar)) mCapsCount--;
}
- if (size() == 0) {
+ // We may have deleted the last one.
+ if (0 == mCodes.size()) {
mIsFirstCharCapitalized = false;
}
+ if (mTrailingSingleQuotesCount > 0) {
+ --mTrailingSingleQuotesCount;
+ } else {
+ int i = mTypedWord.length();
+ while (i > 0) {
+ i = mTypedWord.offsetByCodePoints(i, -1);
+ if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break;
+ ++mTrailingSingleQuotesCount;
+ }
+ }
+ mAutoCorrection = null;
}
/**
* Returns the word as it was typed, without any correction applied.
- * @return the word that was typed so far
+ * @return the word that was typed so far. Never returns null.
*/
public String getTypedWord() {
- if (size() == 0) {
- return null;
- }
return mTypedWord.toString();
}
@@ -179,6 +307,10 @@ public class WordComposer {
return mIsFirstCharCapitalized;
}
+ public int trailingSingleQuotesCount() {
+ return mTrailingSingleQuotesCount;
+ }
+
/**
* Whether or not all of the user typed chars are upper case
* @return true if all user typed chars are upper case, false otherwise
@@ -194,7 +326,7 @@ public class WordComposer {
return mCapsCount > 1;
}
- /**
+ /**
* Saves the reason why the word is capitalized - whether it was automatic or
* due to the user hitting shift in the middle of a sentence.
* @param auto whether it was an automatic capitalization due to start of sentence
@@ -210,4 +342,50 @@ public class WordComposer {
public boolean isAutoCapitalized() {
return mAutoCapitalized;
}
+
+ /**
+ * Sets the auto-correction for this word.
+ */
+ public void setAutoCorrection(final CharSequence correction) {
+ mAutoCorrection = correction;
+ }
+
+ /**
+ * @return the auto-correction for this word, or null if none.
+ */
+ public CharSequence getAutoCorrectionOrNull() {
+ return mAutoCorrection;
+ }
+
+ // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
+ public LastComposedWord commitWord(final int type, final String committedWord,
+ final int separatorCode) {
+ // Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK
+ // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate
+ // the last composed word to ensure this does not happen.
+ final ArrayList<int[]> codes = mCodes;
+ final int[] xCoordinates = mXCoordinates;
+ final int[] yCoordinates = mYCoordinates;
+ mCodes = new ArrayList<int[]>(N);
+ mXCoordinates = new int[N];
+ mYCoordinates = new int[N];
+ final LastComposedWord lastComposedWord = new LastComposedWord(codes,
+ xCoordinates, yCoordinates, mTypedWord.toString(), committedWord, separatorCode);
+ if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
+ && type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
+ lastComposedWord.deactivate();
+ }
+ mTypedWord.setLength(0);
+ mAutoCorrection = null;
+ return lastComposedWord;
+ }
+
+ public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
+ mCodes = lastComposedWord.mCodes;
+ mXCoordinates = lastComposedWord.mXCoordinates;
+ mYCoordinates = lastComposedWord.mYCoordinates;
+ mTypedWord.setLength(0);
+ mTypedWord.append(lastComposedWord.mTypedWord);
+ mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
new file mode 100644
index 000000000..e14c71cb5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/XmlParseUtils.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2011 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.res.TypedArray;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+public class XmlParseUtils {
+ @SuppressWarnings("serial")
+ public static class ParseException extends XmlPullParserException {
+ public ParseException(String msg, XmlPullParser parser) {
+ super(msg + " at " + parser.getPositionDescription());
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static class IllegalStartTag extends ParseException {
+ public IllegalStartTag(XmlPullParser parser, String parent) {
+ super("Illegal start tag " + parser.getName() + " in " + parent, parser);
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static class IllegalEndTag extends ParseException {
+ public IllegalEndTag(XmlPullParser parser, String parent) {
+ super("Illegal end tag " + parser.getName() + " in " + parent, parser);
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static class IllegalAttribute extends ParseException {
+ public IllegalAttribute(XmlPullParser parser, String attribute) {
+ super("Tag " + parser.getName() + " has illegal attribute " + attribute, parser);
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static class NonEmptyTag extends ParseException{
+ public NonEmptyTag(String tag, XmlPullParser parser) {
+ super(tag + " must be empty tag", parser);
+ }
+ }
+
+ public static void checkEndTag(String tag, XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ if (parser.next() == XmlPullParser.END_TAG && tag.equals(parser.getName()))
+ return;
+ throw new NonEmptyTag(tag, parser);
+ }
+
+ public static void checkAttributeExists(TypedArray attr, int attrId, String attrName,
+ String tag, XmlPullParser parser) throws XmlPullParserException {
+ if (attr.hasValue(attrId))
+ return;
+ throw new ParseException(
+ "No " + attrName + " attribute found in <" + tag + "/>", parser);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/define/JniLibName.java b/java/src/com/android/inputmethod/latin/define/JniLibName.java
new file mode 100644
index 000000000..3e94a3c07
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/define/JniLibName.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2011 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.define;
+
+public class JniLibName {
+ public static final String JNI_LIB_NAME = "jni_latinime";
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 095c2c51c..35a5c0f52 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -17,7 +17,9 @@
package com.android.inputmethod.latin.spellcheck;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.res.Resources;
+import android.preference.PreferenceManager;
import android.service.textservice.SpellCheckerService;
import android.text.TextUtils;
import android.util.Log;
@@ -25,25 +27,28 @@ import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.ArraysCompatUtils;
+import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.BinaryDictionary;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.Dictionary.DataType;
import com.android.inputmethod.latin.Dictionary.WordCallback;
import com.android.inputmethod.latin.DictionaryCollection;
import com.android.inputmethod.latin.DictionaryFactory;
import com.android.inputmethod.latin.Flag;
import com.android.inputmethod.latin.LocaleUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.SynchronouslyLoadedContactsDictionary;
import com.android.inputmethod.latin.SynchronouslyLoadedUserDictionary;
-import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.latin.WhitelistDictionary;
import com.android.inputmethod.latin.WordComposer;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
@@ -51,11 +56,14 @@ import java.util.TreeMap;
/**
* Service for spell checking, using LatinIME's dictionaries and mechanisms.
*/
-public class AndroidSpellCheckerService extends SpellCheckerService {
+public class AndroidSpellCheckerService extends SpellCheckerService
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
private static final boolean DBG = false;
private static final int POOL_SIZE = 2;
+ public static final String PREF_USE_CONTACTS_KEY = "pref_spellcheck_use_contacts";
+
private static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
private static final int CAPITALIZE_FIRST = 1; // First only
private static final int CAPITALIZE_ALL = 2; // All caps
@@ -82,15 +90,100 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
// The threshold for a candidate to be offered as a suggestion.
private double mSuggestionThreshold;
- // The threshold for a suggestion to be considered "likely".
- private double mLikelyThreshold;
+ // The threshold for a suggestion to be considered "recommended".
+ private double mRecommendedThreshold;
+ // Whether to use the contacts dictionary
+ private boolean mUseContactsDictionary;
+ private final Object mUseContactsLock = new Object();
+
+ private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
+ new HashSet<WeakReference<DictionaryCollection>>();
+
+ public static final int SCRIPT_LATIN = 0;
+ public static final int SCRIPT_CYRILLIC = 1;
+ private static final TreeMap<String, Integer> mLanguageToScript;
+ static {
+ // List of the supported languages and their associated script. We won't check
+ // words written in another script than the selected script, because we know we
+ // don't have those in our dictionary so we will underline everything and we
+ // will never have any suggestions, so it makes no sense checking them.
+ mLanguageToScript = new TreeMap<String, Integer>();
+ mLanguageToScript.put("en", SCRIPT_LATIN);
+ mLanguageToScript.put("fr", SCRIPT_LATIN);
+ mLanguageToScript.put("de", SCRIPT_LATIN);
+ mLanguageToScript.put("nl", SCRIPT_LATIN);
+ mLanguageToScript.put("cs", SCRIPT_LATIN);
+ mLanguageToScript.put("es", SCRIPT_LATIN);
+ mLanguageToScript.put("it", SCRIPT_LATIN);
+ mLanguageToScript.put("ru", SCRIPT_CYRILLIC);
+ }
@Override public void onCreate() {
super.onCreate();
mSuggestionThreshold =
Double.parseDouble(getString(R.string.spellchecker_suggestion_threshold_value));
- mLikelyThreshold =
- Double.parseDouble(getString(R.string.spellchecker_likely_threshold_value));
+ mRecommendedThreshold =
+ Double.parseDouble(getString(R.string.spellchecker_recommended_threshold_value));
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ prefs.registerOnSharedPreferenceChangeListener(this);
+ onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
+ }
+
+ private static int getScriptFromLocale(final Locale locale) {
+ final Integer script = mLanguageToScript.get(locale.getLanguage());
+ if (null == script) {
+ throw new RuntimeException("We have been called with an unsupported language: \""
+ + locale.getLanguage() + "\". Framework bug?");
+ }
+ return script;
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
+ if (!PREF_USE_CONTACTS_KEY.equals(key)) return;
+ synchronized(mUseContactsLock) {
+ mUseContactsDictionary = prefs.getBoolean(PREF_USE_CONTACTS_KEY, true);
+ if (mUseContactsDictionary) {
+ startUsingContactsDictionaryLocked();
+ } else {
+ stopUsingContactsDictionaryLocked();
+ }
+ }
+ }
+
+ private void startUsingContactsDictionaryLocked() {
+ if (null == mContactsDictionary) {
+ mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+ }
+ final Iterator<WeakReference<DictionaryCollection>> iterator =
+ mDictionaryCollectionsList.iterator();
+ while (iterator.hasNext()) {
+ final WeakReference<DictionaryCollection> dictRef = iterator.next();
+ final DictionaryCollection dict = dictRef.get();
+ if (null == dict) {
+ iterator.remove();
+ } else {
+ dict.addDictionary(mContactsDictionary);
+ }
+ }
+ }
+
+ private void stopUsingContactsDictionaryLocked() {
+ if (null == mContactsDictionary) return;
+ final SynchronouslyLoadedContactsDictionary contactsDict = mContactsDictionary;
+ mContactsDictionary = null;
+ final Iterator<WeakReference<DictionaryCollection>> iterator =
+ mDictionaryCollectionsList.iterator();
+ while (iterator.hasNext()) {
+ final WeakReference<DictionaryCollection> dictRef = iterator.next();
+ final DictionaryCollection dict = dictRef.get();
+ if (null == dict) {
+ iterator.remove();
+ } else {
+ dict.removeDictionary(contactsDict);
+ }
+ }
+ contactsDict.close();
}
@Override
@@ -110,10 +203,11 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
private static class SuggestionsGatherer implements WordCallback {
public static class Result {
public final String[] mSuggestions;
- public final boolean mHasLikelySuggestions;
- public Result(final String[] gatheredSuggestions, final boolean hasLikelySuggestions) {
+ public final boolean mHasRecommendedSuggestions;
+ public Result(final String[] gatheredSuggestions,
+ final boolean hasRecommendedSuggestions) {
mSuggestions = gatheredSuggestions;
- mHasLikelySuggestions = hasLikelySuggestions;
+ mHasRecommendedSuggestions = hasRecommendedSuggestions;
}
}
@@ -121,7 +215,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
private final int[] mScores;
private final String mOriginalText;
private final double mSuggestionThreshold;
- private final double mLikelyThreshold;
+ private final double mRecommendedThreshold;
private final int mMaxLength;
private int mLength = 0;
@@ -131,10 +225,10 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
private int mBestScore = Integer.MIN_VALUE; // As small as possible
SuggestionsGatherer(final String originalText, final double suggestionThreshold,
- final double likelyThreshold, final int maxLength) {
+ final double recommendedThreshold, final int maxLength) {
mOriginalText = originalText;
mSuggestionThreshold = suggestionThreshold;
- mLikelyThreshold = likelyThreshold;
+ mRecommendedThreshold = recommendedThreshold;
mMaxLength = maxLength;
mSuggestions = new ArrayList<CharSequence>(maxLength + 1);
mScores = new int[mMaxLength];
@@ -142,7 +236,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
@Override
synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
- int dicTypeId, DataType dataType) {
+ int dicTypeId, int dataType) {
final int positionIndex = ArraysCompatUtils.binarySearch(mScores, 0, mLength, score);
// binarySearch returns the index if the element exists, and -<insertion index> - 1
// if it doesn't. See documentation for binarySearch.
@@ -175,7 +269,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
// make the threshold.
final String wordString = new String(word, wordOffset, wordLength);
final double normalizedScore =
- Utils.calcNormalizedScore(mOriginalText, wordString, score);
+ BinaryDictionary.calcNormalizedScore(mOriginalText, wordString, score);
if (normalizedScore < mSuggestionThreshold) {
if (DBG) Log.i(TAG, wordString + " does not make the score threshold");
return true;
@@ -198,19 +292,19 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
public Result getResults(final int capitalizeType, final Locale locale) {
final String[] gatheredSuggestions;
- final boolean hasLikelySuggestions;
+ final boolean hasRecommendedSuggestions;
if (0 == mLength) {
// Either we found no suggestions, or we found some BUT the max length was 0.
// If we found some mBestSuggestion will not be null. If it is null, then
// we found none, regardless of the max length.
if (null == mBestSuggestion) {
gatheredSuggestions = null;
- hasLikelySuggestions = false;
+ hasRecommendedSuggestions = false;
} else {
gatheredSuggestions = EMPTY_STRING_ARRAY;
- final double normalizedScore =
- Utils.calcNormalizedScore(mOriginalText, mBestSuggestion, mBestScore);
- hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
+ final double normalizedScore = BinaryDictionary.calcNormalizedScore(
+ mOriginalText, mBestSuggestion, mBestScore);
+ hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
}
} else {
if (DBG) {
@@ -222,7 +316,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
}
}
Collections.reverse(mSuggestions);
- Utils.removeDupes(mSuggestions);
+ StringUtils.removeDupes(mSuggestions);
if (CAPITALIZE_ALL == capitalizeType) {
for (int i = 0; i < mSuggestions.size(); ++i) {
// get(i) returns a CharSequence which is actually a String so .toString()
@@ -232,7 +326,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
} else if (CAPITALIZE_FIRST == capitalizeType) {
for (int i = 0; i < mSuggestions.size(); ++i) {
// Likewise
- mSuggestions.set(i, Utils.toTitleCase(mSuggestions.get(i).toString(),
+ mSuggestions.set(i, StringUtils.toTitleCase(mSuggestions.get(i).toString(),
locale));
}
}
@@ -243,16 +337,17 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
final int bestScore = mScores[mLength - 1];
final CharSequence bestSuggestion = mSuggestions.get(0);
final double normalizedScore =
- Utils.calcNormalizedScore(mOriginalText, bestSuggestion, bestScore);
- hasLikelySuggestions = (normalizedScore > mLikelyThreshold);
+ BinaryDictionary.calcNormalizedScore(
+ mOriginalText, bestSuggestion.toString(), bestScore);
+ hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
if (DBG) {
Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
Log.i(TAG, "Normalized score = " + normalizedScore
- + " (threshold " + mLikelyThreshold
- + ") => hasLikelySuggestions = " + hasLikelySuggestions);
+ + " (threshold " + mRecommendedThreshold
+ + ") => hasRecommendedSuggestions = " + hasRecommendedSuggestions);
}
}
- return new Result(gatheredSuggestions, hasLikelySuggestions);
+ return new Result(gatheredSuggestions, hasRecommendedSuggestions);
}
}
@@ -273,13 +368,15 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
for (Dictionary dict : oldWhitelistDictionaries.values()) {
dict.close();
}
- if (null != mContactsDictionary) {
- // The synchronously loaded contacts dictionary should have been in one
- // or several pools, but it is shielded against multiple closing and it's
- // safe to call it several times.
- final SynchronouslyLoadedContactsDictionary dictToClose = mContactsDictionary;
- mContactsDictionary = null;
- dictToClose.close();
+ synchronized(mUseContactsLock) {
+ if (null != mContactsDictionary) {
+ // The synchronously loaded contacts dictionary should have been in one
+ // or several pools, but it is shielded against multiple closing and it's
+ // safe to call it several times.
+ final SynchronouslyLoadedContactsDictionary dictToClose = mContactsDictionary;
+ mContactsDictionary = null;
+ dictToClose.close();
+ }
}
return false;
}
@@ -295,9 +392,11 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
}
public DictAndProximity createDictAndProximity(final Locale locale) {
- final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo();
+ final int script = getScriptFromLocale(locale);
+ final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo(
+ SpellCheckerProximityInfo.getProximityForScript(script));
final Resources resources = getResources();
- final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
+ final int fallbackResourceId = DictionaryFactory.getMainDictionaryResourceId(resources);
final DictionaryCollection dictionaryCollection =
DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId,
USE_FULL_EDIT_DISTANCE_FLAG_ARRAY);
@@ -314,11 +413,16 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
mWhitelistDictionaries.put(localeStr, whitelistDictionary);
}
dictionaryCollection.addDictionary(whitelistDictionary);
- if (null == mContactsDictionary) {
- mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+ synchronized(mUseContactsLock) {
+ if (mUseContactsDictionary) {
+ if (null == mContactsDictionary) {
+ mContactsDictionary = new SynchronouslyLoadedContactsDictionary(this);
+ }
+ }
+ dictionaryCollection.addDictionary(mContactsDictionary);
+ mDictionaryCollectionsList.add(
+ new WeakReference<DictionaryCollection>(dictionaryCollection));
}
- // TODO: add a setting to use or not contacts when checking spelling
- dictionaryCollection.addDictionary(mContactsDictionary);
return new DictAndProximity(dictionaryCollection, proximityInfo);
}
@@ -327,9 +431,9 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
// If the first char is not uppercase, then the word is either all lower case,
// and in either case we return CAPITALIZE_NONE.
if (!Character.isUpperCase(text.codePointAt(0))) return CAPITALIZE_NONE;
- final int len = text.codePointCount(0, text.length());
+ final int len = text.length();
int capsCount = 1;
- for (int i = 1; i < len; ++i) {
+ for (int i = 1; i < len; i = text.offsetByCodePoints(i, 1)) {
if (1 != capsCount && i != capsCount) break;
if (Character.isUpperCase(text.codePointAt(i))) ++capsCount;
}
@@ -346,6 +450,8 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
private DictionaryPool mDictionaryPool;
// Likewise
private Locale mLocale;
+ // Cache this for performance
+ private int mScript; // One of SCRIPT_LATIN or SCRIPT_CYRILLIC for now.
private final AndroidSpellCheckerService mService;
@@ -358,17 +464,51 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
final String localeString = getLocale();
mDictionaryPool = mService.getDictionaryPool(localeString);
mLocale = LocaleUtils.constructLocaleFromString(localeString);
+ mScript = getScriptFromLocale(mLocale);
+ }
+
+ /*
+ * Returns whether the code point is a letter that makes sense for the specified
+ * locale for this spell checker.
+ * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml
+ * and is limited to EFIGS languages and Russian.
+ * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters
+ * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters.
+ */
+ private static boolean isLetterCheckableByLanguage(final int codePoint,
+ final int script) {
+ switch (script) {
+ case SCRIPT_LATIN:
+ // Our supported latin script dictionaries (EFIGS) at the moment only include
+ // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode
+ // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF,
+ // so the below is a very efficient way to test for it. As for the 0-0x3F, it's
+ // excluded from isLetter anyway.
+ return codePoint <= 0x2AF && Character.isLetter(codePoint);
+ case SCRIPT_CYRILLIC:
+ // All Cyrillic characters are in the 400~52F block. There are some in the upper
+ // Unicode range, but they are archaic characters that are not used in modern
+ // russian and are not used by our dictionary.
+ return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint);
+ default:
+ // Should never come here
+ throw new RuntimeException("Impossible value of script: " + script);
+ }
}
/**
* Finds out whether a particular string should be filtered out of spell checking.
*
- * This will loosely match URLs, numbers, symbols.
+ * This will loosely match URLs, numbers, symbols. To avoid always underlining words that
+ * we know we will never recognize, this accepts a script identifier that should be one
+ * of the SCRIPT_* constants defined above, to rule out quickly characters from very
+ * different languages.
*
* @param text the string to evaluate.
+ * @param script the identifier for the script this spell checker recognizes
* @return true if we should filter this text out, false otherwise
*/
- private boolean shouldFilterOut(final String text) {
+ private static boolean shouldFilterOut(final String text, final int script) {
if (TextUtils.isEmpty(text) || text.length() <= 1) return true;
// TODO: check if an equivalent processing can't be done more quickly with a
@@ -376,20 +516,19 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
// Filter by first letter
final int firstCodePoint = text.codePointAt(0);
// Filter out words that don't start with a letter or an apostrophe
- if (!Character.isLetter(firstCodePoint)
+ if (!isLetterCheckableByLanguage(firstCodePoint, script)
&& '\'' != firstCodePoint) return true;
// Filter contents
final int length = text.length();
int letterCount = 0;
- for (int i = 0; i < length; ++i) {
+ 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
// words or a URI - in either case we don't want to spell check that
- if ('@' == codePoint
- || '/' == codePoint) return true;
- if (Character.isLetter(codePoint)) ++letterCount;
+ if ('@' == codePoint || '/' == codePoint) return true;
+ if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount;
}
// Guestimate heuristic: perform spell checking if at least 3/4 of the characters
// in this word are letters
@@ -408,7 +547,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
try {
final String text = textInfo.getText();
- if (shouldFilterOut(text)) {
+ if (shouldFilterOut(text, mScript)) {
DictAndProximity dictInfo = null;
try {
dictInfo = mDictionaryPool.takeOrGetNull();
@@ -426,22 +565,12 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
// TODO: Don't gather suggestions if the limit is <= 0 unless necessary
final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(text,
- mService.mSuggestionThreshold, mService.mLikelyThreshold, suggestionsLimit);
+ mService.mSuggestionThreshold, mService.mRecommendedThreshold,
+ suggestionsLimit);
final WordComposer composer = new WordComposer();
final int length = text.length();
- for (int i = 0; i < length; ++i) {
- final int character = text.codePointAt(i);
- final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character);
- final int[] proximities;
- if (-1 == proximityIndex) {
- proximities = new int[] { character };
- } else {
- proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY,
- proximityIndex,
- proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
- }
- composer.add(character, proximities,
- WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+ for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
+ composer.addKeyForSpellChecker(text.codePointAt(i), mScript);
}
final int capitalizeType = getCapitalizationType(text);
@@ -475,7 +604,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
+ suggestionsLimit);
Log.i(TAG, "IsInDict = " + isInDict);
Log.i(TAG, "LooksLikeTypo = " + (!isInDict));
- Log.i(TAG, "HasLikelySuggestions = " + result.mHasLikelySuggestions);
+ Log.i(TAG, "HasRecommendedSuggestions = " + result.mHasRecommendedSuggestions);
if (null != result.mSuggestions) {
for (String suggestion : result.mSuggestions) {
Log.i(TAG, suggestion);
@@ -483,10 +612,13 @@ public class AndroidSpellCheckerService extends SpellCheckerService {
}
}
- // TODO: actually use result.mHasLikelySuggestions
final int flags =
(isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
- : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO);
+ : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO)
+ | (result.mHasRecommendedSuggestions
+ ? SuggestionsInfoCompatUtils
+ .getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS()
+ : 0);
return new SuggestionsInfo(flags, result.mSuggestions);
} catch (RuntimeException e) {
// Don't kill the keyboard if there is a bug in the spell checker
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index d5b04b27c..db3544987 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -22,72 +22,158 @@ import com.android.inputmethod.keyboard.ProximityInfo;
import java.util.TreeMap;
public class SpellCheckerProximityInfo {
- final private static int NUL = KeyDetector.NOT_A_CODE;
+ /* public for test */
+ final public static int NUL = KeyDetector.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;
- // 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 = new TreeMap<Integer, Integer>();
+ // 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);
+ }
+ }
+ final protected static int computeIndex(final int characterCode,
+ final TreeMap<Integer, Integer> indices) {
+ final Integer result = indices.get(characterCode);
+ if (null == result) return -1;
+ return result;
+ }
- // The proximity here is the union of
- // - the proximity for a QWERTY keyboard.
- // - the proximity for an AZERTY keyboard.
- // - the proximity for a QWERTZ keyboard.
- // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other.
- //
- // The reasoning behind this construction is, almost any alphabetic text we may want
- // to spell check has been entered with one of the keyboards above. Also, specifically
- // to English, many spelling errors consist of the last vowel of the word being wrong
- // because in English vowels tend to merge with each other in pronunciation.
- final public static int[] PROXIMITY = {
- 'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
- 'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL,
- 'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ private static 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 = new TreeMap<Integer, Integer>();
- 'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
- 's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL,
- 'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'l', 'i', 'k', 'p', 'o', 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,
+ // The proximity here is the union of
+ // - the proximity for a QWERTY keyboard.
+ // - the proximity for an AZERTY keyboard.
+ // - the proximity for a QWERTZ keyboard.
+ // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other.
+ //
+ // The reasoning behind this construction is, almost any alphabetic text we may want
+ // to spell check has been entered with one of the keyboards above. Also, specifically
+ // to English, many spelling errors consist of the last vowel of the word being wrong
+ // because in English vowels tend to merge with each other in pronunciation.
+ final static int[] PROXIMITY = {
+ 'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
+ 'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL,
+ 'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'p', 'o', 'l', 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,
- 'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL,
- 'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- 'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', 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, 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 {
- for (int i = 0; i < PROXIMITY.length; i += ROW_SIZE) {
- if (NUL != PROXIMITY[i]) INDICES.put(PROXIMITY[i], i);
+ 'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
+ 's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL,
+ 'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'l', 'i', 'k', 'p', 'o', 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, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ 'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL,
+ 'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', 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, 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 {
+ buildProximityIndices(PROXIMITY, INDICES);
+ }
+ static int getIndexOf(int characterCode) {
+ return computeIndex(characterCode, INDICES);
}
}
- public static int getIndexOf(int characterCode) {
- final Integer result = INDICES.get(characterCode);
- if (null == result) return -1;
- return result;
+
+ private static class Cyrillic {
+ final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+ final static int[] PROXIMITY = {
+ // TODO: This table is solely based on the keyboard layout. Consult with Russian
+ // speakers on commonly misspelled words/letters.
+ 'й', 'ц', 'ф', 'ы', 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,
+ 'к', 'у', 'в', 'а', 'п', 'е', 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, 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, 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, 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, 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, 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, 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, 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, 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, NUL, NUL, NUL, NUL, NUL, NUL,
+ };
+ static {
+ buildProximityIndices(PROXIMITY, INDICES);
+ }
+ static int getIndexOf(int characterCode) {
+ return computeIndex(characterCode, INDICES);
+ }
+ }
+
+ public static int[] getProximityForScript(final int script) {
+ switch (script) {
+ case AndroidSpellCheckerService.SCRIPT_LATIN:
+ return Latin.PROXIMITY;
+ case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+ return Cyrillic.PROXIMITY;
+ default:
+ throw new RuntimeException("Wrong script supplied: " + script);
+ }
+ }
+ public static int getIndexOfCodeForScript(final int characterCode, final int script) {
+ switch (script) {
+ case AndroidSpellCheckerService.SCRIPT_LATIN:
+ return Latin.getIndexOf(characterCode);
+ case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+ return Cyrillic.getIndexOf(characterCode);
+ default:
+ throw new RuntimeException("Wrong script supplied: " + script);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 9a59ef2e0..c9c88fd23 100644
--- a/java/src/com/android/inputmethod/latin/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -14,37 +14,38 @@
* the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.suggestions;
import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView;
-import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
-import com.android.inputmethod.keyboard.internal.KeyboardParams;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.Utils;
public class MoreSuggestions extends Keyboard {
- private static final boolean DBG = LatinImeLogger.sDBG;
-
public static final int SUGGESTION_CODE_BASE = 1024;
- private MoreSuggestions(Builder.MoreSuggestionsParam params) {
+ MoreSuggestions(Builder.MoreSuggestionsParam params) {
super(params);
}
- public static class Builder extends KeyboardBuilder<Builder.MoreSuggestionsParam> {
+ public static class Builder extends Keyboard.Builder<Builder.MoreSuggestionsParam> {
+ private static final boolean DBG = LatinImeLogger.sDBG;
+
private final MoreSuggestionsView mPaneView;
private SuggestedWords mSuggestions;
private int mFromPos;
private int mToPos;
- public static class MoreSuggestionsParam extends KeyboardParams {
+ public static class MoreSuggestionsParam extends Keyboard.Params {
private final int[] mWidths = new int[SuggestionsView.MAX_SUGGESTIONS];
private final int[] mRowNumbers = new int[SuggestionsView.MAX_SUGGESTIONS];
private final int[] mColumnOrders = new int[SuggestionsView.MAX_SUGGESTIONS];
@@ -61,7 +62,7 @@ public class MoreSuggestions extends Keyboard {
paint.setAntiAlias(true);
final Resources res = view.getContext().getResources();
mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
- // TODO: Drawable itself should has an alpha value.
+ // TODO: Drawable itself should have an alpha value.
mDivider.setAlpha(128);
mDividerWidth = mDivider.getIntrinsicWidth();
final int padding = (int) res.getDimension(
@@ -71,7 +72,7 @@ public class MoreSuggestions extends Keyboard {
int pos = fromPos, rowStartPos = fromPos;
final int size = Math.min(suggestions.size(), SuggestionsView.MAX_SUGGESTIONS);
while (pos < size) {
- final CharSequence word = suggestions.getWord(pos);
+ final String word = suggestions.getWord(pos).toString();
// TODO: Should take care of text x-scaling.
mWidths[pos] = (int)view.getDefaultLabelWidth(word, paint) + padding;
final int numColumn = pos - rowStartPos + 1;
@@ -176,9 +177,9 @@ public class MoreSuggestions extends Keyboard {
public Builder layout(SuggestedWords suggestions, int fromPos, int maxWidth,
int minWidth, int maxRow) {
- final Keyboard keyboard = KeyboardSwitcher.getInstance().getLatinKeyboard();
+ final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard();
final int xmlId = R.xml.kbd_suggestions_pane_template;
- load(keyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId));
+ load(xmlId, keyboard.mId);
mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2;
final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow,
@@ -189,13 +190,19 @@ public class MoreSuggestions extends Keyboard {
return this;
}
- private static String getDebugInfo(SuggestedWords suggestions, int pos) {
- if (!DBG) return null;
- final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
- if (wordInfo == null) return null;
- final String info = wordInfo.getDebugString();
- if (TextUtils.isEmpty(info)) return null;
- return info;
+ private static class Divider extends Key.Spacer {
+ private final Drawable mIcon;
+
+ public Divider(Keyboard.Params params, Drawable icon, int x, int y, int width,
+ int height) {
+ super(params, x, y, width, height);
+ mIcon = icon;
+ }
+
+ @Override
+ public Drawable getIcon(KeyboardIconsSet iconSet) {
+ return mIcon;
+ }
}
@Override
@@ -206,19 +213,19 @@ public class MoreSuggestions extends Keyboard {
final int y = params.getY(pos);
final int width = params.getWidth(pos);
final String word = mSuggestions.getWord(pos).toString();
- final String info = getDebugInfo(mSuggestions, pos);
+ final String info = Utils.getDebugInfo(mSuggestions, pos);
final int index = pos + SUGGESTION_CODE_BASE;
final Key key = new Key(
- params, word, info, null, index, null, x, y, width,
- params.mDefaultRowHeight);
+ params, word, info, KeyboardIconsSet.ICON_UNDEFINED, index, null, x, y,
+ width, params.mDefaultRowHeight, 0);
params.markAsEdgeKey(key, pos);
params.onAddKey(key);
final int columnNumber = params.getColumnNumber(pos);
final int numColumnInRow = params.getNumColumnInRow(pos);
if (columnNumber < numColumnInRow - 1) {
- final Key.Spacer spacer = new Key.Spacer(params, params.mDivider, x + width, y,
+ final Divider divider = new Divider(params, params.mDivider, x + width, y,
params.mDividerWidth, params.mDefaultRowHeight);
- params.onAddKey(spacer);
+ params.onAddKey(divider);
}
}
return new MoreSuggestions(params);
diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 8eab79413..f11b1a4c1 100644
--- a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.suggestions;
import android.content.Context;
import android.content.res.Resources;
@@ -34,6 +34,7 @@ import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy;
import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler;
import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
+import com.android.inputmethod.latin.R;
/**
* A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting
@@ -55,17 +56,17 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
private final KeyboardActionListener mSuggestionsPaneListener =
new KeyboardActionListener.Adapter() {
@Override
- public void onPress(int primaryCode, boolean withSliding) {
- mListener.onPress(primaryCode, withSliding);
+ public void onPressKey(int primaryCode) {
+ mListener.onPressKey(primaryCode);
}
@Override
- public void onRelease(int primaryCode, boolean withSliding) {
- mListener.onRelease(primaryCode, withSliding);
+ public void onReleaseKey(int primaryCode, boolean withSliding) {
+ mListener.onReleaseKey(primaryCode, withSliding);
}
@Override
- public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
+ public void onCodeInput(int primaryCode, int x, int y) {
final int index = primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE;
if (index >= 0 && index < SuggestionsView.MAX_SUGGESTIONS) {
mListener.onCustomRequest(index);
@@ -140,11 +141,6 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel {
}
@Override
- public void setShifted(boolean shifted) {
- // Nothing to do with.
- }
-
- @Override
public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY,
PopupWindow window, KeyboardActionListener listener) {
mController = controller;
diff --git a/java/src/com/android/inputmethod/latin/SuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
index c25ecb382..d3c3afb73 100644
--- a/java/src/com/android/inputmethod/latin/SuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java
@@ -14,7 +14,7 @@
* the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.suggestions;
import android.content.Context;
import android.content.res.Resources;
@@ -30,7 +30,6 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Message;
-import android.os.SystemClock;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
@@ -58,7 +57,13 @@ import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.MoreKeysPanel;
import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.Utils;
import java.util.ArrayList;
import java.util.List;
@@ -73,7 +78,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
public static final int MAX_SUGGESTIONS = 18;
- private static final boolean DBG = LatinImeLogger.sDBG;
+ static final boolean DBG = LatinImeLogger.sDBG;
private final ViewGroup mSuggestionsStrip;
private KeyboardView mKeyboardView;
@@ -91,7 +96,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
private final TextView mPreviewText;
private Listener mListener;
- private SuggestedWords mSuggestions = SuggestedWords.EMPTY;
+ private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
private final SuggestionsViewParams mParams;
private static final float MIN_TEXT_XSCALE = 0.70f;
@@ -101,8 +106,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
private static class UiHandler extends StaticInnerHandlerWrapper<SuggestionsView> {
private static final int MSG_HIDE_PREVIEW = 0;
- private static final long DELAY_HIDE_PREVIEW = 1300;
-
public UiHandler(SuggestionsView outerInstance) {
super(outerInstance);
}
@@ -117,11 +120,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
}
}
- public void postHidePreview() {
- cancelHidePreview();
- sendMessageDelayed(obtainMessage(MSG_HIDE_PREVIEW), DELAY_HIDE_PREVIEW);
- }
-
public void cancelHidePreview() {
removeMessages(MSG_HIDE_PREVIEW);
}
@@ -141,7 +139,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
public final int mDividerWidth;
public final int mSuggestionsStripHeight;
public final int mSuggestionsCountInStrip;
- public final int mMaxMoreSuggestionsRow;
+ public final int mMoreSuggestionsRowHeight;
+ private int mMaxMoreSuggestionsRow;
public final float mMinMoreSuggestionsWidth;
public final int mMoreSuggestionsBottomGap;
@@ -149,6 +148,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
private final List<View> mDividers;
private final List<TextView> mInfos;
+ private final int mColorValidTypedWord;
private final int mColorTypedWord;
private final int mColorAutoCorrect;
private final int mColorSuggested;
@@ -172,7 +172,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
public final TextView mWordToSaveView;
private final TextView mHintToSaveView;
- private final CharSequence mHintToSaveText;
public SuggestionsViewParams(Context context, AttributeSet attrs, int defStyle,
List<TextView> words, List<View> dividers, List<TextView> infos) {
@@ -193,6 +192,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.SuggestionsView, defStyle, R.style.SuggestionsViewStyle);
mSuggestionStripOption = a.getInt(R.styleable.SuggestionsView_suggestionStripOption, 0);
+ final float alphaValidTypedWord = getPercent(a,
+ R.styleable.SuggestionsView_alphaValidTypedWord, 100);
final float alphaTypedWord = getPercent(a,
R.styleable.SuggestionsView_alphaTypedWord, 100);
final float alphaAutoCorrect = getPercent(a,
@@ -200,6 +201,9 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
final float alphaSuggested = getPercent(a,
R.styleable.SuggestionsView_alphaSuggested, 100);
mAlphaObsoleted = getPercent(a, R.styleable.SuggestionsView_alphaSuggested, 100);
+ mColorValidTypedWord = applyAlpha(
+ a.getColor(R.styleable.SuggestionsView_colorValidTypedWord, 0),
+ alphaValidTypedWord);
mColorTypedWord = applyAlpha(
a.getColor(R.styleable.SuggestionsView_colorTypedWord, 0), alphaTypedWord);
mColorAutoCorrect = applyAlpha(
@@ -224,11 +228,32 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
mCenterSuggestionIndex = mSuggestionsCountInStrip / 2;
mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
R.dimen.more_suggestions_bottom_gap);
+ mMoreSuggestionsRowHeight = res.getDimensionPixelSize(
+ R.dimen.more_suggestions_row_height);
final LayoutInflater inflater = LayoutInflater.from(context);
mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
mHintToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
- mHintToSaveText = context.getText(R.string.hint_add_to_dictionary);
+ }
+
+ public int getMaxMoreSuggestionsRow() {
+ return mMaxMoreSuggestionsRow;
+ }
+
+ private int getMoreSuggestionsHeight() {
+ return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
+ }
+
+ public int setMoreSuggestionsHeight(int remainingHeight) {
+ final int currentHeight = getMoreSuggestionsHeight();
+ if (currentHeight <= remainingHeight) {
+ return currentHeight;
+ }
+
+ mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap)
+ / mMoreSuggestionsRowHeight;
+ final int newHeight = getMoreSuggestionsHeight();
+ return newHeight;
}
private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) {
@@ -258,10 +283,10 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
return a.getFraction(index, 1000, 1000, 1) / 1000.0f;
}
- private CharSequence getStyledSuggestionWord(SuggestedWords suggestions, int pos) {
- final CharSequence word = suggestions.getWord(pos);
- final boolean isAutoCorrect = pos == 1 && Utils.willAutoCorrect(suggestions);
- final boolean isTypedWordValid = pos == 0 && suggestions.mTypedWordValid;
+ private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) {
+ final CharSequence word = suggestedWords.getWord(pos);
+ final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect();
+ final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid;
if (!isAutoCorrect && !isTypedWordValid)
return word;
@@ -278,10 +303,10 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
return spannedWord;
}
- private int getWordPosition(int index, SuggestedWords suggestions) {
+ private int getWordPosition(int index, SuggestedWords suggestedWords) {
// TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
// suggestions.
- final int centerPos = Utils.willAutoCorrect(suggestions) ? 1 : 0;
+ final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0;
if (index == mCenterSuggestionIndex) {
return centerPos;
} else if (index == centerPos) {
@@ -291,28 +316,31 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
}
}
- private int getSuggestionTextColor(int index, SuggestedWords suggestions, int pos) {
+ private int getSuggestionTextColor(int index, SuggestedWords suggestedWords, int pos) {
// TODO: Need to revisit this logic with bigram suggestions
final boolean isSuggested = (pos != 0);
final int color;
- if (index == mCenterSuggestionIndex && Utils.willAutoCorrect(suggestions)) {
+ if (index == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) {
color = mColorAutoCorrect;
+ } else if (index == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) {
+ color = mColorValidTypedWord;
} else if (isSuggested) {
color = mColorSuggested;
} else {
color = mColorTypedWord;
}
- if (LatinImeLogger.sDBG) {
- if (index == mCenterSuggestionIndex && suggestions.mHasAutoCorrectionCandidate
- && suggestions.shouldBlockAutoCorrection()) {
+ if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
+ // If we auto-correct, then the autocorrection is in slot 0 and the typed word
+ // is in slot 1.
+ if (index == mCenterSuggestionIndex && suggestedWords.mHasAutoCorrectionCandidate
+ && Suggest.shouldBlockAutoCorrectionBySafetyNet(
+ suggestedWords.getWord(1).toString(), suggestedWords.getWord(0))) {
return 0xFFFF0000;
}
}
- final SuggestedWordInfo info = (pos < suggestions.size())
- ? suggestions.getInfo(pos) : null;
- if (info != null && info.isObsoleteSuggestedWord()) {
+ if (suggestedWords.mIsObsoleteSuggestions && isSuggested) {
return applyAlpha(color, mAlphaObsoleted);
} else {
return color;
@@ -331,19 +359,19 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
params.gravity = Gravity.CENTER;
}
- public void layout(SuggestedWords suggestions, ViewGroup stripView, ViewGroup placer,
+ public void layout(SuggestedWords suggestedWords, ViewGroup stripView, ViewGroup placer,
int stripWidth) {
- if (suggestions.isPunctuationSuggestions()) {
- layoutPunctuationSuggestions(suggestions, stripView);
+ if (suggestedWords.mIsPunctuationSuggestions) {
+ layoutPunctuationSuggestions(suggestedWords, stripView);
return;
}
final int countInStrip = mSuggestionsCountInStrip;
- setupTexts(suggestions, countInStrip);
- mMoreSuggestionsAvailable = (suggestions.size() > countInStrip);
+ setupTexts(suggestedWords, countInStrip);
+ mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
int x = 0;
for (int index = 0; index < countInStrip; index++) {
- final int pos = getWordPosition(index, suggestions);
+ final int pos = getWordPosition(index, suggestedWords);
if (index != 0) {
final View divider = mDividers.get(pos);
@@ -366,7 +394,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
// Disable this suggestion if the suggestion is null or empty.
word.setEnabled(!TextUtils.isEmpty(styled));
- word.setTextColor(getSuggestionTextColor(index, suggestions, pos));
+ word.setTextColor(getSuggestionTextColor(index, suggestedWords, pos));
final int width = getSuggestionWidth(index, stripWidth);
final CharSequence text = getEllipsizedText(styled, width, word.getPaint());
final float scaleX = word.getTextScaleX();
@@ -377,8 +405,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT);
x += word.getMeasuredWidth();
- if (DBG) {
- final CharSequence debugInfo = getDebugInfo(suggestions, pos);
+ if (DBG && pos < suggestedWords.size()) {
+ final CharSequence debugInfo = Utils.getDebugInfo(suggestedWords, pos);
if (debugInfo != null) {
final TextView info = mInfos.get(pos);
info.setText(debugInfo);
@@ -410,11 +438,11 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
}
}
- private void setupTexts(SuggestedWords suggestions, int countInStrip) {
+ private void setupTexts(SuggestedWords suggestedWords, int countInStrip) {
mTexts.clear();
- final int count = Math.min(suggestions.size(), countInStrip);
+ final int count = Math.min(suggestedWords.size(), countInStrip);
for (int pos = 0; pos < count; pos++) {
- final CharSequence styled = getStyledSuggestionWord(suggestions, pos);
+ final CharSequence styled = getStyledSuggestionWord(suggestedWords, pos);
mTexts.add(styled);
}
for (int pos = count; pos < countInStrip; pos++) {
@@ -423,8 +451,9 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
}
}
- private void layoutPunctuationSuggestions(SuggestedWords suggestions, ViewGroup stripView) {
- final int countInStrip = Math.min(suggestions.size(), PUNCTUATIONS_IN_STRIP);
+ private void layoutPunctuationSuggestions(SuggestedWords suggestedWords,
+ ViewGroup stripView) {
+ final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
for (int index = 0; index < countInStrip; index++) {
if (index != 0) {
// Add divider if this isn't the left most suggestion in suggestions strip.
@@ -433,8 +462,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
final TextView word = mWords.get(index);
word.setEnabled(true);
- word.setTextColor(mColorTypedWord);
- final CharSequence text = suggestions.getWord(index);
+ word.setTextColor(mColorAutoCorrect);
+ final CharSequence text = suggestedWords.getWord(index);
word.setText(text);
word.setTextScaleX(1.0f);
word.setCompoundDrawables(null, null, null, null);
@@ -445,7 +474,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
}
public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView,
- int stripWidth) {
+ int stripWidth, CharSequence hintText) {
final int width = stripWidth - mDividerWidth - mPadding * 2;
final TextView wordView = mWordToSaveView;
@@ -464,13 +493,85 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
final TextView hintView = mHintToSaveView;
hintView.setTextColor(mColorAutoCorrect);
final int hintWidth = width - wordWidth;
- final float hintScaleX = getTextScaleX(mHintToSaveText, hintWidth, hintView.getPaint());
- hintView.setText(mHintToSaveText);
+ final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
+ hintView.setText(hintText);
hintView.setTextScaleX(hintScaleX);
stripView.addView(hintView);
setLayoutWeight(
hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
}
+
+ private static void setLayoutWeight(View v, float weight, int height) {
+ final ViewGroup.LayoutParams lp = v.getLayoutParams();
+ if (lp instanceof LinearLayout.LayoutParams) {
+ final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
+ llp.weight = weight;
+ llp.width = 0;
+ llp.height = height;
+ }
+ }
+
+ private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) {
+ paint.setTextScaleX(1.0f);
+ final int width = getTextWidth(text, paint);
+ if (width <= maxWidth) {
+ return 1.0f;
+ }
+ return maxWidth / (float)width;
+ }
+
+ private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
+ TextPaint paint) {
+ if (text == null) return null;
+ paint.setTextScaleX(1.0f);
+ final int width = getTextWidth(text, paint);
+ if (width <= maxWidth) {
+ return text;
+ }
+ final float scaleX = maxWidth / (float)width;
+ if (scaleX >= MIN_TEXT_XSCALE) {
+ paint.setTextScaleX(scaleX);
+ return text;
+ }
+
+ // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To
+ // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
+ final CharSequence ellipsized = TextUtils.ellipsize(
+ text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
+ paint.setTextScaleX(MIN_TEXT_XSCALE);
+ return ellipsized;
+ }
+
+ private static int getTextWidth(CharSequence text, TextPaint paint) {
+ if (TextUtils.isEmpty(text)) return 0;
+ final Typeface savedTypeface = paint.getTypeface();
+ paint.setTypeface(getTextTypeface(text));
+ final int len = text.length();
+ final float[] widths = new float[len];
+ final int count = paint.getTextWidths(text, 0, len, widths);
+ int width = 0;
+ for (int i = 0; i < count; i++) {
+ width += Math.round(widths[i] + 0.5f);
+ }
+ paint.setTypeface(savedTypeface);
+ return width;
+ }
+
+ private static Typeface getTextTypeface(CharSequence text) {
+ if (!(text instanceof SpannableString))
+ return Typeface.DEFAULT;
+
+ final SpannableString ss = (SpannableString)text;
+ final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
+ if (styles.length == 0)
+ return Typeface.DEFAULT;
+
+ switch (styles[0].getStyle()) {
+ case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
+ // TODO: BOLD_ITALIC, ITALIC case?
+ default: return Typeface.DEFAULT;
+ }
+ }
}
/**
@@ -548,98 +649,17 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view);
}
- public void setSuggestions(SuggestedWords suggestions) {
- if (suggestions == null || suggestions.size() == 0)
+ public void setSuggestions(SuggestedWords suggestedWords) {
+ if (suggestedWords == null || suggestedWords.size() == 0)
return;
clear();
- mSuggestions = suggestions;
- mParams.layout(mSuggestions, mSuggestionsStrip, this, getWidth());
- }
-
- private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) {
- if (DBG && pos < suggestions.size()) {
- final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
- if (wordInfo != null) {
- final CharSequence debugInfo = wordInfo.getDebugString();
- if (!TextUtils.isEmpty(debugInfo)) {
- return debugInfo;
- }
- }
- }
- return null;
- }
-
- private static void setLayoutWeight(View v, float weight, int height) {
- final ViewGroup.LayoutParams lp = v.getLayoutParams();
- if (lp instanceof LinearLayout.LayoutParams) {
- final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
- llp.weight = weight;
- llp.width = 0;
- llp.height = height;
- }
- }
-
- private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) {
- paint.setTextScaleX(1.0f);
- final int width = getTextWidth(text, paint);
- if (width <= maxWidth) {
- return 1.0f;
- }
- return maxWidth / (float)width;
+ mSuggestedWords = suggestedWords;
+ mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth());
}
- private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
- TextPaint paint) {
- if (text == null) return null;
- paint.setTextScaleX(1.0f);
- final int width = getTextWidth(text, paint);
- if (width <= maxWidth) {
- return text;
- }
- final float scaleX = maxWidth / (float)width;
- if (scaleX >= MIN_TEXT_XSCALE) {
- paint.setTextScaleX(scaleX);
- return text;
- }
-
- // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To get
- // squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
- final CharSequence ellipsized = TextUtils.ellipsize(
- text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
- paint.setTextScaleX(MIN_TEXT_XSCALE);
- return ellipsized;
- }
-
- private static int getTextWidth(CharSequence text, TextPaint paint) {
- if (TextUtils.isEmpty(text)) return 0;
- final Typeface savedTypeface = paint.getTypeface();
- paint.setTypeface(getTextTypeface(text));
- final int len = text.length();
- final float[] widths = new float[len];
- final int count = paint.getTextWidths(text, 0, len, widths);
- int width = 0;
- for (int i = 0; i < count; i++) {
- width += Math.round(widths[i] + 0.5f);
- }
- paint.setTypeface(savedTypeface);
- return width;
- }
-
- private static Typeface getTextTypeface(CharSequence text) {
- if (!(text instanceof SpannableString))
- return Typeface.DEFAULT;
-
- final SpannableString ss = (SpannableString)text;
- final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
- if (styles.length == 0)
- return Typeface.DEFAULT;
-
- switch (styles[0].getStyle()) {
- case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
- // TODO: BOLD_ITALIC, ITALIC case?
- default: return Typeface.DEFAULT;
- }
+ public int setMoreSuggestionsHeight(int remainingHeight) {
+ return mParams.setMoreSuggestionsHeight(remainingHeight);
}
public boolean isShowingAddToDictionaryHint() {
@@ -647,9 +667,9 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
&& mSuggestionsStrip.getChildAt(0) == mParams.mWordToSaveView;
}
- public void showAddToDictionaryHint(CharSequence word) {
+ public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) {
clear();
- mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth());
+ mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText);
}
public boolean dismissAddToDictionaryHint() {
@@ -661,7 +681,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
}
public SuggestedWords getSuggestions() {
- return mSuggestions;
+ return mSuggestedWords;
}
public void clear() {
@@ -675,34 +695,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
mPreviewPopup.dismiss();
}
- private void showPreview(View view, CharSequence word) {
- if (TextUtils.isEmpty(word))
- return;
-
- final TextView previewText = mPreviewText;
- previewText.setTextColor(mParams.mColorTypedWord);
- previewText.setText(word);
- previewText.measure(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
- final int[] offsetInWindow = new int[2];
- view.getLocationInWindow(offsetInWindow);
- final int posX = offsetInWindow[0];
- final int posY = offsetInWindow[1] - previewText.getMeasuredHeight();
- final PopupWindow previewPopup = mPreviewPopup;
- if (previewPopup.isShowing()) {
- previewPopup.update(posX, posY, previewPopup.getWidth(), previewPopup.getHeight());
- } else {
- previewPopup.showAtLocation(this, Gravity.NO_GRAVITY, posX, posY);
- }
- previewText.setVisibility(VISIBLE);
- mHandler.postHidePreview();
- }
-
private void addToDictionary(CharSequence word) {
- if (mListener.addWordToDictionary(word.toString())) {
- final CharSequence message = getContext().getString(R.string.added_word, word);
- showPreview(mParams.mWordToSaveView, message);
- }
+ mListener.addWordToDictionary(word.toString());
}
private final KeyboardActionListener mMoreSuggestionsListener =
@@ -710,7 +704,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
@Override
public boolean onCustomRequest(int requestCode) {
final int index = requestCode;
- final CharSequence word = mSuggestions.getWord(index);
+ final CharSequence word = mSuggestedWords.getWord(index);
mListener.pickSuggestionManually(index, word);
dismissMoreSuggestions();
return true;
@@ -755,9 +749,9 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
final int maxWidth = stripWidth - container.getPaddingLeft()
- container.getPaddingRight();
final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
- builder.layout(mSuggestions, params.mSuggestionsCountInStrip, maxWidth,
+ builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
(int)(maxWidth * params.mMinMoreSuggestionsWidth),
- params.mMaxMoreSuggestionsRow);
+ params.getMaxMoreSuggestionsRow());
mMoreSuggestionsView.setKeyboard(builder.build());
container.measure(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
@@ -832,8 +826,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
// Decided to be in the sliding input mode only when the touch point has been moved
// upward.
mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE;
- tracker.onShowMoreKeysPanel(
- translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel);
+ tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
} 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;
@@ -858,10 +851,10 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener,
if (!(tag instanceof Integer))
return;
final int index = (Integer) tag;
- if (index >= mSuggestions.size())
+ if (index >= mSuggestedWords.size())
return;
- final CharSequence word = mSuggestions.getWord(index);
+ final CharSequence word = mSuggestedWords.getWord(index);
mListener.pickSuggestionManually(index, word);
}