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.java55
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java234
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java22
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java5
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java27
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java103
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java44
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java41
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitator.java (renamed from java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java)412
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java3
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java200
-rw-r--r--java/src/com/android/inputmethod/latin/InputAttributes.java37
-rw-r--r--java/src/com/android/inputmethod/latin/InputView.java20
-rw-r--r--java/src/com/android/inputmethod/latin/LastComposedWord.java8
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java299
-rw-r--r--java/src/com/android/inputmethod/latin/LatinImeLogger.java74
-rw-r--r--java/src/com/android/inputmethod/latin/PrevWordsInfo.java62
-rw-r--r--java/src/com/android/inputmethod/latin/PunctuationSuggestions.java4
-rw-r--r--java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java32
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java107
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java5
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java12
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java75
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java56
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java101
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java110
-rw-r--r--java/src/com/android/inputmethod/latin/WordListInfo.java4
-rw-r--r--java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java3
-rw-r--r--java/src/com/android/inputmethod/latin/define/ProductionFlag.java10
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java402
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FormatSpec.java11
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/WordProperty.java9
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/AccountUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java59
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java28
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java1
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java37
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java19
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java9
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java29
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java52
-rw-r--r--java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java9
-rw-r--r--java/src/com/android/inputmethod/latin/settings/DebugSettings.java88
-rw-r--r--java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java7
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java13
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java58
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java58
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java7
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java6
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java6
-rw-r--r--java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java3
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java10
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java76
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java52
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java6
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java10
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java185
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java13
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java13
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java30
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java40
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java32
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java63
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java6
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java4
-rw-r--r--java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java3
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java9
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java43
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CollectionUtils.java76
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CsvUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java58
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java129
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java59
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java36
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FragmentUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java13
-rw-r--r--java/src/com/android/inputmethod/latin/utils/JsonUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java67
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java77
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LocaleUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java122
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResourceUtils.java7
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StatsUtils.java29
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java30
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java17
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java3
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java293
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java137
93 files changed, 2415 insertions, 2467 deletions
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 54bc29559..eb8b34ccd 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -16,14 +16,14 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.latin.settings.SettingsValues;
-
import android.content.Context;
import android.media.AudioManager;
import android.os.Vibrator;
import android.view.HapticFeedbackConstants;
import android.view.View;
+import com.android.inputmethod.latin.settings.SettingsValues;
+
/**
* This class gathers audio feedback and haptic feedback functions.
*
@@ -86,40 +86,41 @@ public final class AudioAndHapticFeedbackManager {
if (mAudioManager == null) {
return;
}
- if (mSoundOn) {
- final int sound;
- switch (code) {
- case Constants.CODE_DELETE:
- sound = AudioManager.FX_KEYPRESS_DELETE;
- break;
- case Constants.CODE_ENTER:
- sound = AudioManager.FX_KEYPRESS_RETURN;
- break;
- case Constants.CODE_SPACE:
- sound = AudioManager.FX_KEYPRESS_SPACEBAR;
- break;
- default:
- sound = AudioManager.FX_KEYPRESS_STANDARD;
- break;
- }
- mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume);
+ if (!mSoundOn) {
+ return;
+ }
+ final int sound;
+ switch (code) {
+ case Constants.CODE_DELETE:
+ sound = AudioManager.FX_KEYPRESS_DELETE;
+ break;
+ case Constants.CODE_ENTER:
+ sound = AudioManager.FX_KEYPRESS_RETURN;
+ break;
+ case Constants.CODE_SPACE:
+ sound = AudioManager.FX_KEYPRESS_SPACEBAR;
+ break;
+ default:
+ sound = AudioManager.FX_KEYPRESS_STANDARD;
+ break;
}
+ mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume);
}
public void performHapticFeedback(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);
- }
+ if (mSettingsValues.mKeypressVibrationDuration >= 0) {
+ vibrate(mSettingsValues.mKeypressVibrationDuration);
return;
}
- vibrate(mSettingsValues.mKeypressVibrationDuration);
+ // Go ahead with the system default
+ if (viewToPerformHapticFeedbackOn != null) {
+ viewToPerformHapticFeedbackOn.performHapticFeedback(
+ HapticFeedbackConstants.KEYBOARD_TAP,
+ HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
+ }
}
public void onSettingsChanged(final SettingsValues settingsValues) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b88509fde..543f74fc4 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -30,7 +30,6 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.makedict.WordProperty;
import com.android.inputmethod.latin.settings.NativeSuggestOptions;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.FileUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LanguageModelParam;
@@ -104,8 +103,7 @@ public final class BinaryDictionary extends Dictionary {
private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
- private final SparseArray<DicTraverseSession> mDicTraverseSessions =
- CollectionUtils.newSparseArray();
+ private final SparseArray<DicTraverseSession> mDicTraverseSessions = new SparseArray<>();
// TODO: There should be a way to remove used DicTraverseSession objects from
// {@code mDicTraverseSessions}.
@@ -113,11 +111,8 @@ public final class BinaryDictionary extends Dictionary {
synchronized(mDicTraverseSessions) {
DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
if (traverseSession == null) {
- traverseSession = mDicTraverseSessions.get(traverseSessionId);
- if (traverseSession == null) {
- traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
- mDicTraverseSessions.put(traverseSessionId, traverseSession);
- }
+ traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
+ mDicTraverseSessions.put(traverseSessionId, traverseSession);
}
return traverseSession;
}
@@ -188,13 +183,15 @@ public final class BinaryDictionary extends Dictionary {
private static native void getHeaderInfoNative(long dict, int[] outHeaderSize,
int[] outFormatVersion, ArrayList<int[]> outAttributeKeys,
ArrayList<int[]> outAttributeValues);
- private static native void flushNative(long dict, String filePath);
+ private static native boolean flushNative(long dict, String filePath);
private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
- private static native void flushWithGCNative(long dict, String filePath);
+ private static native boolean flushWithGCNative(long dict, String filePath);
private static native void closeNative(long dict);
private static native int getFormatVersionNative(long dict);
private static native int getProbabilityNative(long dict, int[] word);
- private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1);
+ private static native int getMaxProbabilityOfExactMatchesNative(long dict, int[] word);
+ private static native int getBigramProbabilityNative(long dict, int[] word0,
+ boolean isBeginningOfSentence, int[] word1);
private static native void getWordPropertyNative(long dict, int[] word,
int[] outCodePoints, boolean[] outFlags, int[] outProbabilityInfo,
ArrayList<int[]> outBigramTargets, ArrayList<int[]> outBigramProbabilityInfo,
@@ -203,21 +200,24 @@ public final class BinaryDictionary extends Dictionary {
private static native void getSuggestionsNative(long dict, long proximityInfo,
long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions,
- int[] prevWordCodePointArray, int[] outputSuggestionCount, int[] outputCodePoints,
- int[] outputScores, int[] outputIndices, int[] outputTypes,
- int[] outputAutoCommitFirstWordConfidence, float[] inOutLanguageWeight);
- private static native void addUnigramWordNative(long dict, int[] word, int probability,
- int[] shortcutTarget, int shortcutProbability, boolean isNotAWord,
- boolean isBlacklisted, int timestamp);
- private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
- int probability, int timestamp);
- private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
+ int[] prevWordCodePointArray, boolean isBeginningOfSentence,
+ int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores,
+ int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence,
+ float[] inOutLanguageWeight);
+ private static native boolean addUnigramWordNative(long dict, int[] word, int probability,
+ int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence,
+ boolean isNotAWord, boolean isBlacklisted, int timestamp);
+ private static native boolean removeUnigramWordNative(long dict, int[] word);
+ private static native boolean addBigramWordsNative(long dict, int[] word0,
+ boolean isBeginningOfSentence, int[] word1, int probability, int timestamp);
+ private static native boolean removeBigramWordsNative(long dict, int[] word0,
+ boolean isBeginningOfSentence, int[] word1);
private static native int addMultipleDictionaryEntriesNative(long dict,
LanguageModelParam[] languageModelParams, int startIndex);
- private static native int calculateProbabilityNative(long dict, int unigramProbability,
- int bigramProbability);
private static native String getPropertyNative(long dict, String query);
private static native boolean isCorruptedNative(long dict);
+ private static native boolean migrateNative(long dict, String dictFilePath,
+ long newFormatVersion);
// TODO: Move native dict into session
private final void loadDictionary(final String path, final long startOffset,
@@ -248,11 +248,11 @@ public final class BinaryDictionary extends Dictionary {
}
final int[] outHeaderSize = new int[1];
final int[] outFormatVersion = new int[1];
- final ArrayList<int[]> outAttributeKeys = CollectionUtils.newArrayList();
- final ArrayList<int[]> outAttributeValues = CollectionUtils.newArrayList();
+ final ArrayList<int[]> outAttributeKeys = new ArrayList<>();
+ final ArrayList<int[]> outAttributeValues = new ArrayList<>();
getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys,
outAttributeValues);
- final HashMap<String, String> attributes = new HashMap<String, String>();
+ final HashMap<String, String> attributes = new HashMap<>();
for (int i = 0; i < outAttributeKeys.size(); i++) {
final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray(
outAttributeKeys.get(i));
@@ -266,19 +266,9 @@ public final class BinaryDictionary extends Dictionary {
new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo));
}
-
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final float[] inOutLanguageWeight) {
- return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight);
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId, final float[] inOutLanguageWeight) {
if (!isValidDictionary()) {
@@ -287,8 +277,8 @@ public final class BinaryDictionary extends Dictionary {
Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
// TODO: toLowerCase in the native code
- final int[] prevWordCodePointArray = (null == prevWord)
- ? null : StringUtils.toCodePointArray(prevWord);
+ final int[] prevWordCodePointArray = (null == prevWordsInfo.mPrevWord)
+ ? null : StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
final InputPointers inputPointers = composer.getInputPointers();
final boolean isGesture = composer.isBatchMode();
final int inputSize;
@@ -303,6 +293,7 @@ public final class BinaryDictionary extends Dictionary {
}
mNativeSuggestOptions.setIsGesture(isGesture);
+ mNativeSuggestOptions.setBlockOffensiveWords(blockOffensiveWords);
mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions);
if (inOutLanguageWeight != null) {
mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
@@ -314,14 +305,15 @@ public final class BinaryDictionary extends Dictionary {
getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(),
inputPointers.getYCoordinates(), inputPointers.getTimes(),
inputPointers.getPointerIds(), mInputCodePoints, inputSize,
- mNativeSuggestOptions.getOptions(), prevWordCodePointArray, mOutputSuggestionCount,
+ mNativeSuggestOptions.getOptions(), prevWordCodePointArray,
+ prevWordsInfo.mIsBeginningOfSentence, mOutputSuggestionCount,
mOutputCodePoints, mOutputScores, mSpaceIndices, mOutputTypes,
mOutputAutoCommitFirstWordConfidence, mInputOutputLanguageWeight);
if (inOutLanguageWeight != null) {
inOutLanguageWeight[0] = mInputOutputLanguageWeight[0];
}
final int count = mOutputSuggestionCount[0];
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
for (int j = 0; j < count; ++j) {
final int start = j * MAX_WORD_LENGTH;
int len = 0;
@@ -329,21 +321,8 @@ public final class BinaryDictionary extends Dictionary {
++len;
}
if (len > 0) {
- final int flags = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_FLAGS;
- if (blockOffensiveWords
- && 0 != (flags & SuggestedWordInfo.KIND_FLAG_POSSIBLY_OFFENSIVE)
- && 0 == (flags & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH)) {
- // If we block potentially offensive words, and if the word is possibly
- // offensive, then we don't output it unless it's also an exact match.
- continue;
- }
- final int kind = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_KIND;
- final int score = SuggestedWordInfo.KIND_WHITELIST == kind
- ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
- // TODO: check that all users of the `kind' parameter are ready to accept
- // flags too and pass mOutputTypes[j] instead of kind
suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
- score, kind, this /* sourceDict */,
+ mOutputScores[j], mOutputTypes[j], this /* sourceDict */,
mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
mOutputAutoCommitFirstWordConfidence[0]));
}
@@ -360,28 +339,37 @@ public final class BinaryDictionary extends Dictionary {
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
return getFrequency(word) != NOT_A_PROBABILITY;
}
@Override
public int getFrequency(final String word) {
- if (word == null) return NOT_A_PROBABILITY;
+ if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
int[] codePoints = StringUtils.toCodePointArray(word);
return getProbabilityNative(mNativeDict, codePoints);
}
- // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
- // calls when checking for changes in an entire dictionary.
- public boolean isValidBigram(final String word0, final String word1) {
- return getBigramProbability(word0, word1) != NOT_A_PROBABILITY;
+ @Override
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
+ int[] codePoints = StringUtils.toCodePointArray(word);
+ return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints);
}
- public int getBigramProbability(final String word0, final String word1) {
- if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) return NOT_A_PROBABILITY;
- final int[] codePoints0 = StringUtils.toCodePointArray(word0);
- final int[] codePoints1 = StringUtils.toCodePointArray(word1);
- return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1);
+ @UsedForTesting
+ public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
+ return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
+ }
+
+ public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
+ if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ return NOT_A_PROBABILITY;
+ }
+ final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
+ final int[] codePoints1 = StringUtils.toCodePointArray(word);
+ return getBigramProbabilityNative(mNativeDict, codePoints0,
+ prevWordsInfo.mIsBeginningOfSentence, codePoints1);
}
public WordProperty getWordProperty(final String word) {
@@ -393,10 +381,10 @@ public final class BinaryDictionary extends Dictionary {
final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT];
final int[] outProbabilityInfo =
new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT];
- final ArrayList<int[]> outBigramTargets = CollectionUtils.newArrayList();
- final ArrayList<int[]> outBigramProbabilityInfo = CollectionUtils.newArrayList();
- final ArrayList<int[]> outShortcutTargets = CollectionUtils.newArrayList();
- final ArrayList<Integer> outShortcutProbabilities = CollectionUtils.newArrayList();
+ final ArrayList<int[]> outBigramTargets = new ArrayList<>();
+ final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>();
+ final ArrayList<int[]> outShortcutTargets = new ArrayList<>();
+ final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>();
getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo,
outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
outShortcutProbabilities);
@@ -413,8 +401,8 @@ public final class BinaryDictionary extends Dictionary {
public WordProperty mWordProperty;
public int mNextToken;
- public GetNextWordPropertyResult(final WordProperty wordPreperty, final int nextToken) {
- mWordProperty = wordPreperty;
+ public GetNextWordPropertyResult(final WordProperty wordProperty, final int nextToken) {
+ mWordProperty = wordProperty;
mNextToken = nextToken;
}
}
@@ -431,41 +419,66 @@ public final class BinaryDictionary extends Dictionary {
}
// Add a unigram entry to binary dictionary with unigram attributes in native code.
- public void addUnigramWord(final String word, final int probability,
- final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord,
+ public boolean addUnigramEntry(final String word, final int probability,
+ final String shortcutTarget, final int shortcutProbability,
+ final boolean isBeginningOfSentence, final boolean isNotAWord,
final boolean isBlacklisted, final int timestamp) {
- if (TextUtils.isEmpty(word)) {
- return;
+ if (word == null || (word.isEmpty() && !isBeginningOfSentence)) {
+ return false;
}
final int[] codePoints = StringUtils.toCodePointArray(word);
final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
StringUtils.toCodePointArray(shortcutTarget) : null;
- addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
- shortcutProbability, isNotAWord, isBlacklisted, timestamp);
+ if (!addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
+ shortcutProbability, isBeginningOfSentence, isNotAWord, isBlacklisted, timestamp)) {
+ return false;
+ }
mHasUpdated = true;
+ return true;
}
- // Add a bigram entry to binary dictionary with timestamp in native code.
- public void addBigramWords(final String word0, final String word1, final int probability,
- final int timestamp) {
- if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
- return;
+ // Remove a unigram entry from the binary dictionary in native code.
+ public boolean removeUnigramEntry(final String word) {
+ if (TextUtils.isEmpty(word)) {
+ return false;
+ }
+ final int[] codePoints = StringUtils.toCodePointArray(word);
+ if (!removeUnigramWordNative(mNativeDict, codePoints)) {
+ return false;
}
- final int[] codePoints0 = StringUtils.toCodePointArray(word0);
- final int[] codePoints1 = StringUtils.toCodePointArray(word1);
- addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp);
mHasUpdated = true;
+ return true;
}
- // Remove a bigram entry form binary dictionary in native code.
- public void removeBigramWords(final String word0, final String word1) {
- if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
- return;
+ // Add an n-gram entry to the binary dictionary with timestamp in native code.
+ public boolean addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
+ final int probability, final int timestamp) {
+ if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ return false;
+ }
+ final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
+ final int[] codePoints1 = StringUtils.toCodePointArray(word);
+ if (!addBigramWordsNative(mNativeDict, codePoints0, prevWordsInfo.mIsBeginningOfSentence,
+ codePoints1, probability, timestamp)) {
+ return false;
}
- final int[] codePoints0 = StringUtils.toCodePointArray(word0);
- final int[] codePoints1 = StringUtils.toCodePointArray(word1);
- removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
mHasUpdated = true;
+ return true;
+ }
+
+ // Remove an n-gram entry from the binary dictionary in native code.
+ public boolean removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) {
+ if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
+ return false;
+ }
+ final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
+ final int[] codePoints1 = StringUtils.toCodePointArray(word);
+ if (!removeBigramWordsNative(mNativeDict, codePoints0, prevWordsInfo.mIsBeginningOfSentence,
+ codePoints1)) {
+ return false;
+ }
+ mHasUpdated = true;
+ return true;
}
public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
@@ -495,26 +508,33 @@ public final class BinaryDictionary extends Dictionary {
}
// Flush to dict file if the dictionary has been updated.
- public void flush() {
- if (!isValidDictionary()) return;
+ public boolean flush() {
+ if (!isValidDictionary()) return false;
if (mHasUpdated) {
- flushNative(mNativeDict, mDictFilePath);
+ if (!flushNative(mNativeDict, mDictFilePath)) {
+ return false;
+ }
reopen();
}
+ return true;
}
// Run GC and flush to dict file if the dictionary has been updated.
- public void flushWithGCIfHasUpdated() {
+ public boolean flushWithGCIfHasUpdated() {
if (mHasUpdated) {
- flushWithGC();
+ return flushWithGC();
}
+ return true;
}
// Run GC and flush to dict file.
- public void flushWithGC() {
- if (!isValidDictionary()) return;
- flushWithGCNative(mNativeDict, mDictFilePath);
+ public boolean flushWithGC() {
+ if (!isValidDictionary()) return false;
+ if (!flushWithGCNative(mNativeDict, mDictFilePath)) {
+ return false;
+ }
reopen();
+ return true;
}
/**
@@ -533,11 +553,15 @@ public final class BinaryDictionary extends Dictionary {
return false;
}
final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION;
- // TODO: Implement migrateNative(tmpDictFilePath, newFormatVersion).
+ if (!migrateNative(mNativeDict, tmpDictFilePath, newFormatVersion)) {
+ return false;
+ }
close();
final File dictFile = new File(mDictFilePath);
final File tmpDictFile = new File(tmpDictFilePath);
- FileUtils.deleteRecursively(dictFile);
+ if (!FileUtils.deleteRecursively(dictFile)) {
+ return false;
+ }
if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) {
return false;
}
@@ -547,12 +571,6 @@ public final class BinaryDictionary extends Dictionary {
}
@UsedForTesting
- public int calculateProbability(final int unigramProbability, final int bigramProbability) {
- if (!isValidDictionary()) return NOT_A_PROBABILITY;
- return calculateProbabilityNative(mNativeDict, unigramProbability, bigramProbability);
- }
-
- @UsedForTesting
public String getPropertyForTest(final String query) {
if (!isValidDictionary()) return "";
return getPropertyNative(mNativeDict, query);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index e428b1d54..10b1f1b77 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -28,7 +28,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
-import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.dictionarypack.MD5Calculator;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils.DictionaryInfo;
import com.android.inputmethod.latin.utils.FileTransforms;
@@ -38,12 +38,13 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -163,12 +164,13 @@ public final class BinaryDictionaryFileDumper {
if (cursor.getCount() <= 0 || !cursor.moveToFirst()) {
return Collections.<WordListInfo>emptyList();
}
- final ArrayList<WordListInfo> list = CollectionUtils.newArrayList();
+ final ArrayList<WordListInfo> list = new ArrayList<>();
do {
final String wordListId = cursor.getString(0);
final String wordListLocale = cursor.getString(1);
+ final String wordListRawChecksum = cursor.getString(2);
if (TextUtils.isEmpty(wordListId)) continue;
- list.add(new WordListInfo(wordListId, wordListLocale));
+ list.add(new WordListInfo(wordListId, wordListLocale, wordListRawChecksum));
} while (cursor.moveToNext());
return list;
} catch (RemoteException e) {
@@ -217,7 +219,8 @@ public final class BinaryDictionaryFileDumper {
* and creating it (and its containing directory) if necessary.
*/
private static void cacheWordList(final String wordlistId, final String locale,
- final ContentProviderClient providerClient, final Context context) {
+ final String rawChecksum, final ContentProviderClient providerClient,
+ final Context context) {
final int COMPRESSED_CRYPTED_COMPRESSED = 0;
final int CRYPTED_COMPRESSED = 1;
final int COMPRESSED_CRYPTED = 2;
@@ -299,6 +302,13 @@ public final class BinaryDictionaryFileDumper {
checkMagicAndCopyFileTo(bufferedInputStream, bufferedOutputStream);
bufferedOutputStream.flush();
bufferedOutputStream.close();
+ final String actualRawChecksum = MD5Calculator.checksum(
+ new BufferedInputStream(new FileInputStream(outputFile)));
+ Log.i(TAG, "Computed checksum for downloaded dictionary. Expected = " + rawChecksum
+ + " ; actual = " + actualRawChecksum);
+ if (!TextUtils.isEmpty(rawChecksum) && !rawChecksum.equals(actualRawChecksum)) {
+ throw new IOException("Could not decode the file correctly : checksum differs");
+ }
final File finalFile = new File(finalFileName);
finalFile.delete();
if (!outputFile.renameTo(finalFile)) {
@@ -408,7 +418,7 @@ public final class BinaryDictionaryFileDumper {
final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
hasDefaultWordList);
for (WordListInfo id : idList) {
- cacheWordList(id.mId, id.mLocale, providerClient, context);
+ cacheWordList(id.mId, id.mLocale, id.mRawChecksum, providerClient, context);
}
} finally {
providerClient.release();
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 4c49cb31c..867c18686 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -24,7 +24,6 @@ import android.util.Log;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
@@ -160,7 +159,7 @@ final public class BinaryDictionaryGetter {
public static File[] getCachedWordLists(final String locale, final Context context) {
final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context);
if (null == directoryList) return EMPTY_FILE_ARRAY;
- final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
+ final HashMap<String, FileAndMatchLevel> cacheFiles = new HashMap<>();
for (File directory : directoryList) {
if (!directory.isDirectory()) continue;
final String dirLocale =
@@ -273,7 +272,7 @@ final public class BinaryDictionaryGetter {
final DictPackSettings dictPackSettings = new DictPackSettings(context);
boolean foundMainDict = false;
- final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
+ final ArrayList<AssetFileAddress> fileList = new ArrayList<>();
// cachedWordLists may not be null, see doc for getCachedDictionaryList
for (final File f : cachedWordLists) {
final String wordListId = DictionaryInfoUtils.getWordListIdFromFileName(f.getName());
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index e71723a15..fa51436de 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -115,6 +115,11 @@ public final class Constants {
*/
public static final String IS_ADDITIONAL_SUBTYPE = "isAdditionalSubtype";
+ /**
+ * The subtype extra value used to specify the combining rules.
+ */
+ public static final String COMBINING_RULES = "CombiningRules";
+
private ExtraValue() {
// This utility class is not publicly instantiable.
}
@@ -153,6 +158,10 @@ public final class Constants {
// A hint on how many characters to cache from the TextView. A good value of this is given by
// how many characters we need to be able to almost always find the caps mode.
public static final int EDITOR_CONTENTS_CACHE_SIZE = 1024;
+ // How many characters we accept for the recapitalization functionality. This needs to be
+ // large enough for all reasonable purposes, but avoid purposeful attacks. 100k sounds about
+ // right for this.
+ public static final int MAX_CHARACTERS_FOR_RECAPITALIZATION = 1024 * 100;
// Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
@@ -164,6 +173,8 @@ public final class Constants {
// How many continuous deletes at which to start deleting at a higher speed.
public static final int DELETE_ACCELERATE_AT = 20;
+ public static final String WORD_SEPARATOR = " ";
+
public static boolean isValidCoordinate(final int coordinate) {
// Detect {@link NOT_A_COORDINATE}, {@link SUGGESTION_STRIP_COORDINATE},
// and {@link SPELL_CHECKER_COORDINATE}.
@@ -185,7 +196,6 @@ public final class Constants {
public static final int CODE_SPACE = ' ';
public static final int CODE_PERIOD = '.';
public static final int CODE_COMMA = ',';
- public static final int CODE_ARMENIAN_PERIOD = 0x0589;
public static final int CODE_DASH = '-';
public static final int CODE_SINGLE_QUOTE = '\'';
public static final int CODE_DOUBLE_QUOTE = '"';
@@ -201,6 +211,11 @@ public final class Constants {
public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
public static final int CODE_CLOSING_CURLY_BRACKET = '}';
public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
+ public static final int CODE_INVERTED_QUESTION_MARK = 0xBF; // ¿
+ public static final int CODE_INVERTED_EXCLAMATION_MARK = 0xA1; // ¡
+
+ public static final String REGEXP_PERIOD = "\\.";
+ public static final String STRING_SPACE = " ";
/**
* Special keys code. Must be negative.
@@ -242,14 +257,16 @@ public final class Constants {
case CODE_LANGUAGE_SWITCH: return "languageSwitch";
case CODE_EMOJI: return "emoji";
case CODE_SHIFT_ENTER: return "shiftEnter";
+ case CODE_ALPHA_FROM_EMOJI: return "alpha";
case CODE_UNSPECIFIED: return "unspec";
case CODE_TAB: return "tab";
case CODE_ENTER: return "enter";
- case CODE_ALPHA_FROM_EMOJI: return "alpha";
+ case CODE_SPACE: return "space";
default:
- if (code < CODE_SPACE) return String.format("'\\u%02x'", code);
- if (code < 0x100) return String.format("'%c'", code);
- return String.format("'\\u%04x'", code);
+ if (code < CODE_SPACE) return String.format("\\u%02x", code);
+ if (code < 0x100) return String.format("%c", code);
+ if (code < 0x10000) return String.format("\\u04x", code);
+ return String.format("\\U%05x", code);
}
}
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 09d0ea210..96160fa4e 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -29,10 +29,13 @@ import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.personalization.AccountUtils;
+import com.android.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import java.io.File;
+import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -59,10 +62,10 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private static final int INDEX_NAME = 1;
/** The number of contacts in the most recent dictionary rebuild. */
- static private int sContactCountAtLastRebuild = 0;
+ private int mContactCountAtLastRebuild = 0;
- /** The locale for this contacts dictionary. Controls name bigram predictions. */
- public final Locale mLocale;
+ /** The hash code of ArrayList of contacts names in the most recent dictionary rebuild. */
+ private int mHashCodeAtLastRebuild = 0;
private ContentObserver mObserver;
@@ -71,25 +74,21 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
*/
private final boolean mUseFirstLastBigrams;
- public ContactsBinaryDictionary(final Context context, final Locale locale) {
- this(context, locale, null /* dictFile */);
- }
-
- public ContactsBinaryDictionary(final Context context, final Locale locale,
- final File dictFile) {
- this(context, locale, dictFile, NAME);
- }
-
protected ContactsBinaryDictionary(final Context context, final Locale locale,
final File dictFile, final String name) {
super(context, getDictName(name, locale, dictFile), locale, Dictionary.TYPE_CONTACTS,
dictFile);
- mLocale = locale;
mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale);
registerObserver(context);
reloadDictionaryIfRequired();
}
+ @UsedForTesting
+ public static ContactsBinaryDictionary getDictionary(final Context context, final Locale locale,
+ final File dictFile, final String dictNamePrefix) {
+ return new ContactsBinaryDictionary(context, locale, dictFile, dictNamePrefix + NAME);
+ }
+
private synchronized void registerObserver(final Context context) {
if (mObserver != null) return;
ContentResolver cres = context.getContentResolver();
@@ -97,7 +96,14 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
new ContentObserver(null) {
@Override
public void onChange(boolean self) {
- setNeedsToReload();
+ ExecutorUtils.getExecutor("Check Contacts").execute(new Runnable() {
+ @Override
+ public void run() {
+ if (haveContentsChanged()) {
+ setNeedsToRecreate();
+ }
+ }
+ });
}
});
}
@@ -130,7 +136,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
Log.d(TAG, "loadAccountVocabulary: " + word);
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addWordDynamicallyLocked(word, FREQUENCY_FOR_CONTACTS, null /* shortcut */,
+ addUnigramLocked(word, FREQUENCY_FOR_CONTACTS, null /* shortcut */,
0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */,
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
@@ -144,7 +150,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
return;
}
if (cursor.moveToFirst()) {
- sContactCountAtLastRebuild = getContactCount();
+ mContactCountAtLastRebuild = getContactCount();
addWordsLocked(cursor);
}
} catch (final SQLiteException e) {
@@ -168,9 +174,11 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
private void addWordsLocked(final Cursor cursor) {
int count = 0;
+ final ArrayList<String> names = new ArrayList<>();
while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) {
String name = cursor.getString(INDEX_NAME);
if (isValidName(name)) {
+ names.add(name);
addNameLocked(name);
++count;
} else {
@@ -180,6 +188,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
}
cursor.moveToNext();
}
+ mHashCodeAtLastRebuild = names.hashCode();
}
private int getContactCount() {
@@ -209,7 +218,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
*/
private void addNameLocked(final String name) {
int len = StringUtils.codePointCount(name);
- String prevWord = null;
+ PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
// TODO: Better tokenization for non-Latin writing systems
for (int i = 0; i < len; i++) {
if (Character.isLetter(name.codePointAt(i))) {
@@ -224,19 +233,19 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
final int wordLen = StringUtils.codePointCount(word);
if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
if (DEBUG) {
- Log.d(TAG, "addName " + name + ", " + word + ", " + prevWord);
+ Log.d(TAG, "addName " + name + ", " + word + ", "
+ + prevWordsInfo.mPrevWord);
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addWordDynamicallyLocked(word, FREQUENCY_FOR_CONTACTS,
+ addUnigramLocked(word, FREQUENCY_FOR_CONTACTS,
null /* shortcut */, 0 /* shortcutFreq */, false /* isNotAWord */,
false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
- if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
+ if (!TextUtils.isEmpty(prevWordsInfo.mPrevWord) && mUseFirstLastBigrams) {
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addBigramDynamicallyLocked(prevWord, word,
- FREQUENCY_FOR_CONTACTS_BIGRAM,
+ addNgramEntryLocked(prevWordsInfo, word, FREQUENCY_FOR_CONTACTS_BIGRAM,
BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
- prevWord = word;
+ prevWordsInfo = new PrevWordsInfo(word);
}
}
}
@@ -259,8 +268,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
return end;
}
- @Override
- protected boolean haveContentsChanged() {
+ private boolean haveContentsChanged() {
final long startTime = SystemClock.uptimeMillis();
final int contactCount = getContactCount();
if (contactCount > MAX_CONTACT_COUNT) {
@@ -269,9 +277,9 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
// TODO: Sort and check only the MAX_CONTACT_COUNT most recent contacts?
return false;
}
- if (contactCount != sContactCountAtLastRebuild) {
+ if (contactCount != mContactCountAtLastRebuild) {
if (DEBUG) {
- Log.d(TAG, "Contact count changed: " + sContactCountAtLastRebuild + " to "
+ Log.d(TAG, "Contact count changed: " + mContactCountAtLastRebuild + " to "
+ contactCount);
}
return true;
@@ -284,20 +292,20 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
if (null == cursor) {
return false;
}
+ final ArrayList<String> names = new ArrayList<>();
try {
if (cursor.moveToFirst()) {
while (!cursor.isAfterLast()) {
String name = cursor.getString(INDEX_NAME);
- if (isValidName(name) && !isNameInDictionaryLocked(name)) {
- if (DEBUG) {
- Log.d(TAG, "Contact name missing: " + name + " (runtime = "
- + (SystemClock.uptimeMillis() - startTime) + " ms)");
- }
- return true;
+ if (isValidName(name)) {
+ names.add(name);
}
cursor.moveToNext();
}
}
+ if (names.hashCode() != mHashCodeAtLastRebuild) {
+ return true;
+ }
} finally {
cursor.close();
}
@@ -314,33 +322,4 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
}
return false;
}
-
- /**
- * Checks if the words in a name are in the current binary dictionary.
- */
- private boolean isNameInDictionaryLocked(final String name) {
- int len = StringUtils.codePointCount(name);
- String prevWord = null;
- for (int i = 0; i < len; i++) {
- if (Character.isLetter(name.codePointAt(i))) {
- int end = getWordEndPosition(name, len, i);
- String word = name.substring(i, end);
- i = end - 1;
- final int wordLen = StringUtils.codePointCount(word);
- if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
- if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) {
- if (!isValidBigramLocked(prevWord, word)) {
- return false;
- }
- } else {
- if (!isValidWordLocked(word)) {
- return false;
- }
- }
- prevWord = word;
- }
- }
- }
- return true;
- }
}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 0742fbde9..b55ed125f 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -57,6 +58,8 @@ public abstract class Dictionary {
public static final String TYPE_USER_HISTORY = "history";
// Personalization dictionary.
public static final String TYPE_PERSONALIZATION = "personalization";
+ // Contextual dictionary.
+ public static final String TYPE_CONTEXTUAL = "contextual";
public final String mDictType;
public Dictionary(final String dictType) {
@@ -67,43 +70,44 @@ public abstract class Dictionary {
* Searches for suggestions for a given context. For the moment the context is only the
* previous word.
* @param composer the key sequence to match with coordinate info, as a WordComposer
- * @param prevWord the previous word, or null if none
+ * @param prevWordsInfo the information of previous words.
* @param proximityInfo the object for key proximity. May be ignored by some implementations.
* @param blockOffensiveWords whether to block potentially offensive words
* @param additionalFeaturesOptions options about additional features used for the suggestion.
+ * @param sessionId the session id.
* @param inOutLanguageWeight the language weight used for generating suggestions.
* inOutLanguageWeight is a float array that has only one element. This can be updated when the
* different language weight is used.
* @return the list of suggestions (possibly null if none)
*/
- // TODO: pass more context than just the previous word, to enable better suggestions (n-gram
- // and more)
abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final float[] inOutLanguageWeight);
+ final int sessionId, final float[] inOutLanguageWeight);
- // The default implementation of this method ignores sessionId.
- // Subclasses that want to use sessionId need to override this method.
- public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final int sessionId, final float[] inOutLanguageWeight) {
- return getSuggestions(composer, prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, inOutLanguageWeight);
+ /**
+ * Checks if the given word has to be treated as a valid word. Please note that some
+ * dictionaries have entries that should be treated as invalid words.
+ * @param word the word to search for. The search should be case-insensitive.
+ * @return true if the word is valid, false otherwise
+ */
+ public boolean isValidWord(final String word) {
+ return isInDictionary(word);
}
/**
- * Checks if the given word occurs in the dictionary
- * @param word the word to search for. The search should be case-insensitive.
- * @return true if the word exists, false otherwise
+ * Checks if the given word is in the dictionary regardless of it being valid or not.
*/
- abstract public boolean isValidWord(final String word);
+ abstract public boolean isInDictionary(final String word);
public int getFrequency(final String word) {
return NOT_A_PROBABILITY;
}
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ return NOT_A_PROBABILITY;
+ }
+
/**
* Compares the contents of the character array with the typed word and returns true if they
* are the same.
@@ -163,14 +167,14 @@ public abstract class Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final float[] inOutLanguageWeight) {
+ final int sessionId, final float[] inOutLanguageWeight) {
return null;
}
@Override
- public boolean isValidWord(String word) {
+ public boolean isInDictionary(String word) {
return false;
}
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 16173fffc..89d61ce2a 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -20,7 +20,6 @@ import android.util.Log;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
@@ -36,52 +35,52 @@ public final class DictionaryCollection extends Dictionary {
public DictionaryCollection(final String dictType) {
super(dictType);
- mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
+ mDictionaries = new CopyOnWriteArrayList<>();
}
public DictionaryCollection(final String dictType, final Dictionary... dictionaries) {
super(dictType);
if (null == dictionaries) {
- mDictionaries = CollectionUtils.newCopyOnWriteArrayList();
+ mDictionaries = new CopyOnWriteArrayList<>();
} else {
- mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
+ mDictionaries = new CopyOnWriteArrayList<>(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
}
public DictionaryCollection(final String dictType, final Collection<Dictionary> dictionaries) {
super(dictType);
- mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries);
+ mDictionaries = new CopyOnWriteArrayList<>(dictionaries);
mDictionaries.removeAll(Collections.singleton(null));
}
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final float[] inOutLanguageWeight) {
+ final int sessionId, final float[] inOutLanguageWeight) {
final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries;
if (dictionaries.isEmpty()) return null;
// To avoid creating unnecessary objects, we get the list out of the first
// dictionary and add the rest to it if not null, hence the get(0)
ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer,
- prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
- inOutLanguageWeight);
- if (null == suggestions) suggestions = CollectionUtils.newArrayList();
+ prevWordsInfo, proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
+ sessionId, inOutLanguageWeight);
+ if (null == suggestions) suggestions = new ArrayList<>();
final int length = dictionaries.size();
for (int i = 1; i < length; ++ i) {
final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer,
- prevWord, proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
- inOutLanguageWeight);
+ prevWordsInfo, proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
+ sessionId, inOutLanguageWeight);
if (null != sugg) suggestions.addAll(sugg);
}
return suggestions;
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
for (int i = mDictionaries.size() - 1; i >= 0; --i)
- if (mDictionaries.get(i).isValidWord(word)) return true;
+ if (mDictionaries.get(i).isInDictionary(word)) return true;
return false;
}
@@ -90,9 +89,17 @@ public final class DictionaryCollection extends Dictionary {
int maxFreq = -1;
for (int i = mDictionaries.size() - 1; i >= 0; --i) {
final int tempFreq = mDictionaries.get(i).getFrequency(word);
- if (tempFreq >= maxFreq) {
- maxFreq = tempFreq;
- }
+ maxFreq = Math.max(tempFreq, maxFreq);
+ }
+ return maxFreq;
+ }
+
+ @Override
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ int maxFreq = -1;
+ for (int i = mDictionaries.size() - 1; i >= 0; --i) {
+ final int tempFreq = mDictionaries.get(i).getMaxFrequencyOfExactMatches(word);
+ maxFreq = Math.max(tempFreq, maxFreq);
}
return maxFreq;
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 5238395a4..e6e2bcbc7 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -19,21 +19,30 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
+import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.ContextualDictionary;
+import com.android.inputmethod.latin.personalization.PersonalizationDataChunk;
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
-import com.android.inputmethod.latin.personalization.PersonalizationHelper;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
-import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary;
import com.android.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.LanguageModelParam;
import com.android.inputmethod.latin.utils.SuggestionResults;
import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -41,80 +50,93 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
// TODO: Consolidate dictionaries in native code.
-public class DictionaryFacilitatorForSuggest {
- public static final String TAG = DictionaryFacilitatorForSuggest.class.getSimpleName();
+public class DictionaryFacilitator {
+ public static final String TAG = DictionaryFacilitator.class.getSimpleName();
// HACK: This threshold is being used when adding a capitalized entry in the User History
// dictionary.
private static final int CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT = 140;
private Dictionaries mDictionaries = new Dictionaries();
+ private boolean mIsUserDictEnabled = false;
private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
// To synchronize assigning mDictionaries to ensure closing dictionaries.
- private Object mLock = new Object();
+ private final Object mLock = new Object();
+ private final DistracterFilter mDistracterFilter;
- private static final String[] dictTypesOrderedToGetSuggestion =
+ private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS =
new String[] {
Dictionary.TYPE_MAIN,
Dictionary.TYPE_USER_HISTORY,
Dictionary.TYPE_PERSONALIZATION,
Dictionary.TYPE_USER,
- Dictionary.TYPE_CONTACTS
+ Dictionary.TYPE_CONTACTS,
+ Dictionary.TYPE_CONTEXTUAL
};
+ public static final Map<String, Class<? extends ExpandableBinaryDictionary>>
+ DICT_TYPE_TO_CLASS = new HashMap<>();
+
+ static {
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_PERSONALIZATION, PersonalizationDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTEXTUAL, ContextualDictionary.class);
+ }
+
+ private static final String DICT_FACTORY_METHOD_NAME = "getDictionary";
+ private static final Class<?>[] DICT_FACTORY_METHOD_ARG_TYPES =
+ new Class[] { Context.class, Locale.class, File.class, String.class };
+
+ private static final String[] SUB_DICT_TYPES =
+ Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS, 1 /* start */,
+ DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length);
+
/**
* Class contains dictionaries for a locale.
*/
private static class Dictionaries {
public final Locale mLocale;
- public final ConcurrentHashMap<String, Dictionary> mDictMap =
- CollectionUtils.newConcurrentHashMap();
+ private Dictionary mMainDict;
public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
- CollectionUtils.newConcurrentHashMap();
- // TODO: Remove sub dictionary members and use mSubDictMap.
- public final UserBinaryDictionary mUserDictionary;
+ new ConcurrentHashMap<>();
public Dictionaries() {
mLocale = null;
- mUserDictionary = null;
}
public Dictionaries(final Locale locale, final Dictionary mainDict,
- final ExpandableBinaryDictionary contactsDict, final UserBinaryDictionary userDict,
- final ExpandableBinaryDictionary userHistoryDict,
- final ExpandableBinaryDictionary personalizationDict) {
+ final Map<String, ExpandableBinaryDictionary> subDicts) {
mLocale = locale;
// Main dictionary can be asynchronously loaded.
setMainDict(mainDict);
- setSubDict(Dictionary.TYPE_CONTACTS, contactsDict);
- mUserDictionary = userDict;
- setSubDict(Dictionary.TYPE_USER, mUserDictionary);
- setSubDict(Dictionary.TYPE_USER_HISTORY, userHistoryDict);
- setSubDict(Dictionary.TYPE_PERSONALIZATION, personalizationDict);
+ for (final Map.Entry<String, ExpandableBinaryDictionary> entry : subDicts.entrySet()) {
+ setSubDict(entry.getKey(), entry.getValue());
+ }
}
private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) {
if (dict != null) {
- mDictMap.put(dictType, dict);
mSubDictMap.put(dictType, dict);
}
}
public void setMainDict(final Dictionary mainDict) {
// Close old dictionary if exists. Main dictionary can be assigned multiple times.
- final Dictionary oldDict;
- if (mainDict != null) {
- oldDict = mDictMap.put(Dictionary.TYPE_MAIN, mainDict);
- } else {
- oldDict = mDictMap.remove(Dictionary.TYPE_MAIN);
- }
+ final Dictionary oldDict = mMainDict;
+ mMainDict = mainDict;
if (oldDict != null && mainDict != oldDict) {
oldDict.close();
}
}
- public Dictionary getMainDict() {
- return mDictMap.get(Dictionary.TYPE_MAIN);
+ public Dictionary getDict(final String dictType) {
+ if (Dictionary.TYPE_MAIN.equals(dictType)) {
+ return mMainDict;
+ } else {
+ return getSubDict(dictType);
+ }
}
public ExpandableBinaryDictionary getSubDict(final String dictType) {
@@ -122,12 +144,20 @@ public class DictionaryFacilitatorForSuggest {
}
public boolean hasDict(final String dictType) {
- return mDictMap.containsKey(dictType);
+ if (Dictionary.TYPE_MAIN.equals(dictType)) {
+ return mMainDict != null;
+ } else {
+ return mSubDictMap.containsKey(dictType);
+ }
}
public void closeDict(final String dictType) {
- final Dictionary dict = mDictMap.remove(dictType);
- mSubDictMap.remove(dictType);
+ final Dictionary dict;
+ if (Dictionary.TYPE_MAIN.equals(dictType)) {
+ dict = mMainDict;
+ } else {
+ dict = mSubDictMap.remove(dictType);
+ }
if (dict != null) {
dict.close();
}
@@ -138,80 +168,104 @@ public class DictionaryFacilitatorForSuggest {
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
}
- public DictionaryFacilitatorForSuggest() {}
+ public DictionaryFacilitator() {
+ mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER;
+ }
+
+ public DictionaryFacilitator(final DistracterFilter distracterFilter) {
+ mDistracterFilter = distracterFilter;
+ }
+
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
+ mDistracterFilter.updateEnabledSubtypes(enabledSubtypes);
+ }
public Locale getLocale() {
return mDictionaries.mLocale;
}
+ private static ExpandableBinaryDictionary getSubDict(final String dictType,
+ final Context context, final Locale locale, final File dictFile,
+ final String dictNamePrefix) {
+ final Class<? extends ExpandableBinaryDictionary> dictClass =
+ DICT_TYPE_TO_CLASS.get(dictType);
+ if (dictClass == null) {
+ return null;
+ }
+ try {
+ final Method factoryMethod = dictClass.getMethod(DICT_FACTORY_METHOD_NAME,
+ DICT_FACTORY_METHOD_ARG_TYPES);
+ final Object dict = factoryMethod.invoke(null /* obj */,
+ new Object[] { context, locale, dictFile, dictNamePrefix });
+ return (ExpandableBinaryDictionary) dict;
+ } catch (final NoSuchMethodException | SecurityException | IllegalAccessException
+ | IllegalArgumentException | InvocationTargetException e) {
+ Log.e(TAG, "Cannot create dictionary: " + dictType, e);
+ return null;
+ }
+ }
+
public void resetDictionaries(final Context context, final Locale newLocale,
final boolean useContactsDict, final boolean usePersonalizedDicts,
final boolean forceReloadMainDictionary,
final DictionaryInitializationListener listener) {
+ resetDictionariesWithDictNamePrefix(context, newLocale, useContactsDict,
+ usePersonalizedDicts, forceReloadMainDictionary, listener, "" /* dictNamePrefix */);
+ }
+
+ public void resetDictionariesWithDictNamePrefix(final Context context, final Locale newLocale,
+ final boolean useContactsDict, final boolean usePersonalizedDicts,
+ final boolean forceReloadMainDictionary,
+ final DictionaryInitializationListener listener,
+ final String dictNamePrefix) {
final boolean localeHasBeenChanged = !newLocale.equals(mDictionaries.mLocale);
// We always try to have the main dictionary. Other dictionaries can be unused.
final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary;
- final boolean closeContactsDictionary = localeHasBeenChanged || !useContactsDict;
- final boolean closeUserDictionary = localeHasBeenChanged;
- final boolean closeUserHistoryDictionary = localeHasBeenChanged || !usePersonalizedDicts;
- final boolean closePersonalizationDictionary =
- localeHasBeenChanged || !usePersonalizedDicts;
+ // TODO: Make subDictTypesToUse configurable by resource or a static final list.
+ final HashSet<String> subDictTypesToUse = new HashSet<>();
+ if (useContactsDict) {
+ subDictTypesToUse.add(Dictionary.TYPE_CONTACTS);
+ }
+ subDictTypesToUse.add(Dictionary.TYPE_USER);
+ if (usePersonalizedDicts) {
+ subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
+ subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION);
+ subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL);
+ }
final Dictionary newMainDict;
if (reloadMainDictionary) {
// The main dictionary will be asynchronously loaded.
newMainDict = null;
} else {
- newMainDict = mDictionaries.getMainDict();
- }
-
- // Open or move contacts dictionary.
- final ExpandableBinaryDictionary newContactsDict;
- if (!closeContactsDictionary && mDictionaries.hasDict(Dictionary.TYPE_CONTACTS)) {
- newContactsDict = mDictionaries.getSubDict(Dictionary.TYPE_CONTACTS);
- } else if (useContactsDict) {
- newContactsDict = new ContactsBinaryDictionary(context, newLocale);
- } else {
- newContactsDict = null;
- }
-
- // Open or move user dictionary.
- final UserBinaryDictionary newUserDictionary;
- if (!closeUserDictionary && mDictionaries.hasDict(Dictionary.TYPE_USER)) {
- newUserDictionary = mDictionaries.mUserDictionary;
- } else {
- newUserDictionary = new UserBinaryDictionary(context, newLocale);
- }
-
- // Open or move user history dictionary.
- final ExpandableBinaryDictionary newUserHistoryDict;
- if (!closeUserHistoryDictionary && mDictionaries.hasDict(Dictionary.TYPE_USER_HISTORY)) {
- newUserHistoryDict = mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
- } else if (usePersonalizedDicts) {
- newUserHistoryDict = PersonalizationHelper.getUserHistoryDictionary(context, newLocale);
- } else {
- newUserHistoryDict = null;
+ newMainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
}
- // Open or move personalization dictionary.
- final ExpandableBinaryDictionary newPersonalizationDict;
- if (!closePersonalizationDictionary
- && mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION)) {
- newPersonalizationDict = mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
- } else if (usePersonalizedDicts) {
- newPersonalizationDict =
- PersonalizationHelper.getPersonalizationDictionary(context, newLocale);
- } else {
- newPersonalizationDict = null;
+ final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
+ for (final String dictType : SUB_DICT_TYPES) {
+ if (!subDictTypesToUse.contains(dictType)) {
+ // This dictionary will not be used.
+ continue;
+ }
+ final ExpandableBinaryDictionary dict;
+ if (!localeHasBeenChanged && mDictionaries.hasDict(dictType)) {
+ // Continue to use current dictionary.
+ dict = mDictionaries.getSubDict(dictType);
+ } else {
+ // Start to use new dictionary.
+ dict = getSubDict(dictType, context, newLocale, null /* dictFile */,
+ dictNamePrefix);
+ }
+ subDicts.put(dictType, dict);
}
// Replace Dictionaries.
- final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict,
- newContactsDict, newUserDictionary, newUserHistoryDict, newPersonalizationDict);
+ final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict, subDicts);
final Dictionaries oldDictionaries;
synchronized (mLock) {
oldDictionaries = mDictionaries;
mDictionaries = newDictionaries;
+ mIsUserDictEnabled = UserBinaryDictionary.isEnabled(context);
if (reloadMainDictionary) {
asyncReloadMainDictionary(context, newLocale, listener);
}
@@ -219,24 +273,15 @@ public class DictionaryFacilitatorForSuggest {
if (listener != null) {
listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
}
-
// Clean up old dictionaries.
if (reloadMainDictionary) {
oldDictionaries.closeDict(Dictionary.TYPE_MAIN);
}
- if (closeContactsDictionary) {
- oldDictionaries.closeDict(Dictionary.TYPE_CONTACTS);
- }
- if (closeUserDictionary) {
- oldDictionaries.closeDict(Dictionary.TYPE_USER);
- }
- if (closeUserHistoryDictionary) {
- oldDictionaries.closeDict(Dictionary.TYPE_USER_HISTORY);
- }
- if (closePersonalizationDictionary) {
- oldDictionaries.closeDict(Dictionary.TYPE_PERSONALIZATION);
+ for (final String dictType : SUB_DICT_TYPES) {
+ if (localeHasBeenChanged || !subDictTypesToUse.contains(dictType)) {
+ oldDictionaries.closeDict(dictType);
+ }
}
- oldDictionaries.mDictMap.clear();
oldDictionaries.mSubDictMap.clear();
}
@@ -270,52 +315,28 @@ public class DictionaryFacilitatorForSuggest {
final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles,
final Map<String, Map<String, String>> additionalDictAttributes) {
Dictionary mainDictionary = null;
- ContactsBinaryDictionary contactsDictionary = null;
- UserBinaryDictionary userDictionary = null;
- UserHistoryDictionary userHistoryDictionary = null;
- PersonalizationDictionary personalizationDictionary = null;
+ final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>();
for (final String dictType : dictionaryTypes) {
if (dictType.equals(Dictionary.TYPE_MAIN)) {
mainDictionary = DictionaryFactory.createMainDictionaryFromManager(context, locale);
- } else if (dictType.equals(Dictionary.TYPE_USER_HISTORY)) {
- userHistoryDictionary =
- PersonalizationHelper.getUserHistoryDictionary(context, locale);
- // Staring with an empty user history dictionary for testing.
- // Testing program may populate this dictionary before actual testing.
- userHistoryDictionary.reloadDictionaryIfRequired();
- userHistoryDictionary.waitAllTasksForTests();
+ } else {
+ final File dictFile = dictionaryFiles.get(dictType);
+ final ExpandableBinaryDictionary dict = getSubDict(
+ dictType, context, locale, dictFile, "" /* dictNamePrefix */);
if (additionalDictAttributes.containsKey(dictType)) {
- userHistoryDictionary.clearAndFlushDictionaryWithAdditionalAttributes(
+ dict.clearAndFlushDictionaryWithAdditionalAttributes(
additionalDictAttributes.get(dictType));
}
- } else if (dictType.equals(Dictionary.TYPE_PERSONALIZATION)) {
- personalizationDictionary =
- PersonalizationHelper.getPersonalizationDictionary(context, locale);
- // Staring with an empty personalization dictionary for testing.
- // Testing program may populate this dictionary before actual testing.
- personalizationDictionary.reloadDictionaryIfRequired();
- personalizationDictionary.waitAllTasksForTests();
- if (additionalDictAttributes.containsKey(dictType)) {
- personalizationDictionary.clearAndFlushDictionaryWithAdditionalAttributes(
- additionalDictAttributes.get(dictType));
+ if (dict == null) {
+ throw new RuntimeException("Unknown dictionary type: " + dictType);
}
- } else if (dictType.equals(Dictionary.TYPE_USER)) {
- final File file = dictionaryFiles.get(dictType);
- userDictionary = new UserBinaryDictionary(context, locale, file);
- userDictionary.reloadDictionaryIfRequired();
- userDictionary.waitAllTasksForTests();
- } else if (dictType.equals(Dictionary.TYPE_CONTACTS)) {
- final File file = dictionaryFiles.get(dictType);
- contactsDictionary = new ContactsBinaryDictionary(context, locale, file);
- contactsDictionary.reloadDictionaryIfRequired();
- contactsDictionary.waitAllTasksForTests();
- } else {
- throw new RuntimeException("Unknown dictionary type: " + dictType);
+ dict.reloadDictionaryIfRequired();
+ dict.waitAllTasksForTests();
+ subDicts.put(dictType, dict);
}
}
- mDictionaries = new Dictionaries(locale, mainDictionary, contactsDictionary,
- userDictionary, userHistoryDictionary, personalizationDictionary);
+ mDictionaries = new Dictionaries(locale, mainDictionary, subDicts);
}
public void closeDictionaries() {
@@ -324,15 +345,16 @@ public class DictionaryFacilitatorForSuggest {
dictionaries = mDictionaries;
mDictionaries = new Dictionaries();
}
- for (final Dictionary dict : dictionaries.mDictMap.values()) {
- dict.close();
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ dictionaries.closeDict(dictType);
}
+ mDistracterFilter.close();
}
// The main dictionary could have been loaded asynchronously. Don't cache the return value
// of this method.
public boolean hasInitializedMainDictionary() {
- final Dictionary mainDict = mDictionaries.getMainDict();
+ final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
return mainDict != null && mainDict.isInitialized();
}
@@ -364,48 +386,59 @@ public class DictionaryFacilitatorForSuggest {
}
public boolean isUserDictionaryEnabled() {
- final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary;
- if (userDictionary == null) {
- return false;
- }
- return userDictionary.mEnabled;
+ return mIsUserDictEnabled;
}
- public void addWordToUserDictionary(String word) {
- final UserBinaryDictionary userDictionary = mDictionaries.mUserDictionary;
- if (userDictionary == null) {
+ public void addWordToUserDictionary(final Context context, final String word) {
+ final Locale locale = getLocale();
+ if (locale == null) {
return;
}
- userDictionary.addWordToUserDictionary(word);
+ UserBinaryDictionary.addWordToUserDictionary(context, locale, word);
}
public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
- final String previousWord, final int timeStampInSeconds) {
+ final PrevWordsInfo prevWordsInfo, final int timeStampInSeconds,
+ final boolean blockPotentiallyOffensive) {
final Dictionaries dictionaries = mDictionaries;
+ final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
+ for (int i = 0; i < words.length; i++) {
+ final String currentWord = words[i];
+ final PrevWordsInfo prevWordsInfoForCurrentWord =
+ (i == 0) ? prevWordsInfo : new PrevWordsInfo(words[i - 1]);
+ final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
+ addWordToUserHistory(dictionaries, prevWordsInfoForCurrentWord, currentWord,
+ wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive);
+ }
+ }
+
+ private void addWordToUserHistory(final Dictionaries dictionaries,
+ final PrevWordsInfo prevWordsInfo, final String word, final boolean wasAutoCapitalized,
+ final int timeStampInSeconds, final boolean blockPotentiallyOffensive) {
final ExpandableBinaryDictionary userHistoryDictionary =
dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
if (userHistoryDictionary == null) {
return;
}
- final int maxFreq = getMaxFrequency(suggestion);
- if (maxFreq == 0) {
+ final int maxFreq = getFrequency(word);
+ if (maxFreq == 0 && blockPotentiallyOffensive) {
return;
}
- final String suggestionLowerCase = suggestion.toLowerCase(dictionaries.mLocale);
+ final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
final String secondWord;
if (wasAutoCapitalized) {
- if (isValidWord(suggestion, false /* ignoreCase */)
- && !isValidWord(suggestionLowerCase, false /* ignoreCase */)) {
+ if (isValidWord(word, false /* ignoreCase */)
+ && !isValidWord(lowerCasedWord, false /* ignoreCase */)) {
// If the word was auto-capitalized and exists only as a capitalized word in the
// dictionary, then we must not downcase it before registering it. For example,
// the name of the contacts in start-of-sentence position would come here with the
// wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version
// of that contact's name which would end up popping in suggestions.
- secondWord = suggestion;
+ secondWord = word;
} else {
// If however the word is not in the dictionary, or exists as a lower-case word
// only, then we consider that was a lower-case word that had been auto-capitalized.
- secondWord = suggestionLowerCase;
+ secondWord = lowerCasedWord;
}
} else {
// HACK: We'd like to avoid adding the capitalized form of common words to the User
@@ -413,46 +446,46 @@ public class DictionaryFacilitatorForSuggest {
// consolidation is done.
// TODO: Remove this hack when ready.
final int lowerCaseFreqInMainDict = dictionaries.hasDict(Dictionary.TYPE_MAIN) ?
- dictionaries.getMainDict().getFrequency(suggestionLowerCase) :
+ dictionaries.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
Dictionary.NOT_A_PROBABILITY;
if (maxFreq < lowerCaseFreqInMainDict
&& lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
// Use lower cased word as the word can be a distracter of the popular word.
- secondWord = suggestionLowerCase;
+ secondWord = lowerCasedWord;
} else {
- secondWord = suggestion;
+ secondWord = word;
}
}
// We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
// We don't add words with 0-frequency (assuming they would be profanity etc.).
final boolean isValid = maxFreq > 0;
- UserHistoryDictionary.addToDictionary(userHistoryDictionary, previousWord, secondWord,
- isValid, timeStampInSeconds);
+ UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord,
+ isValid, timeStampInSeconds, mDistracterFilter);
}
- public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
+ public void cancelAddingUserHistory(final PrevWordsInfo prevWordsInfo,
+ final String committedWord) {
final ExpandableBinaryDictionary userHistoryDictionary =
mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
if (userHistoryDictionary != null) {
- userHistoryDictionary.removeBigramDynamically(previousWord, committedWord);
+ userHistoryDictionary.removeNgramDynamically(prevWordsInfo, committedWord);
}
}
// TODO: Revise the way to fusion suggestion results.
public SuggestionResults getSuggestionResults(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId, final ArrayList<SuggestedWordInfo> rawSuggestions) {
final Dictionaries dictionaries = mDictionaries;
- final Map<String, Dictionary> dictMap = dictionaries.mDictMap;
final SuggestionResults suggestionResults =
new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS);
final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT };
- for (final String dictType : dictTypesOrderedToGetSuggestion) {
- final Dictionary dictionary = dictMap.get(dictType);
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaries.getDict(dictType);
if (null == dictionary) continue;
final ArrayList<SuggestedWordInfo> dictionarySuggestions =
- dictionary.getSuggestionsWithSessionId(composer, prevWord, proximityInfo,
+ dictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
blockOffensiveWords, additionalFeaturesOptions, sessionId,
languageWeight);
if (null == dictionarySuggestions) continue;
@@ -465,11 +498,11 @@ public class DictionaryFacilitatorForSuggest {
}
public boolean isValidMainDictWord(final String word) {
- final Dictionaries dictionaries = mDictionaries;
- if (TextUtils.isEmpty(word) || !dictionaries.hasDict(Dictionary.TYPE_MAIN)) {
+ final Dictionary mainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN);
+ if (TextUtils.isEmpty(word) || mainDict == null) {
return false;
}
- return dictionaries.getMainDict().isValidWord(word);
+ return mainDict.isValidWord(word);
}
public boolean isValidWord(final String word, final boolean ignoreCase) {
@@ -481,8 +514,8 @@ public class DictionaryFacilitatorForSuggest {
return false;
}
final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
- final Map<String, Dictionary> dictMap = dictionaries.mDictMap;
- for (final Dictionary dictionary : dictMap.values()) {
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaries.getDict(dictType);
// 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.
@@ -495,14 +528,22 @@ public class DictionaryFacilitatorForSuggest {
return false;
}
- private int getMaxFrequency(final String word) {
+ private int getFrequencyInternal(final String word,
+ final boolean isGettingMaxFrequencyOfExactMatches) {
if (TextUtils.isEmpty(word)) {
return Dictionary.NOT_A_PROBABILITY;
}
- int maxFreq = -1;
- final Map<String, Dictionary> dictMap = mDictionaries.mDictMap;
- for (final Dictionary dictionary : dictMap.values()) {
- final int tempFreq = dictionary.getFrequency(word);
+ int maxFreq = Dictionary.NOT_A_PROBABILITY;
+ final Dictionaries dictionaries = mDictionaries;
+ for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) {
+ final Dictionary dictionary = dictionaries.getDict(dictType);
+ if (dictionary == null) continue;
+ final int tempFreq;
+ if (isGettingMaxFrequencyOfExactMatches) {
+ tempFreq = dictionary.getMaxFrequencyOfExactMatches(word);
+ } else {
+ tempFreq = dictionary.getFrequency(word);
+ }
if (tempFreq >= maxFreq) {
maxFreq = tempFreq;
}
@@ -510,6 +551,14 @@ public class DictionaryFacilitatorForSuggest {
return maxFreq;
}
+ public int getFrequency(final String word) {
+ return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */);
+ }
+
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */);
+ }
+
public void clearUserHistoryDictionary() {
final ExpandableBinaryDictionary userHistoryDict =
mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
@@ -530,13 +579,26 @@ public class DictionaryFacilitatorForSuggest {
personalizationDict.clear();
}
- public void addMultipleDictionaryEntriesToPersonalizationDictionary(
- final ArrayList<LanguageModelParam> languageModelParams,
+ public void addEntriesToPersonalizationDictionary(
+ final PersonalizationDataChunk personalizationDataChunk,
+ final SpacingAndPunctuations spacingAndPunctuations,
final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) {
final ExpandableBinaryDictionary personalizationDict =
mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
- if (personalizationDict == null || languageModelParams == null
- || languageModelParams.isEmpty()) {
+ if (personalizationDict == null) {
+ if (callback != null) {
+ callback.onFinished();
+ }
+ return;
+ }
+ final ArrayList<LanguageModelParam> languageModelParams =
+ LanguageModelParam.createLanguageModelParamsFrom(
+ personalizationDataChunk.mTokens,
+ personalizationDataChunk.mTimestampInSeconds,
+ this /* dictionaryFacilitator */, spacingAndPunctuations,
+ new DistracterFilterCheckingIsInDictionary(
+ mDistracterFilter, personalizationDict));
+ if (languageModelParams == null || languageModelParams.isEmpty()) {
if (callback != null) {
callback.onFinished();
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index e09c309ea..59de4f82a 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -23,7 +23,6 @@ import android.content.res.Resources;
import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import java.io.File;
@@ -55,7 +54,7 @@ public final class DictionaryFactory {
createReadOnlyBinaryDictionary(context, locale));
}
- final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList();
+ final LinkedList<Dictionary> dictList = new LinkedList<>();
final ArrayList<AssetFileAddress> assetFileList =
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 550db4a6c..b1966bffc 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -27,6 +27,7 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.makedict.WordProperty;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.utils.CombinedFormatUtils;
+import com.android.inputmethod.latin.utils.DistracterFilter;
import com.android.inputmethod.latin.utils.ExecutorUtils;
import com.android.inputmethod.latin.utils.FileUtils;
import com.android.inputmethod.latin.utils.LanguageModelParam;
@@ -48,12 +49,12 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
* queries in native code. This binary dictionary is written to internal storage.
*/
abstract public class ExpandableBinaryDictionary extends Dictionary {
+ private static final boolean DEBUG = false;
/** Used for Log actions from this class */
private static final String TAG = ExpandableBinaryDictionary.class.getSimpleName();
/** Whether to print debug output to log */
- private static boolean DEBUG = false;
private static final boolean DBG_STRESS_TEST = false;
private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100;
@@ -92,11 +93,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
/** Indicates whether a task for reloading the dictionary has been scheduled. */
private final AtomicBoolean mIsReloading;
- /** Indicates whether the current dictionary needs to be reloaded. */
- private boolean mNeedsToReload;
+ /** Indicates whether the current dictionary needs to be recreated. */
+ private boolean mNeedsToRecreate;
private final ReentrantReadWriteLock mLock;
+ private Map<String, String> mAdditionalAttributeMap = null;
+
/* A extension for a binary dictionary file. */
protected static final String DICT_FILE_EXTENSION = ".dict";
@@ -105,26 +108,26 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
protected abstract void loadInitialContentsLocked();
- /**
- * Indicates that the source dictionary contents have changed and a rebuild of the binary file
- * is required. If it returns false, the next reload will only read the current binary
- * dictionary from file.
- */
- protected abstract boolean haveContentsChanged();
-
private boolean matchesExpectedBinaryDictFormatVersionForThisType(final int formatVersion) {
return formatVersion == FormatSpec.VERSION4;
}
private boolean needsToMigrateDictionary(final int formatVersion) {
- // TODO: Check version.
- return false;
+ // When we bump up the dictionary format version, the old version should be added to here
+ // for supporting migration. Note that native code has to support reading such formats.
+ return formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING;
}
public boolean isValidDictionaryLocked() {
return mBinaryDictionary.isValidDictionary();
}
+ // TODO: Remove and always enable beginning of sentence prediction. Currently, this is enabled
+ // only for ContextualDictionary.
+ protected boolean enableBeginningOfSentencePrediction() {
+ return false;
+ }
+
/**
* Creates a new expandable binary dictionary.
*
@@ -145,7 +148,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
mDictFile = getDictFile(context, dictName, dictFile);
mBinaryDictionary = null;
mIsReloading = new AtomicBoolean();
- mNeedsToReload = false;
+ mNeedsToRecreate = false;
mLock = new ReentrantReadWriteLock();
}
@@ -195,7 +198,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
protected Map<String, String> getHeaderAttributeMap() {
- HashMap<String, String> attributeMap = new HashMap<String, String>();
+ HashMap<String, String> attributeMap = new HashMap<>();
+ if (mAdditionalAttributeMap != null) {
+ attributeMap.putAll(mAdditionalAttributeMap);
+ }
attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, mDictName);
attributeMap.put(DictionaryHeader.DICTIONARY_LOCALE_KEY, mLocale.toString());
attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY,
@@ -270,11 +276,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
/**
- * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
+ * Adds unigram information of a word to the dictionary. May overwrite an existing entry.
*/
- public void addWordDynamically(final String word, final int frequency,
+ public void addUnigramEntryWithCheckingDistracter(final String word, final int frequency,
final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
- final boolean isBlacklisted, final int timestamp) {
+ final boolean isBlacklisted, final int timestamp,
+ final DistracterFilter distracterFilter) {
reloadDictionaryIfRequired();
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
@@ -282,24 +289,31 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
if (mBinaryDictionary == null) {
return;
}
+ if (distracterFilter.isDistracterToWordsInDictionaries(
+ PrevWordsInfo.EMPTY_PREV_WORDS_INFO, word, mLocale)) {
+ // The word is a distracter.
+ return;
+ }
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addWordDynamicallyLocked(word, frequency, shortcutTarget, shortcutFreq,
+ addUnigramLocked(word, frequency, shortcutTarget, shortcutFreq,
isNotAWord, isBlacklisted, timestamp);
}
});
}
- protected void addWordDynamicallyLocked(final String word, final int frequency,
+ protected void addUnigramLocked(final String word, final int frequency,
final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
final boolean isBlacklisted, final int timestamp) {
- mBinaryDictionary.addUnigramWord(word, frequency, shortcutTarget, shortcutFreq,
- isNotAWord, isBlacklisted, timestamp);
+ if (!mBinaryDictionary.addUnigramEntry(word, frequency, shortcutTarget, shortcutFreq,
+ false /* isBeginningOfSentence */, isNotAWord, isBlacklisted, timestamp)) {
+ Log.e(TAG, "Cannot add unigram entry. word: " + word);
+ }
}
/**
- * Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
+ * Adds n-gram information of a word to the dictionary. May overwrite an existing entry.
*/
- public void addBigramDynamically(final String word0, final String word1,
+ public void addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
final int frequency, final int timestamp) {
reloadDictionaryIfRequired();
asyncExecuteTaskWithWriteLock(new Runnable() {
@@ -309,20 +323,25 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return;
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addBigramDynamicallyLocked(word0, word1, frequency, timestamp);
+ addNgramEntryLocked(prevWordsInfo, word, frequency, timestamp);
}
});
}
- protected void addBigramDynamicallyLocked(final String word0, final String word1,
+ protected void addNgramEntryLocked(final PrevWordsInfo prevWordsInfo, final String word,
final int frequency, final int timestamp) {
- mBinaryDictionary.addBigramWords(word0, word1, frequency, timestamp);
+ if (!mBinaryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp)) {
+ if (DEBUG) {
+ Log.i(TAG, "Cannot add n-gram entry.");
+ Log.i(TAG, " PrevWordsInfo: " + prevWordsInfo + ", word: " + word);
+ }
+ }
}
/**
- * Dynamically remove a word bigram in the dictionary.
+ * Dynamically remove the n-gram entry in the dictionary.
*/
- public void removeBigramDynamically(final String word0, final String word1) {
+ public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word) {
reloadDictionaryIfRequired();
asyncExecuteTaskWithWriteLock(new Runnable() {
@Override
@@ -331,7 +350,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return;
}
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- mBinaryDictionary.removeBigramWords(word0, word1);
+ if (!mBinaryDictionary.removeNgramEntry(prevWordsInfo, word)) {
+ if (DEBUG) {
+ Log.i(TAG, "Cannot remove n-gram entry.");
+ Log.i(TAG, " PrevWordsInfo: " + prevWordsInfo + ", word: " + word);
+ }
+ }
}
});
}
@@ -367,8 +391,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
@Override
- public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
+ public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId, final float[] inOutLanguageWeight) {
reloadDictionaryIfRequired();
@@ -380,10 +404,14 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
if (mBinaryDictionary == null) {
return null;
}
+ if (composer.size() == 0 && prevWordsInfo.mIsBeginningOfSentence
+ && !enableBeginningOfSentencePrediction()) {
+ return null;
+ }
final ArrayList<SuggestedWordInfo> suggestions =
- mBinaryDictionary.getSuggestionsWithSessionId(composer, prevWord,
- proximityInfo, blockOffensiveWords, additionalFeaturesOptions,
- sessionId, inOutLanguageWeight);
+ mBinaryDictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions, sessionId,
+ inOutLanguageWeight);
if (mBinaryDictionary.isCorrupted()) {
Log.i(TAG, "Dictionary (" + mDictName +") is corrupted. "
+ "Remove and regenerate it.");
@@ -402,16 +430,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
@Override
- public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final float[] inOutLanguageWeight) {
- return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight);
- }
-
- @Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
reloadDictionaryIfRequired();
boolean lockAcquired = false;
try {
@@ -421,10 +440,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
if (mBinaryDictionary == null) {
return false;
}
- return isValidWordLocked(word);
+ return isInDictionaryLocked(word);
}
} catch (final InterruptedException e) {
- Log.e(TAG, "Interrupted tryLock() in isValidWord().", e);
+ Log.e(TAG, "Interrupted tryLock() in isInDictionary().", e);
} finally {
if (lockAcquired) {
mLock.readLock().unlock();
@@ -433,14 +452,38 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
return false;
}
- protected boolean isValidWordLocked(final String word) {
+ protected boolean isInDictionaryLocked(final String word) {
if (mBinaryDictionary == null) return false;
- return mBinaryDictionary.isValidWord(word);
+ return mBinaryDictionary.isInDictionary(word);
}
- protected boolean isValidBigramLocked(final String word1, final String word2) {
+ @Override
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ reloadDictionaryIfRequired();
+ boolean lockAcquired = false;
+ try {
+ lockAcquired = mLock.readLock().tryLock(
+ TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
+ if (lockAcquired) {
+ if (mBinaryDictionary == null) {
+ return NOT_A_PROBABILITY;
+ }
+ return mBinaryDictionary.getMaxFrequencyOfExactMatches(word);
+ }
+ } catch (final InterruptedException e) {
+ Log.e(TAG, "Interrupted tryLock() in getMaxFrequencyOfExactMatches().", e);
+ } finally {
+ if (lockAcquired) {
+ mLock.readLock().unlock();
+ }
+ }
+ return NOT_A_PROBABILITY;
+ }
+
+
+ protected boolean isValidNgramLocked(final PrevWordsInfo prevWordsInfo, final String word) {
if (mBinaryDictionary == null) return false;
- return mBinaryDictionary.isValidBigram(word1, word2);
+ return mBinaryDictionary.isValidNgram(prevWordsInfo, word);
}
/**
@@ -465,7 +508,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
if (mBinaryDictionary.isValidDictionary()
&& needsToMigrateDictionary(mBinaryDictionary.getFormatVersion())) {
- mBinaryDictionary.migrateTo(DICTIONARY_FORMAT_VERSION);
+ if (!mBinaryDictionary.migrateTo(DICTIONARY_FORMAT_VERSION)) {
+ Log.e(TAG, "Dictionary migration failed: " + mDictName);
+ removeBinaryDictionaryLocked();
+ }
}
}
@@ -481,11 +527,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
/**
- * Marks that the dictionary needs to be reloaded.
+ * Marks that the dictionary needs to be recreated.
*
*/
- protected void setNeedsToReload() {
- mNeedsToReload = true;
+ protected void setNeedsToRecreate() {
+ mNeedsToRecreate = true;
}
/**
@@ -503,7 +549,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
* Returns whether a dictionary reload is required.
*/
private boolean isReloadRequired() {
- return mBinaryDictionary == null || mNeedsToReload;
+ return mBinaryDictionary == null || mNeedsToRecreate;
}
/**
@@ -515,25 +561,24 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
@Override
public void run() {
try {
- // TODO: Quit checking contents in ExpandableBinaryDictionary.
- if (!mDictFile.exists() || (mNeedsToReload && haveContentsChanged())) {
+ if (!mDictFile.exists() || mNeedsToRecreate) {
// If the dictionary file does not exist or contents have been updated,
// generate a new one.
createNewDictionaryLocked();
} else if (mBinaryDictionary == null) {
// Otherwise, load the existing dictionary.
loadBinaryDictionaryLocked();
+ if (mBinaryDictionary != null && !(isValidDictionaryLocked()
+ // TODO: remove the check below
+ && matchesExpectedBinaryDictFormatVersionForThisType(
+ mBinaryDictionary.getFormatVersion()))) {
+ // Binary dictionary or its format version is not valid. Regenerate
+ // the dictionary file. createNewDictionaryLocked will remove the
+ // existing files if appropriate.
+ createNewDictionaryLocked();
+ }
}
- mNeedsToReload = false;
- if (mBinaryDictionary != null && !(isValidDictionaryLocked()
- // TODO: remove the check below
- && matchesExpectedBinaryDictFormatVersionForThisType(
- mBinaryDictionary.getFormatVersion()))) {
- // Binary dictionary or its format version is not valid. Regenerate
- // the dictionary file. writeBinaryDictionary will remove the
- // existing files if appropriate.
- createNewDictionaryLocked();
- }
+ mNeedsToRecreate = false;
} finally {
mIsReloading.set(false);
}
@@ -561,20 +606,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
});
}
- // TODO: Implement BinaryDictionary.isInDictionary().
- @UsedForTesting
- public boolean isInUnderlyingBinaryDictionaryForTests(final String word) {
- mLock.readLock().lock();
- try {
- if (mBinaryDictionary != null && mDictType == Dictionary.TYPE_USER_HISTORY) {
- return mBinaryDictionary.isValidWord(word);
- }
- return false;
- } finally {
- mLock.readLock().unlock();
- }
- }
-
@UsedForTesting
public void waitAllTasksForTests() {
final CountDownLatch countDownLatch = new CountDownLatch(1);
@@ -592,6 +623,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
}
@UsedForTesting
+ public void clearAndFlushDictionaryWithAdditionalAttributes(
+ final Map<String, String> attributeMap) {
+ mAdditionalAttributeMap = attributeMap;
+ clear();
+ }
+
public void dumpAllWordsForDebug() {
reloadDictionaryIfRequired();
asyncExecuteTaskWithLock(mLock.readLock(), new Runnable() {
@@ -600,6 +637,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
Log.d(TAG, "Dump dictionary: " + mDictName);
try {
final DictionaryHeader header = mBinaryDictionary.getHeader();
+ Log.d(TAG, "Format version: " + mBinaryDictionary.getFormatVersion());
Log.d(TAG, CombinedFormatUtils.formatAttributeMap(
header.mDictionaryOptions.mAttributes));
} catch (final UnsupportedFormatException e) {
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index 726b3d141..e1ae3dfe3 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -16,11 +16,13 @@
package com.android.inputmethod.latin;
+import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE;
+import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT;
+
import android.text.InputType;
import android.util.Log;
import android.view.inputmethod.EditorInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.InputTypeUtils;
import com.android.inputmethod.latin.utils.StringUtils;
@@ -36,16 +38,23 @@ public final class InputAttributes {
final public String mTargetApplicationPackageName;
final public boolean mInputTypeNoAutoCorrect;
final public boolean mIsPasswordField;
- final public boolean mIsSettingsSuggestionStripOn;
+ final public boolean mShouldShowSuggestions;
final public boolean mApplicationSpecifiedCompletionOn;
final public boolean mShouldInsertSpacesAutomatically;
final private int mInputType;
+ final private EditorInfo mEditorInfo;
+ final private String mPackageNameForPrivateImeOptions;
- public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) {
+ public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode,
+ final String packageNameForPrivateImeOptions) {
+ mEditorInfo = editorInfo;
+ mPackageNameForPrivateImeOptions = packageNameForPrivateImeOptions;
mTargetApplicationPackageName = null != editorInfo ? editorInfo.packageName : null;
final int inputType = null != editorInfo ? editorInfo.inputType : 0;
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
mInputType = inputType;
+ mIsPasswordField = InputTypeUtils.isPasswordInputType(inputType)
+ || InputTypeUtils.isVisiblePasswordInputType(inputType);
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
@@ -61,8 +70,7 @@ public final class InputAttributes {
Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x"
+ " imeOptions=0x%08x", inputType, editorInfo.imeOptions));
}
- mIsPasswordField = false;
- mIsSettingsSuggestionStripOn = false;
+ mShouldShowSuggestions = false;
mInputTypeNoAutoCorrect = false;
mApplicationSpecifiedCompletionOn = false;
mShouldInsertSpacesAutomatically = false;
@@ -79,17 +87,15 @@ public final class InputAttributes {
final boolean flagAutoComplete =
0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE);
- mIsPasswordField = InputTypeUtils.isPasswordInputType(inputType)
- || InputTypeUtils.isVisiblePasswordInputType(inputType);
// TODO: Have a helper method in InputTypeUtils
// Make sure that passwords are not displayed in {@link SuggestionStripView}.
- final boolean noSuggestionStrip = mIsPasswordField
+ final boolean shouldSuppressSuggestions = mIsPasswordField
|| InputTypeUtils.isEmailVariation(variation)
|| InputType.TYPE_TEXT_VARIATION_URI == variation
|| InputType.TYPE_TEXT_VARIATION_FILTER == variation
|| flagNoSuggestions
|| flagAutoComplete;
- mIsSettingsSuggestionStripOn = !noSuggestionStrip;
+ mShouldShowSuggestions = !shouldSuppressSuggestions;
mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType);
@@ -113,6 +119,15 @@ public final class InputAttributes {
return editorInfo.inputType == mInputType;
}
+ public boolean hasNoMicrophoneKeyOption() {
+ @SuppressWarnings("deprecation")
+ final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
+ null, NO_MICROPHONE_COMPAT, mEditorInfo);
+ final boolean noMicrophone = InputAttributes.inPrivateImeOptions(
+ mPackageNameForPrivateImeOptions, NO_MICROPHONE, mEditorInfo);
+ return noMicrophone || deprecatedNoMicrophone;
+ }
+
@SuppressWarnings("unused")
private void dumpFlags(final int inputType) {
final int inputClass = inputType & InputType.TYPE_MASK_CLASS;
@@ -215,7 +230,7 @@ public final class InputAttributes {
}
private static String toFlagsString(final int flags) {
- final ArrayList<String> flagsArray = CollectionUtils.newArrayList();
+ final ArrayList<String> flagsArray = new ArrayList<>();
if (0 != (flags & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS))
flagsArray.add("TYPE_TEXT_FLAG_NO_SUGGESTIONS");
if (0 != (flags & InputType.TYPE_TEXT_FLAG_MULTI_LINE))
@@ -243,7 +258,7 @@ public final class InputAttributes {
mInputType,
(mInputTypeNoAutoCorrect ? " noAutoCorrect" : ""),
(mIsPasswordField ? " password" : ""),
- (mIsSettingsSuggestionStripOn ? " suggestionStrip" : ""),
+ (mShouldShowSuggestions ? " shouldShowSuggestions" : ""),
(mApplicationSpecifiedCompletionOn ? " appSpecified" : ""),
(mShouldInsertSpacesAutomatically ? " insertSpaces" : ""),
mTargetApplicationPackageName);
diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java
index ea7859e60..0801cfa88 100644
--- a/java/src/com/android/inputmethod/latin/InputView.java
+++ b/java/src/com/android/inputmethod/latin/InputView.java
@@ -23,12 +23,14 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
+import com.android.inputmethod.accessibility.AccessibilityUtils;
import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.latin.suggestions.MoreSuggestionsView;
import com.android.inputmethod.latin.suggestions.SuggestionStripView;
public final class InputView extends LinearLayout {
private final Rect mInputViewRect = new Rect();
+ private MainKeyboardView mMainKeyboardView;
private KeyboardTopPaddingForwarder mKeyboardTopPaddingForwarder;
private MoreSuggestionsViewCanceler mMoreSuggestionsViewCanceler;
private MotionEventForwarder<?, ?> mActiveForwarder;
@@ -41,12 +43,11 @@ public final class InputView extends LinearLayout {
protected void onFinishInflate() {
final SuggestionStripView suggestionStripView =
(SuggestionStripView)findViewById(R.id.suggestion_strip_view);
- final MainKeyboardView mainKeyboardView =
- (MainKeyboardView)findViewById(R.id.keyboard_view);
+ mMainKeyboardView = (MainKeyboardView)findViewById(R.id.keyboard_view);
mKeyboardTopPaddingForwarder = new KeyboardTopPaddingForwarder(
- mainKeyboardView, suggestionStripView);
+ mMainKeyboardView, suggestionStripView);
mMoreSuggestionsViewCanceler = new MoreSuggestionsViewCanceler(
- mainKeyboardView, suggestionStripView);
+ mMainKeyboardView, suggestionStripView);
}
public void setKeyboardTopPadding(final int keyboardTopPadding) {
@@ -54,6 +55,17 @@ public final class InputView extends LinearLayout {
}
@Override
+ protected boolean dispatchHoverEvent(final MotionEvent event) {
+ if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()
+ && mMainKeyboardView.isShowingMoreKeysPanel()) {
+ // With accessibility mode on, discard hover events while a more keys keyboard is shown.
+ // The {@link MoreKeysKeyboard} receives hover events directly from the platform.
+ return true;
+ }
+ return super.dispatchHoverEvent(event);
+ }
+
+ @Override
public boolean onInterceptTouchEvent(final MotionEvent me) {
final Rect rect = mInputViewRect;
getGlobalVisibleRect(rect);
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 232bf7407..8cbf8379b 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -48,7 +48,7 @@ public final class LastComposedWord {
public final String mTypedWord;
public final CharSequence mCommittedWord;
public final String mSeparatorString;
- public final String mPrevWord;
+ public final PrevWordsInfo mPrevWordsInfo;
public final int mCapitalizedMode;
public final InputPointers mInputPointers =
new InputPointers(Constants.DICTIONARY_MAX_WORD_LENGTH);
@@ -64,16 +64,16 @@ public final class LastComposedWord {
public LastComposedWord(final ArrayList<Event> events,
final InputPointers inputPointers, final String typedWord,
final CharSequence committedWord, final String separatorString,
- final String prevWord, final int capitalizedMode) {
+ final PrevWordsInfo prevWordsInfo, final int capitalizedMode) {
if (inputPointers != null) {
mInputPointers.copy(inputPointers);
}
mTypedWord = typedWord;
- mEvents = new ArrayList<Event>(events);
+ mEvents = new ArrayList<>(events);
mCommittedWord = committedWord;
mSeparatorString = separatorString;
mActive = true;
- mPrevWord = prevWord;
+ mPrevWordsInfo = prevWordsInfo;
mCapitalizedMode = capitalizedMode;
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f1b1b8db2..8b671a94b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -28,7 +28,6 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -38,7 +37,6 @@ import android.net.ConnectivityManager;
import android.os.Debug;
import android.os.IBinder;
import android.os.Message;
-import android.preference.PreferenceManager;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Log;
@@ -55,7 +53,6 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.accessibility.AccessibilityUtils;
-import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
@@ -84,18 +81,18 @@ import com.android.inputmethod.latin.utils.ApplicationUtils;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.DialogUtils;
-import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatches;
import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.JniUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import com.android.inputmethod.latin.utils.StatsUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
-import com.android.inputmethod.research.ResearchLogger;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
@@ -104,7 +101,7 @@ import java.util.concurrent.TimeUnit;
*/
public class LatinIME extends InputMethodService implements KeyboardActionListener,
SuggestionStripView.Listener, SuggestionStripViewAccessor,
- DictionaryFacilitatorForSuggest.DictionaryInitializationListener,
+ DictionaryFacilitator.DictionaryInitializationListener,
ImportantNoticeDialog.ImportantNoticeDialogListener {
private static final String TAG = LatinIME.class.getSimpleName();
private static final boolean TRACE = false;
@@ -123,12 +120,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final String SCHEME_PACKAGE = "package";
private final Settings mSettings;
+ private final DictionaryFacilitator mDictionaryFacilitator =
+ new DictionaryFacilitator(new DistracterFilterCheckingExactMatches(this /* context */));
private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */,
- this /* SuggestionStripViewAccessor */);
+ this /* SuggestionStripViewAccessor */, mDictionaryFacilitator);
// We expect to have only one decoder in almost all cases, hence the default capacity of 1.
// If it turns out we need several, it will get grown seamlessly.
- final SparseArray<HardwareEventDecoder> mHardwareEventDecoders
- = new SparseArray<HardwareEventDecoder>(1);
+ final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1);
private View mExtractArea;
private View mKeyPreviewBackingView;
@@ -140,10 +138,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private final SubtypeState mSubtypeState = new SubtypeState();
// Object for reacting to adding/removing a dictionary pack.
- private BroadcastReceiver mDictionaryPackInstallReceiver =
+ private final BroadcastReceiver mDictionaryPackInstallReceiver =
new DictionaryPackInstallBroadcastReceiver(this);
- private BroadcastReceiver mDictionaryDumpBroadcastReceiver =
+ private final BroadcastReceiver mDictionaryDumpBroadcastReceiver =
new DictionaryDumpBroadcastReceiver(this);
private AlertDialog mOptionsDialog;
@@ -168,6 +166,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2;
private static final int ARG2_UNUSED = 0;
+ private static final int ARG1_FALSE = 0;
+ private static final int ARG1_TRUE = 1;
private int mDelayUpdateSuggestions;
private int mDelayUpdateShiftState;
@@ -215,7 +215,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
case MSG_RESUME_SUGGESTIONS:
latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor(
latinIme.mSettings.getCurrent(),
- false /* includeResumedWordInSuggestions */);
+ msg.arg1 == ARG1_TRUE /* shouldIncludeResumedWordInSuggestions */);
break;
case MSG_REOPEN_DICTIONARIES:
latinIme.resetSuggest();
@@ -252,16 +252,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
}
- public void postResumeSuggestions() {
+ public void postResumeSuggestions(final boolean shouldIncludeResumedWordInSuggestions) {
final LatinIME latinIme = getOwnerInstance();
if (latinIme == null) {
return;
}
- if (!latinIme.mSettings.getCurrent().isSuggestionStripVisible()) {
+ if (!latinIme.mSettings.getCurrent()
+ .isCurrentOrientationAllowingSuggestionsPerUserSettings()) {
return;
}
removeMessages(MSG_RESUME_SUGGESTIONS);
- sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
+ sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS,
+ shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE,
+ 0 /* ignored */),
+ mDelayUpdateSuggestions);
}
public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) {
@@ -481,6 +485,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
KeyboardSwitcher.init(this);
AudioAndHapticFeedbackManager.init(this);
AccessibilityUtils.init(this);
+ StatsUtils.init(this);
super.onCreate();
@@ -491,12 +496,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
loadSettings();
resetSuggest();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().init(this, mKeyboardSwitcher);
- ResearchLogger.getInstance().initDictionary(
- mInputLogic.mSuggest.mDictionaryFacilitator);
- }
-
// Register to receive ringer mode change and network state change.
// Also receive installation and removal of a dictionary pack.
final IntentFilter filter = new IntentFilter();
@@ -520,7 +519,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
DictionaryDecayBroadcastReciever.setUpIntervalAlarmForDictionaryDecaying(this);
- StatsUtils.onCreateCompleted(this);
+ StatsUtils.onCreate(mSettings.getCurrent());
}
// Has to be package-visible for unit tests
@@ -528,7 +527,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
void loadSettings() {
final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale();
final EditorInfo editorInfo = getCurrentInputEditorInfo();
- final InputAttributes inputAttributes = new InputAttributes(editorInfo, isFullscreenMode());
+ final InputAttributes inputAttributes = new InputAttributes(
+ editorInfo, isFullscreenMode(), getPackageName());
mSettings.loadSettings(this, locale, inputAttributes);
final SettingsValues currentSettingsValues = mSettings.getCurrent();
AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues);
@@ -538,24 +538,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!mHandler.hasPendingReopenDictionaries()) {
resetSuggestForLocale(locale);
}
+ mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList(
+ true /* allowsImplicitlySelectedSubtypes */));
refreshPersonalizationDictionarySession();
- }
-
- private DistracterFilter createDistracterFilter() {
- final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
- // TODO: Create Keyboard when mainKeyboardView is null.
- // TODO: Figure out the most reasonable keyboard for the filter. Refer to the
- // spellchecker's logic.
- final Keyboard keyboard = (mainKeyboardView != null) ?
- mainKeyboardView.getKeyboard() : null;
- final DistracterFilter distracterFilter = new DistracterFilter(mInputLogic.mSuggest,
- keyboard);
- return distracterFilter;
+ StatsUtils.onLoadSettings(currentSettingsValues);
}
private void refreshPersonalizationDictionarySession() {
- final DictionaryFacilitatorForSuggest dictionaryFacilitator =
- mInputLogic.mSuggest.mDictionaryFacilitator;
final boolean shouldKeepUserHistoryDictionaries;
final boolean shouldKeepPersonalizationDictionaries;
if (mSettings.getCurrent().mUsePersonalizedDicts) {
@@ -570,16 +559,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!shouldKeepUserHistoryDictionaries) {
// Remove user history dictionaries.
PersonalizationHelper.removeAllUserHistoryDictionaries(this);
- dictionaryFacilitator.clearUserHistoryDictionary();
+ mDictionaryFacilitator.clearUserHistoryDictionary();
}
if (!shouldKeepPersonalizationDictionaries) {
// Remove personalization dictionaries.
PersonalizationHelper.removeAllPersonalizationDictionaries(this);
PersonalizationDictionarySessionRegistrar.resetAll(this);
} else {
- final DistracterFilter distracterFilter = createDistracterFilter();
- PersonalizationDictionarySessionRegistrar.init(
- this, dictionaryFacilitator, distracterFilter);
+ PersonalizationDictionarySessionRegistrar.init(this, mDictionaryFacilitator);
}
}
@@ -617,13 +604,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
* @param locale the locale
*/
private void resetSuggestForLocale(final Locale locale) {
- final DictionaryFacilitatorForSuggest dictionaryFacilitator =
- mInputLogic.mSuggest.mDictionaryFacilitator;
final SettingsValues settingsValues = mSettings.getCurrent();
- dictionaryFacilitator.resetDictionaries(this /* context */, locale,
+ mDictionaryFacilitator.resetDictionaries(this /* context */, locale,
settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
false /* forceReloadMainDictionary */, this);
- if (settingsValues.mCorrectionEnabled) {
+ if (settingsValues.mAutoCorrectionEnabled) {
mInputLogic.mSuggest.setAutoCorrectionThreshold(
settingsValues.mAutoCorrectionThreshold);
}
@@ -633,27 +618,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
* Reset suggest by loading the main dictionary of the current locale.
*/
/* package private */ void resetSuggestMainDict() {
- final DictionaryFacilitatorForSuggest dictionaryFacilitator =
- mInputLogic.mSuggest.mDictionaryFacilitator;
final SettingsValues settingsValues = mSettings.getCurrent();
- dictionaryFacilitator.resetDictionaries(this /* context */,
- dictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
+ mDictionaryFacilitator.resetDictionaries(this /* context */,
+ mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict,
settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this);
}
@Override
public void onDestroy() {
- mInputLogic.mSuggest.mDictionaryFacilitator.closeDictionaries();
+ mDictionaryFacilitator.closeDictionaries();
mSettings.onDestroy();
unregisterReceiver(mConnectivityAndRingerModeChangeReceiver);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().onDestroy();
- }
unregisterReceiver(mDictionaryPackInstallReceiver);
unregisterReceiver(mDictionaryDumpBroadcastReceiver);
PersonalizationDictionarySessionRegistrar.close(this);
- LatinImeLogger.commit();
- LatinImeLogger.onDestroy();
StatsUtils.onDestroy();
super.onDestroy();
}
@@ -668,18 +646,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onConfigurationChanged(final Configuration conf) {
- // If orientation changed while predicting, commit the change
final SettingsValues settingsValues = mSettings.getCurrent();
if (settingsValues.mDisplayOrientation != conf.orientation) {
mHandler.startOrientationChanging();
- mInputLogic.mConnection.beginBatchEdit();
- mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
- mInputLogic.mConnection.finishComposingText();
- mInputLogic.mConnection.endBatchEdit();
+ // If !isComposingWord, #commitTyped() is a no-op, but still, it's better to avoid
+ // the useless IPC of {begin,end}BatchEdit.
+ if (mInputLogic.mWordComposer.isComposingWord()) {
+ mInputLogic.mConnection.beginBatchEdit();
+ // If we had a composition in progress, we need to commit the word so that the
+ // suggestionsSpan will be added. This will allow resuming on the same suggestions
+ // after rotation is finished.
+ mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR);
+ mInputLogic.mConnection.endBatchEdit();
+ }
}
- final DistracterFilter distracterFilter = createDistracterFilter();
PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf,
- mInputLogic.mSuggest.mDictionaryFacilitator, distracterFilter);
+ mDictionaryFacilitator);
super.onConfigurationChanged(conf);
}
@@ -698,9 +680,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (hasSuggestionStripView()) {
mSuggestionStripView.setListener(this, view);
}
- if (LatinImeLogger.sVISUALDEBUG) {
- mKeyPreviewBackingView.setBackgroundColor(0x10FF0000);
- }
}
@Override
@@ -734,6 +713,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
mSubtypeSwitcher.onSubtypeChanged(subtype);
+ mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype));
loadKeyboard();
}
@@ -772,10 +752,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
Log.i(TAG, "Starting input. Cursor position = "
+ editorInfo.initialSelStart + "," + editorInfo.initialSelEnd);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
- ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, prefs);
- }
+ // TODO: Consolidate these checks with {@link InputAttributes}.
if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) {
Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions);
Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead");
@@ -785,7 +762,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
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 (mainKeyboardView == null) {
return;
@@ -809,7 +785,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// The app calling setText() has the effect of clearing the composing
// span, so we should reset our state unconditionally, even if restarting is true.
- mInputLogic.startInput(restarting, editorInfo);
+ // We also tell the input logic about the combining rules for the current subtype, so
+ // it can adjust its combiners if needed.
+ mInputLogic.startInput(restarting, editorInfo,
+ mSubtypeSwitcher.getCombiningRulesExtraValueOfCurrentSubtype());
// Note: the following does a round-trip IPC on the main thread: be careful
final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
@@ -835,7 +814,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// When rotating, initialSelStart and initialSelEnd sometimes are lying. Make a best
// effort to work around this bug.
mInputLogic.mConnection.tryFixLyingCursorPosition();
- mHandler.postResumeSuggestions();
+ mHandler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */);
canReachInputConnection = true;
}
@@ -847,8 +826,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mainKeyboardView.closing();
currentSettingsValues = mSettings.getCurrent();
- if (currentSettingsValues.mCorrectionEnabled) {
- suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
+ if (currentSettingsValues.mAutoCorrectionEnabled) {
+ suggest.setAutoCorrectionThreshold(
+ currentSettingsValues.mAutoCorrectionThreshold);
}
switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(),
@@ -877,7 +857,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mHandler.cancelUpdateSuggestionStrip();
mainKeyboardView.setMainDictionaryAvailability(
- suggest.mDictionaryFacilitator.hasInitializedMainDictionary());
+ mDictionaryFacilitator.hasInitializedMainDictionary());
mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay);
mainKeyboardView.setSlidingKeyInputPreviewEnabled(
@@ -902,7 +882,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void onFinishInputInternal() {
super.onFinishInput();
- LatinImeLogger.commit();
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
mainKeyboardView.closing();
@@ -911,16 +890,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void onFinishInputViewInternal(final boolean finishingInput) {
super.onFinishInputView(finishingInput);
- mKeyboardSwitcher.onFinishInputView();
mKeyboardSwitcher.deallocateMemory();
// Remove pending messages related to update suggestions
mHandler.cancelUpdateSuggestionStrip();
// Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
mInputLogic.finishInput();
- // Notify ResearchLogger
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput);
- }
}
@Override
@@ -934,11 +908,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ ", nss=" + newSelStart + ", nse=" + newSelEnd
+ ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd);
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onUpdateSelection(oldSelStart, oldSelEnd,
- oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
- composingSpanEnd, mInputLogic.mConnection);
- }
// If the keyboard is not visible, we don't need to do all the housekeeping work, as it
// will be reset when the keyboard shows up anyway.
@@ -999,13 +968,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void hideWindow() {
- LatinImeLogger.commit();
mKeyboardSwitcher.onHideWindow();
- if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
- AccessibleKeyboardViewProxy.getInstance().onHideWindow();
- }
-
if (TRACE) Debug.stopMethodTracing();
if (isShowingOptionDialog()) {
mOptionsDialog.dismiss();
@@ -1029,9 +993,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
if (applicationSpecifiedCompletions == null) {
setNeutralSuggestionStrip();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onDisplayCompletions(null);
- }
return;
}
@@ -1042,10 +1003,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
null /* rawSuggestions */, false /* typedWordValid */, false /* willAutoCorrect */,
false /* isObsoleteSuggestions */, false /* isPrediction */);
// When in fullscreen mode, show completions generated by the application forcibly
- setSuggestedWords(suggestedWords, true /* isSuggestionStripVisible */);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions);
- }
+ setSuggestedWords(suggestedWords);
}
private int getAdjustedBackingViewHeight() {
@@ -1179,7 +1137,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else {
wordToEdit = word;
}
- mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary(wordToEdit);
+ mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit);
}
// Callback for the {@link SuggestionStripView}, to call when the important notice strip is
@@ -1348,30 +1306,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Nothing to do so far.
}
- private boolean isSuggestionStripVisible() {
- if (!hasSuggestionStripView()) {
- return false;
- }
- if (mSuggestionStripView.isShowingAddToDictionaryHint()) {
- return true;
- }
- final SettingsValues currentSettings = mSettings.getCurrent();
- if (null == currentSettings) {
- return false;
- }
- if (ImportantNoticeUtils.shouldShowImportantNotice(this,
- currentSettings.mInputAttributes)) {
- return true;
- }
- if (!currentSettings.isSuggestionStripVisible()) {
- return false;
- }
- if (currentSettings.isApplicationSpecifiedCompletionsOn()) {
- return true;
- }
- return currentSettings.isSuggestionsRequested();
- }
-
public boolean hasSuggestionStripView() {
return null != mSuggestionStripView;
}
@@ -1389,34 +1323,49 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSuggestionStripView.dismissAddToDictionaryHint();
}
- // TODO[IL]: Define a clear interface for this
- public void setSuggestedWords(final SuggestedWords suggestedWords,
- final boolean isSuggestionStripVisible) {
+ private void setSuggestedWords(final SuggestedWords suggestedWords) {
mInputLogic.setSuggestedWords(suggestedWords);
// TODO: Modify this when we support suggestions with hard keyboard
if (!hasSuggestionStripView()) {
return;
}
- mKeyboardSwitcher.onAutoCorrectionStateChanged(suggestedWords.mWillAutoCorrect);
if (!onEvaluateInputViewShown()) {
return;
}
- if (!isSuggestionStripVisible) {
- mSuggestionStripView.setVisibility(isFullscreenMode() ? View.GONE : View.INVISIBLE);
+
+ final SettingsValues currentSettingsValues = mSettings.getCurrent();
+ final boolean shouldShowImportantNotice =
+ ImportantNoticeUtils.shouldShowImportantNotice(this);
+ final boolean shouldShowSuggestionCandidates =
+ currentSettingsValues.mInputAttributes.mShouldShowSuggestions
+ && currentSettingsValues.isCurrentOrientationAllowingSuggestionsPerUserSettings();
+ final boolean shouldShowSuggestionsStripUnlessPassword = shouldShowImportantNotice
+ || currentSettingsValues.mShowsVoiceInputKey
+ || shouldShowSuggestionCandidates
+ || currentSettingsValues.isApplicationSpecifiedCompletionsOn();
+ final boolean shouldShowSuggestionsStrip = shouldShowSuggestionsStripUnlessPassword
+ && !currentSettingsValues.mInputAttributes.mIsPasswordField;
+ mSuggestionStripView.updateVisibility(shouldShowSuggestionsStrip, isFullscreenMode());
+ if (!shouldShowSuggestionsStrip) {
return;
}
- mSuggestionStripView.setVisibility(View.VISIBLE);
- final SettingsValues currentSettings = mSettings.getCurrent();
- final boolean showSuggestions;
- if (SuggestedWords.EMPTY == suggestedWords || suggestedWords.isPunctuationSuggestions()
- || !currentSettings.isSuggestionsRequested()) {
- showSuggestions = !mSuggestionStripView.maybeShowImportantNoticeTitle(
- currentSettings.mInputAttributes);
- } else {
- showSuggestions = true;
+ final boolean isEmptyApplicationSpecifiedCompletions =
+ currentSettingsValues.isApplicationSpecifiedCompletionsOn()
+ && suggestedWords.isEmpty();
+ final boolean noSuggestionsToShow = (SuggestedWords.EMPTY == suggestedWords)
+ || suggestedWords.isPunctuationSuggestions()
+ || isEmptyApplicationSpecifiedCompletions;
+ if (shouldShowImportantNotice && noSuggestionsToShow) {
+ if (mSuggestionStripView.maybeShowImportantNoticeTitle()) {
+ return;
+ }
}
- if (showSuggestions) {
+
+ if (currentSettingsValues.isCurrentOrientationAllowingSuggestionsPerUserSettings()
+ // We should clear suggestions if there is no suggestion to show.
+ || noSuggestionsToShow
+ || currentSettingsValues.isApplicationSpecifiedCompletionsOn()) {
mSuggestionStripView.setSuggestions(suggestedWords,
SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype()));
}
@@ -1430,35 +1379,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
callback.onGetSuggestedWords(SuggestedWords.EMPTY);
return;
}
- // Get the word on which we should search the bigrams. If we are composing a word, it's
- // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we
- // should just skip whitespace if any, so 1.
final SettingsValues currentSettings = mSettings.getCurrent();
final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues;
-
- if (DEBUG) {
- if (mInputLogic.mWordComposer.isComposingWord()
- || mInputLogic.mWordComposer.isBatchMode()) {
- final String previousWord
- = mInputLogic.mWordComposer.getPreviousWordForSuggestion();
- // TODO: this is for checking consistency with older versions. Remove this when
- // we are confident this is stable.
- // We're checking the previous word in the text field against the memorized previous
- // word. If we are composing a word we should have the second word before the cursor
- // memorized, otherwise we should have the first.
- final CharSequence rereadPrevWord = mInputLogic.getNthPreviousWordForSuggestion(
- currentSettings.mSpacingAndPunctuations,
- mInputLogic.mWordComposer.isComposingWord() ? 2 : 1);
- if (!TextUtils.equals(previousWord, rereadPrevWord)) {
- throw new RuntimeException("Unexpected previous word: "
- + previousWord + " <> " + rereadPrevWord);
- }
- }
- }
mInputLogic.mSuggest.getSuggestedWords(mInputLogic.mWordComposer,
- mInputLogic.mWordComposer.getPreviousWordForSuggestion(),
+ mInputLogic.getPrevWordsInfoFromNthPreviousWordForSuggestion(
+ currentSettings.mSpacingAndPunctuations,
+ // Get the word on which we should search the bigrams. If we are composing
+ // a word, it's whatever is *before* the half-committed word in the buffer,
+ // hence 2; if we aren't, we should just skip whitespace if any, so 1.
+ mInputLogic.mWordComposer.isComposingWord() ? 2 : 1),
keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive,
- currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId,
+ currentSettings.mAutoCorrectionEnabled, additionalFeaturesOptions, sessionId,
sequenceNumber, callback);
}
@@ -1478,7 +1409,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
setNeutralSuggestionStrip();
} else {
mInputLogic.mWordComposer.setAutoCorrection(autoCorrection);
- setSuggestedWords(suggestedWords, isSuggestionStripVisible());
+ setSuggestedWords(suggestedWords);
}
// Cache the auto-correction in accessibility code so we can speak it if the user
// touches a key that will insert it.
@@ -1511,7 +1442,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final SettingsValues currentSettings = mSettings.getCurrent();
final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled
? SuggestedWords.EMPTY : currentSettings.mSpacingAndPunctuations.mSuggestPuncList;
- setSuggestedWords(neutralSuggestions, isSuggestionStripVisible());
+ setSuggestedWords(neutralSuggestions);
}
// TODO: Make this private
@@ -1596,18 +1527,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
public void onReleaseKey(final int primaryCode, final boolean withSliding) {
mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding, getCurrentAutoCapsState(),
getCurrentRecapitalizeState());
-
- // If accessibility is on, ensure the user receives keyboard state updates.
- if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
- switch (primaryCode) {
- case Constants.CODE_SHIFT:
- AccessibleKeyboardViewProxy.getInstance().notifyShiftState();
- break;
- case Constants.CODE_SWITCH_ALPHA_SYMBOL:
- AccessibleKeyboardViewProxy.getInstance().notifySymbolsState();
- break;
- }
- }
}
private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) {
@@ -1652,7 +1571,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// boolean onKeyMultiple(final int keyCode, final int count, final KeyEvent event);
// receive ringer mode change and network state change.
- private BroadcastReceiver mConnectivityAndRingerModeChangeReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mConnectivityAndRingerModeChangeReceiver =
+ new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
@@ -1747,15 +1667,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@UsedForTesting
/* package for test */ void waitForLoadingDictionaries(final long timeout, final TimeUnit unit)
throws InterruptedException {
- mInputLogic.mSuggest.mDictionaryFacilitator.waitForLoadingDictionariesForTesting(
- timeout, unit);
+ mDictionaryFacilitator.waitForLoadingDictionariesForTesting(timeout, unit);
}
// DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly.
@UsedForTesting
/* package for test */ void replaceDictionariesForTest(final Locale locale) {
final SettingsValues settingsValues = mSettings.getCurrent();
- mInputLogic.mSuggest.mDictionaryFacilitator.resetDictionaries(this, locale,
+ mDictionaryFacilitator.resetDictionaries(this, locale,
settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts,
false /* forceReloadMainDictionary */, this /* listener */);
}
@@ -1763,17 +1682,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// DO NOT USE THIS for any other purpose than testing.
@UsedForTesting
/* package for test */ void clearPersonalizedDictionariesForTest() {
- mInputLogic.mSuggest.mDictionaryFacilitator.clearUserHistoryDictionary();
- mInputLogic.mSuggest.mDictionaryFacilitator.clearPersonalizationDictionary();
+ mDictionaryFacilitator.clearUserHistoryDictionary();
+ mDictionaryFacilitator.clearPersonalizationDictionary();
+ }
+
+ @UsedForTesting
+ /* package for test */ List<InputMethodSubtype> getEnabledSubtypesForTest() {
+ return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList(
+ true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>();
}
public void dumpDictionaryForDebug(final String dictName) {
- final DictionaryFacilitatorForSuggest dictionaryFacilitator =
- mInputLogic.mSuggest.mDictionaryFacilitator;
- if (dictionaryFacilitator.getLocale() == null) {
+ if (mDictionaryFacilitator.getLocale() == null) {
resetSuggest();
}
- mInputLogic.mSuggest.mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
+ mDictionaryFacilitator.dumpDictionaryForDebug(dictName);
}
public void debugDumpStateAndCrashWithException(final String context) {
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index 3f2b0a3f4..8fd36b937 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -16,76 +16,12 @@
package com.android.inputmethod.latin;
-import android.content.SharedPreferences;
-import android.view.inputmethod.EditorInfo;
+import android.content.Context;
-import com.android.inputmethod.keyboard.Keyboard;
+// TODO: Rename this class name to make it more relevant.
+public final class LatinImeLogger {
+ public static final boolean sDBG = false;
-public final class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
-
- 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(LatinIME context) {
- }
-
- public static void commit() {
- }
-
- public static boolean getUsabilityStudyMode(final SharedPreferences prefs) {
- return false;
- }
-
- public static void onDestroy() {
- }
-
- public static void logOnManualSuggestion(
- String before, String after, int position, SuggestedWords suggestedWords) {
- }
-
- public static void logOnAutoCorrectionForTyping(
- String before, String after, int separatorCode) {
- }
-
- public static void logOnAutoCorrectionForGeometric(String before, String after,
- int separatorCode, InputPointers inputPointers) {
- }
-
- public static void logOnAutoCorrectionCancelled() {
- }
-
- public static void logOnDelete(int x, int y) {
- }
-
- public static void logOnInputChar() {
- }
-
- public static void logOnInputSeparator() {
- }
-
- public static void logOnException(String metaData, Throwable e) {
- }
-
- public static void logOnWarning(String warning) {
- }
-
- public static void onStartInputView(EditorInfo editorInfo) {
- }
-
- public static void onStartSuggestion(CharSequence previousWords) {
- }
-
- public static void onAddSuggestedWord(String word, String sourceDictionaryId) {
- }
-
- public static void onSetKeyboard(Keyboard kb) {
- }
-
- public static void onPrintAllUsabilityStudyLogs() {
+ public static void init(Context context) {
}
}
diff --git a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
new file mode 100644
index 000000000..42b311c69
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+/**
+ * Class to represent information of previous words. This class is used to add n-gram entries
+ * into binary dictionaries, to get predictions, and to get suggestions.
+ */
+// TODO: Support multiple previous words for n-gram.
+public class PrevWordsInfo {
+ public static final PrevWordsInfo EMPTY_PREV_WORDS_INFO = new PrevWordsInfo(null);
+ public static final PrevWordsInfo BEGINNING_OF_SENTENCE = new PrevWordsInfo();
+
+ // The word immediately before the considered word. null means we don't have any context
+ // including the "beginning of sentence context" - we just don't know what to predict.
+ // An example of that is after a comma.
+ // For simplicity of implementation, this may also be null transiently after the WordComposer
+ // was reset and before starting a new composing word, but we should never be calling
+ // getSuggetions* in this situation.
+ // This is an empty string when mIsBeginningOfSentence is true.
+ public final String mPrevWord;
+
+ // TODO: Have sentence separator.
+ // Whether the current context is beginning of sentence or not. This is true when composing at
+ // the beginning of an input field or composing a word after a sentence separator.
+ public final boolean mIsBeginningOfSentence;
+
+ // Beginning of sentence.
+ public PrevWordsInfo() {
+ mPrevWord = "";
+ mIsBeginningOfSentence = true;
+ }
+
+ public PrevWordsInfo(final String prevWord) {
+ mPrevWord = prevWord;
+ mIsBeginningOfSentence = false;
+ }
+
+ public boolean isValid() {
+ return mPrevWord != null;
+ }
+
+ @Override
+ public String toString() {
+ return "PrevWord: " + mPrevWord + ", isBeginningOfSentence: "
+ + mIsBeginningOfSentence + ".";
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
index 4911bcdf6..0fba37c8a 100644
--- a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java
@@ -17,8 +17,6 @@
package com.android.inputmethod.latin;
import com.android.inputmethod.keyboard.internal.KeySpecParser;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
@@ -49,7 +47,7 @@ public final class PunctuationSuggestions extends SuggestedWords {
*/
public static PunctuationSuggestions newPunctuationSuggestions(
final String[] punctuationSpecs) {
- final ArrayList<SuggestedWordInfo> puncuationsList = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> puncuationsList = new ArrayList<>();
for (final String puncSpec : punctuationSpecs) {
puncuationsList.add(newHardCodedWordInfo(puncSpec));
}
diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
index 9f61d6c37..e59ef7563 100644
--- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java
@@ -50,22 +50,14 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
- final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final float[] inOutLanguageWeight) {
- return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
- additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight);
- }
-
- @Override
- public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId, final float[] inOutLanguageWeight) {
if (mLock.readLock().tryLock()) {
try {
- return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
- blockOffensiveWords, additionalFeaturesOptions, inOutLanguageWeight);
+ return mBinaryDictionary.getSuggestions(composer, prevWordsInfo, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions, sessionId,
+ inOutLanguageWeight);
} finally {
mLock.readLock().unlock();
}
@@ -74,10 +66,10 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
if (mLock.readLock().tryLock()) {
try {
- return mBinaryDictionary.isValidWord(word);
+ return mBinaryDictionary.isInDictionary(word);
} finally {
mLock.readLock().unlock();
}
@@ -110,6 +102,18 @@ public final class ReadOnlyBinaryDictionary extends Dictionary {
}
@Override
+ public int getMaxFrequencyOfExactMatches(final String word) {
+ if (mLock.readLock().tryLock()) {
+ try {
+ return mBinaryDictionary.getMaxFrequencyOfExactMatches(word);
+ } finally {
+ mLock.readLock().unlock();
+ }
+ }
+ return NOT_A_PROBABILITY;
+ }
+
+ @Override
public void close() {
mLock.writeLock().lock();
try {
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 606bb775e..96476b2ee 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -26,14 +26,12 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.DebugLogUtils;
import com.android.inputmethod.latin.utils.SpannableStringUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.Arrays;
import java.util.regex.Pattern;
@@ -174,9 +172,6 @@ public final class RichInputConnection {
}
if (null != mIC && shouldFinishComposition) {
mIC.finishComposingText();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_finishComposingText();
- }
}
return true;
}
@@ -223,9 +218,6 @@ public final class RichInputConnection {
mComposingText.setLength(0);
if (null != mIC) {
mIC.finishComposingText();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_finishComposingText();
- }
}
}
@@ -363,9 +355,6 @@ public final class RichInputConnection {
}
if (null != mIC) {
mIC.deleteSurroundingText(beforeLength, afterLength);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_deleteSurroundingText(beforeLength, afterLength);
- }
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -374,9 +363,6 @@ public final class RichInputConnection {
mIC = mParent.getCurrentInputConnection();
if (null != mIC) {
mIC.performEditorAction(actionId);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_performEditorAction(actionId);
- }
}
}
@@ -429,9 +415,6 @@ public final class RichInputConnection {
}
if (null != mIC) {
mIC.sendKeyEvent(keyEvent);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_sendKeyEvent(keyEvent);
- }
}
}
@@ -469,9 +452,6 @@ public final class RichInputConnection {
// newCursorPosition != 1.
if (null != mIC) {
mIC.setComposingText(text, newCursorPosition);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_setComposingText(text, newCursorPosition);
- }
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@@ -500,9 +480,6 @@ public final class RichInputConnection {
if (!isIcValid) {
return false;
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_setSelection(start, end);
- }
}
return reloadTextCache();
}
@@ -530,18 +507,17 @@ public final class RichInputConnection {
mComposingText.setLength(0);
if (null != mIC) {
mIC.commitCompletion(completionInfo);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_commitCompletion(completionInfo);
- }
}
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
}
@SuppressWarnings("unused")
- public String getNthPreviousWord(final SpacingAndPunctuations spacingAndPunctuations,
- final int n) {
+ public PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(
+ final SpacingAndPunctuations spacingAndPunctuations, final int n) {
mIC = mParent.getCurrentInputConnection();
- if (null == mIC) return null;
+ if (null == mIC) {
+ return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ }
final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
if (DEBUG_PREVIOUS_TEXT && null != prev) {
final int checkLength = LOOKBACK_CHARACTER_NUM - 1;
@@ -561,46 +537,73 @@ public final class RichInputConnection {
}
}
}
- return getNthPreviousWord(prev, spacingAndPunctuations, n);
+ return getPrevWordsInfoFromNthPreviousWord(prev, spacingAndPunctuations, n);
}
private static boolean isSeparator(final int code, final int[] sortedSeparators) {
return Arrays.binarySearch(sortedSeparators, code) >= 0;
}
- // Get the nth word before cursor. n = 1 retrieves the word immediately before the cursor,
- // n = 2 retrieves the word before that, and so on. This splits on whitespace only.
+ // Get information of the nth word before cursor. n = 1 retrieves the word immediately before
+ // the cursor, n = 2 retrieves the word before that, and so on. This splits on whitespace only.
// Also, it won't return words that end in a separator (if the nth word before the cursor
- // ends in a separator, it returns null).
+ // ends in a separator, it returns information representing beginning-of-sentence).
// Example :
// (n = 1) "abc def|" -> def
// (n = 1) "abc def |" -> def
- // (n = 1) "abc def. |" -> null
- // (n = 1) "abc def . |" -> null
+ // (n = 1) "abc 'def|" -> 'def
+ // (n = 1) "abc def. |" -> beginning-of-sentence
+ // (n = 1) "abc def . |" -> beginning-of-sentence
// (n = 2) "abc def|" -> abc
// (n = 2) "abc def |" -> abc
+ // (n = 2) "abc 'def|" -> empty. The context is different from "abc def", but we cannot
+ // represent this situation using PrevWordsInfo. See TODO in the method.
// (n = 2) "abc def. |" -> abc
// (n = 2) "abc def . |" -> def
- // (n = 2) "abc|" -> null
- // (n = 2) "abc |" -> null
- // (n = 2) "abc. def|" -> null
- public static String getNthPreviousWord(final CharSequence prev,
+ // (n = 2) "abc|" -> beginning-of-sentence
+ // (n = 2) "abc |" -> beginning-of-sentence
+ // (n = 2) "abc. def|" -> beginning-of-sentence
+ public static PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(final CharSequence prev,
final SpacingAndPunctuations spacingAndPunctuations, final int n) {
- if (prev == null) return null;
+ if (prev == null) return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
final String[] w = spaceRegex.split(prev);
- // If we can't find n words, or we found an empty word, return null.
- if (w.length < n) return null;
+ // Referring to the word after the nth word.
+ if ((n - 1) > 0 && (n - 1) <= w.length) {
+ final String wordFollowingTheNthPrevWord = w[w.length - n + 1];
+ if (!wordFollowingTheNthPrevWord.isEmpty()) {
+ final char firstChar = wordFollowingTheNthPrevWord.charAt(0);
+ if (spacingAndPunctuations.isWordConnector(firstChar)) {
+ // The word following the n-th prev word is starting with a word connector.
+ // TODO: Return meaningful context for this case.
+ return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ }
+ }
+ }
+
+ // If we can't find n words, or we found an empty word, the context is
+ // beginning-of-sentence.
+ if (w.length < n) {
+ return PrevWordsInfo.BEGINNING_OF_SENTENCE;
+ }
final String nthPrevWord = w[w.length - n];
final int length = nthPrevWord.length();
- if (length <= 0) return null;
+ if (length <= 0) {
+ return PrevWordsInfo.BEGINNING_OF_SENTENCE;
+ }
- // If ends in a separator, return null
+ // If ends in a sentence separator, the context is beginning-of-sentence.
final char lastChar = nthPrevWord.charAt(length - 1);
+ if (spacingAndPunctuations.isSentenceSeparator(lastChar)) {
+ return PrevWordsInfo.BEGINNING_OF_SENTENCE;
+ }
+ // If ends in a word separator or connector, the context is unclear.
+ // TODO: Return meaningful context for this case.
if (spacingAndPunctuations.isWordSeparator(lastChar)
- || spacingAndPunctuations.isWordConnector(lastChar)) return null;
-
- return nthPrevWord;
+ || spacingAndPunctuations.isWordConnector(lastChar)) {
+ return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ }
+ return new PrevWordsInfo(nthPrevWord);
}
/**
@@ -752,9 +755,6 @@ public final class RichInputConnection {
deleteSurroundingText(2, 0);
final String singleSpace = " ";
commitText(singleSpace, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_revertDoubleSpacePeriod();
- }
return true;
}
@@ -777,9 +777,6 @@ public final class RichInputConnection {
deleteSurroundingText(2, 0);
final String text = " " + textBeforeCursor.subSequence(0, 1);
commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.richInputConnection_revertSwapPunctuation();
- }
return true;
}
@@ -894,4 +891,8 @@ public final class RichInputConnection {
public boolean hasSelection() {
return mExpectedSelEnd != mExpectedSelStart;
}
+
+ public boolean isCursorPositionKnown() {
+ return INVALID_CURSOR_POSITION != mExpectedSelStart;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 2b0be545e..7758ac78e 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -31,7 +31,6 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.util.Collections;
@@ -53,9 +52,9 @@ public final class RichInputMethodManager {
private InputMethodManagerCompatWrapper mImmWrapper;
private InputMethodInfoCache mInputMethodInfoCache;
final HashMap<InputMethodInfo, List<InputMethodSubtype>>
- mSubtypeListCacheWithImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
+ mSubtypeListCacheWithImplicitlySelectedSubtypes = new HashMap<>();
final HashMap<InputMethodInfo, List<InputMethodSubtype>>
- mSubtypeListCacheWithoutImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
+ mSubtypeListCacheWithoutImplicitlySelectedSubtypes = new HashMap<>();
private static final int INDEX_NOT_FOUND = -1;
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 021133945..a3d09565c 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -52,7 +52,6 @@ public final class SubtypeSwitcher {
private /* final */ RichInputMethodManager mRichImm;
private /* final */ Resources mResources;
- private /* final */ ConnectivityManager mConnectivityManager;
private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper =
new LanguageOnSpacebarHelper();
@@ -111,10 +110,10 @@ public final class SubtypeSwitcher {
}
mResources = context.getResources();
mRichImm = RichInputMethodManager.getInstance();
- mConnectivityManager = (ConnectivityManager) context.getSystemService(
+ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
- final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
+ final NetworkInfo info = connectivityManager.getActiveNetworkInfo();
mIsNetworkConnected = (info != null && info.isConnected());
onSubtypeChanged(getCurrentSubtype());
@@ -256,8 +255,7 @@ public final class SubtypeSwitcher {
public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() {
final Locale systemLocale = mResources.getConfiguration().locale;
- final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes =
- new HashSet<InputMethodSubtype>();
+ final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>();
final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager();
final List<InputMethodInfo> enabledInputMethodInfoList =
inputMethodManager.getEnabledInputMethodList();
@@ -327,4 +325,8 @@ public final class SubtypeSwitcher {
+ DUMMY_EMOJI_SUBTYPE);
return DUMMY_EMOJI_SUBTYPE;
}
+
+ public String getCombiningRulesExtraValueOfCurrentSubtype() {
+ return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype());
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index db0a8a81c..1ba5d5ea6 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -18,13 +18,11 @@ package com.android.inputmethod.latin;
import android.text.TextUtils;
-import com.android.inputmethod.event.Event;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.SuggestionResults;
@@ -53,11 +51,14 @@ public final class Suggest {
private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
private static final boolean DBG = LatinImeLogger.sDBG;
- public final DictionaryFacilitatorForSuggest mDictionaryFacilitator =
- new DictionaryFacilitatorForSuggest();
+ private final DictionaryFacilitator mDictionaryFacilitator;
private float mAutoCorrectionThreshold;
+ public Suggest(final DictionaryFacilitator dictionaryFacilitator) {
+ mDictionaryFacilitator = dictionaryFacilitator;
+ }
+
public Locale getLocale() {
return mDictionaryFacilitator.getLocale();
}
@@ -71,17 +72,16 @@ public final class Suggest {
}
public void getSuggestedWords(final WordComposer wordComposer,
- final String prevWordForBigram, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final boolean isCorrectionEnabled,
final int[] additionalFeaturesOptions, final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
- LatinImeLogger.onStartSuggestion(prevWordForBigram);
if (wordComposer.isBatchMode()) {
- getSuggestedWordsForBatchInput(wordComposer, prevWordForBigram, proximityInfo,
+ getSuggestedWordsForBatchInput(wordComposer, prevWordsInfo, proximityInfo,
blockOffensiveWords, additionalFeaturesOptions, sessionId, sequenceNumber,
callback);
} else {
- getSuggestedWordsForTypingInput(wordComposer, prevWordForBigram, proximityInfo,
+ getSuggestedWordsForTypingInput(wordComposer, prevWordsInfo, proximityInfo,
blockOffensiveWords, isCorrectionEnabled, additionalFeaturesOptions,
sequenceNumber, callback);
}
@@ -90,29 +90,31 @@ public final class Suggest {
// Retrieves suggestions for the typing input
// and calls the callback function with the suggestions.
private void getSuggestedWordsForTypingInput(final WordComposer wordComposer,
- final String prevWordForBigram, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final boolean isCorrectionEnabled,
final int[] additionalFeaturesOptions, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
- final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
final String typedWord = wordComposer.getTypedWord();
+ final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(typedWord);
final String consideredWord = trailingSingleQuotesCount > 0
? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount)
: typedWord;
- LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED);
final ArrayList<SuggestedWordInfo> rawSuggestions;
if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) {
- rawSuggestions = CollectionUtils.newArrayList();
+ rawSuggestions = new ArrayList<>();
} else {
rawSuggestions = null;
}
final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords,
+ wordComposer, prevWordsInfo, proximityInfo, blockOffensiveWords,
additionalFeaturesOptions, SESSION_TYPING, rawSuggestions);
- final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
- final boolean isAllUpperCase = wordComposer.isAllUpperCase();
+ final boolean isOnlyFirstCharCapitalized = wordComposer.isOnlyFirstCharCapitalized();
+ // If resumed, then we don't want to upcase everything: resuming on a fully-capitalized
+ // words is rarely done to switch to another fully-capitalized word, but usually to a
+ // normal, non-capitalized suggestion.
+ final boolean isAllUpperCase = wordComposer.isAllUpperCase() && !wordComposer.isResumed();
final String firstSuggestion;
final String whitelistedWord;
if (suggestionResults.isEmpty()) {
@@ -120,9 +122,9 @@ public final class Suggest {
} else {
final SuggestedWordInfo firstSuggestedWordInfo = getTransformedSuggestedWordInfo(
suggestionResults.first(), suggestionResults.mLocale, isAllUpperCase,
- isFirstCharCapitalized, trailingSingleQuotesCount);
+ isOnlyFirstCharCapitalized, trailingSingleQuotesCount);
firstSuggestion = firstSuggestedWordInfo.mWord;
- if (SuggestedWordInfo.KIND_WHITELIST != firstSuggestedWordInfo.mKind) {
+ if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
whitelistedWord = null;
} else {
whitelistedWord = firstSuggestion;
@@ -140,7 +142,7 @@ public final class Suggest {
final boolean allowsToBeAutoCorrected = (null != whitelistedWord
&& !whitelistedWord.equals(typedWord))
|| (consideredWord.length() > 1 && !mDictionaryFacilitator.isValidWord(
- consideredWord, wordComposer.isFirstCharCapitalized())
+ consideredWord, isOnlyFirstCharCapitalized)
&& !typedWord.equals(firstSuggestion));
final boolean hasAutoCorrection;
@@ -153,7 +155,7 @@ public final class Suggest {
|| suggestionResults.isEmpty() || wordComposer.hasDigits()
|| wordComposer.isMostlyCaps() || wordComposer.isResumed()
|| !mDictionaryFacilitator.hasInitializedMainDictionary()
- || SuggestedWordInfo.KIND_SHORTCUT == suggestionResults.first().mKind) {
+ || suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) {
// If we don't have a main dictionary, we never want to auto-correct. The reason for
// this is, the user may have a contact whose name happens to match a valid word in
// their language, and it will unexpectedly auto-correct. For example, if the user
@@ -169,24 +171,18 @@ public final class Suggest {
}
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- CollectionUtils.newArrayList(suggestionResults);
+ new ArrayList<>(suggestionResults);
final int suggestionsCount = suggestionsContainer.size();
- if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
+ if (isOnlyFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) {
for (int i = 0; i < suggestionsCount; ++i) {
final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
- wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized,
- trailingSingleQuotesCount);
+ wordInfo, suggestionResults.mLocale, isAllUpperCase,
+ isOnlyFirstCharCapitalized, trailingSingleQuotesCount);
suggestionsContainer.set(i, transformedWordInfo);
}
}
- for (int i = 0; i < suggestionsCount; ++i) {
- final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
- LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(),
- wordInfo.mSourceDict.mDictType);
- }
-
if (!TextUtils.isEmpty(typedWord)) {
suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED,
@@ -215,25 +211,21 @@ public final class Suggest {
// Retrieves suggestions for the batch input
// and calls the callback function with the suggestions.
private void getSuggestedWordsForBatchInput(final WordComposer wordComposer,
- final String prevWordForBigram, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
final int sessionId, final int sequenceNumber,
final OnGetSuggestedWordsCallback callback) {
final ArrayList<SuggestedWordInfo> rawSuggestions;
if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) {
- rawSuggestions = CollectionUtils.newArrayList();
+ rawSuggestions = new ArrayList<>();
} else {
rawSuggestions = null;
}
final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- wordComposer, prevWordForBigram, proximityInfo, blockOffensiveWords,
+ wordComposer, prevWordsInfo, proximityInfo, blockOffensiveWords,
additionalFeaturesOptions, sessionId, rawSuggestions);
- for (SuggestedWordInfo wordInfo : suggestionResults) {
- LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType);
- }
-
final ArrayList<SuggestedWordInfo> suggestionsContainer =
- CollectionUtils.newArrayList(suggestionResults);
+ new ArrayList<>(suggestionResults);
final int suggestionsCount = suggestionsContainer.size();
final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock();
final boolean isAllUpperCase = wordComposer.isAllUpperCase();
@@ -276,8 +268,7 @@ public final class Suggest {
final SuggestedWordInfo typedWordInfo = suggestions.get(0);
typedWordInfo.setDebugString("+");
final int suggestionsSize = suggestions.size();
- final ArrayList<SuggestedWordInfo> suggestionsList =
- CollectionUtils.newArrayList(suggestionsSize);
+ final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>(suggestionsSize);
suggestionsList.add(typedWordInfo);
// 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.
@@ -301,11 +292,11 @@ public final class Suggest {
/* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo(
final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
- final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
+ final boolean isOnlyFirstCharCapitalized, final int trailingSingleQuotesCount) {
final StringBuilder sb = new StringBuilder(wordInfo.mWord.length());
if (isAllUpperCase) {
sb.append(wordInfo.mWord.toUpperCase(locale));
- } else if (isFirstCharCapitalized) {
+ } else if (isOnlyFirstCharCapitalized) {
sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
} else {
sb.append(wordInfo.mWord);
@@ -318,7 +309,7 @@ public final class Suggest {
for (int i = quotesToAppend - 1; i >= 0; --i) {
sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE);
}
- return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind,
+ return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKindAndFlags,
wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord,
wordInfo.mAutoCommitFirstWordConfidence);
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index dc2c9fd0e..72461e17a 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -19,7 +19,6 @@ package com.android.inputmethod.latin;
import android.text.TextUtils;
import android.view.inputmethod.CompletionInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.StringUtils;
import java.util.ArrayList;
@@ -34,8 +33,7 @@ public class SuggestedWords {
// The maximum number of suggestions available.
public static final int MAX_SUGGESTIONS = 18;
- private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
- CollectionUtils.newArrayList(0);
+ private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = new ArrayList<>(0);
public static final SuggestedWords EMPTY = new SuggestedWords(
EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, false, false, false, false);
@@ -165,7 +163,7 @@ public class SuggestedWords {
public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions(
final CompletionInfo[] infos) {
- final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> result = new ArrayList<>();
for (final CompletionInfo info : infos) {
if (null == info || null == info.getText()) {
continue;
@@ -179,8 +177,8 @@ public class SuggestedWords {
// and replace it with what the user currently typed.
public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions(
final String typedWord, final SuggestedWords previousSuggestions) {
- final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList();
- final HashSet<String> alreadySeen = CollectionUtils.newHashSet();
+ final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>();
+ final HashSet<String> alreadySeen = new HashSet<>();
suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */,
@@ -209,7 +207,8 @@ public class SuggestedWords {
public static final int NOT_AN_INDEX = -1;
public static final int NOT_A_CONFIDENCE = -1;
public static final int MAX_SCORE = Integer.MAX_VALUE;
- public static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind
+
+ private static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind
public static final int KIND_TYPED = 0; // What user typed
public static final int KIND_CORRECTION = 1; // Simple correction/suggestion
public static final int KIND_COMPLETION = 2; // Completion (suggestion with appended chars)
@@ -224,16 +223,16 @@ public class SuggestedWords {
public static final int KIND_RESUMED = 9;
public static final int KIND_OOV_CORRECTION = 10; // Most probable string correction
- public static final int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags
public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
public static final int KIND_FLAG_EXACT_MATCH = 0x40000000;
+ public static final int KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION = 0x20000000;
public final String mWord;
// The completion info from the application. Null for suggestions that don't come from
// the application (including keyboard-computed ones, so this is almost always null)
public final CompletionInfo mApplicationSpecifiedCompletionInfo;
public final int mScore;
- public final int mKind; // one of the KIND_* constants above
+ public final int mKindAndFlags;
public final int mCodePointCount;
public final Dictionary mSourceDict;
// For auto-commit. This keeps track of the index inside the touch coordinates array
@@ -249,18 +248,19 @@ public class SuggestedWords {
* Create a new suggested word info.
* @param word The string to suggest.
* @param score A measure of how likely this suggestion is.
- * @param kind The kind of suggestion, as one of the above KIND_* constants.
+ * @param kindAndFlags The kind of suggestion, as one of the above KIND_* constants with
+ * flags.
* @param sourceDict What instance of Dictionary produced this suggestion.
* @param indexOfTouchPointOfSecondWord See mIndexOfTouchPointOfSecondWord.
* @param autoCommitFirstWordConfidence See mAutoCommitFirstWordConfidence.
*/
- public SuggestedWordInfo(final String word, final int score, final int kind,
+ public SuggestedWordInfo(final String word, final int score, final int kindAndFlags,
final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord,
final int autoCommitFirstWordConfidence) {
mWord = word;
mApplicationSpecifiedCompletionInfo = null;
mScore = score;
- mKind = kind;
+ mKindAndFlags = kindAndFlags;
mSourceDict = sourceDict;
mCodePointCount = StringUtils.codePointCount(mWord);
mIndexOfTouchPointOfSecondWord = indexOfTouchPointOfSecondWord;
@@ -276,7 +276,7 @@ public class SuggestedWords {
mWord = applicationSpecifiedCompletion.getText().toString();
mApplicationSpecifiedCompletionInfo = applicationSpecifiedCompletion;
mScore = SuggestedWordInfo.MAX_SCORE;
- mKind = SuggestedWordInfo.KIND_APP_DEFINED;
+ mKindAndFlags = SuggestedWordInfo.KIND_APP_DEFINED;
mSourceDict = Dictionary.DICTIONARY_APPLICATION_DEFINED;
mCodePointCount = StringUtils.codePointCount(mWord);
mIndexOfTouchPointOfSecondWord = SuggestedWordInfo.NOT_AN_INDEX;
@@ -284,7 +284,27 @@ public class SuggestedWords {
}
public boolean isEligibleForAutoCommit() {
- return (KIND_CORRECTION == mKind && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord);
+ return (isKindOf(KIND_CORRECTION) && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord);
+ }
+
+ public int getKind() {
+ return (mKindAndFlags & KIND_MASK_KIND);
+ }
+
+ public boolean isKindOf(final int kind) {
+ return getKind() == kind;
+ }
+
+ public boolean isPossiblyOffensive() {
+ return (mKindAndFlags & KIND_FLAG_POSSIBLY_OFFENSIVE) != 0;
+ }
+
+ public boolean isExactMatch() {
+ return (mKindAndFlags & KIND_FLAG_EXACT_MATCH) != 0;
+ }
+
+ public boolean isExactMatchWithIntentionalOmission() {
+ return (mKindAndFlags & KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION) != 0;
}
public void setDebugString(final String str) {
@@ -337,11 +357,11 @@ public class SuggestedWords {
// SuggestedWords is an immutable object, as much as possible. We must not just remove
// words from the member ArrayList as some other parties may expect the object to never change.
public SuggestedWords getSuggestedWordsExcludingTypedWord() {
- final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>();
String typedWord = null;
for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
- if (SuggestedWordInfo.KIND_TYPED != info.mKind) {
+ if (!info.isKindOf(SuggestedWordInfo.KIND_TYPED)) {
newSuggestions.add(info);
} else {
assert(null == typedWord);
@@ -361,12 +381,12 @@ public class SuggestedWords {
// we should only suggest replacements for this last word.
// TODO: make this work with languages without spaces.
public SuggestedWords getSuggestedWordsForLastWordOfPhraseGesture() {
- final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>();
for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
final int indexOfLastSpace = info.mWord.lastIndexOf(Constants.CODE_SPACE) + 1;
final String lastWord = info.mWord.substring(indexOfLastSpace);
- newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKind,
+ newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKindAndFlags,
info.mSourceDict, SuggestedWordInfo.NOT_AN_INDEX,
SuggestedWordInfo.NOT_A_CONFIDENCE));
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 9d9ce0138..debaad13e 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -28,8 +28,8 @@ import android.provider.UserDictionary.Words;
import android.text.TextUtils;
import android.util.Log;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.UserDictionaryCompatUtils;
-import com.android.inputmethod.latin.utils.LocaleUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
import java.io.File;
@@ -51,43 +51,21 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
// to auto-correct, so we set this to the highest frequency that won't, i.e. 14.
private static final int USER_DICT_SHORTCUT_FREQUENCY = 14;
- // TODO: use Words.SHORTCUT when we target JellyBean or above
- final static String SHORTCUT = "shortcut";
- private static final String[] PROJECTION_QUERY;
- static {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- PROJECTION_QUERY = new String[] {
- Words.WORD,
- SHORTCUT,
- Words.FREQUENCY,
- };
- } else {
- PROJECTION_QUERY = new String[] {
- Words.WORD,
- Words.FREQUENCY,
- };
- }
- }
+ private static final String[] PROJECTION_QUERY_WITH_SHORTCUT = new String[] {
+ Words.WORD,
+ Words.SHORTCUT,
+ Words.FREQUENCY,
+ };
+ private static final String[] PROJECTION_QUERY_WITHOUT_SHORTCUT = new String[] {
+ Words.WORD,
+ Words.FREQUENCY,
+ };
private static final String NAME = "userunigram";
private ContentObserver mObserver;
final private String mLocale;
final private boolean mAlsoUseMoreRestrictiveLocales;
- final public boolean mEnabled;
-
- public UserBinaryDictionary(final Context context, final Locale locale) {
- this(context, locale, false /* alsoUseMoreRestrictiveLocales */, null /* dictFile */);
- }
-
- public UserBinaryDictionary(final Context context, final Locale locale, final File dictFile) {
- this(context, locale, false /* alsoUseMoreRestrictiveLocales */, dictFile);
- }
-
- public UserBinaryDictionary(final Context context, final Locale locale,
- final boolean alsoUseMoreRestrictiveLocales, final File dictFile) {
- this(context, locale, alsoUseMoreRestrictiveLocales, dictFile, NAME);
- }
protected UserBinaryDictionary(final Context context, final Locale locale,
final boolean alsoUseMoreRestrictiveLocales, final File dictFile, final String name) {
@@ -116,14 +94,20 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
// devices. On older versions of the platform, the hook above will be called instead.
@Override
public void onChange(final boolean self, final Uri uri) {
- setNeedsToReload();
+ setNeedsToRecreate();
}
};
cres.registerContentObserver(Words.CONTENT_URI, true, mObserver);
- mEnabled = readIsEnabled();
reloadDictionaryIfRequired();
}
+ @UsedForTesting
+ public static UserBinaryDictionary getDictionary(final Context context, final Locale locale,
+ final File dictFile, final String dictNamePrefix) {
+ return new UserBinaryDictionary(context, locale, false /* alsoUseMoreRestrictiveLocales */,
+ dictFile, dictNamePrefix + NAME);
+ }
+
@Override
public synchronized void close() {
if (mObserver != null) {
@@ -182,10 +166,29 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
} else {
requestArguments = localeElements;
}
+ final String requestString = request.toString();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ try {
+ addWordsFromProjectionLocked(PROJECTION_QUERY_WITH_SHORTCUT, requestString,
+ requestArguments);
+ } catch (IllegalArgumentException e) {
+ // This may happen on some non-compliant devices where the declared API is JB+ but
+ // the SHORTCUT column is not present for some reason.
+ addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString,
+ requestArguments);
+ }
+ } else {
+ addWordsFromProjectionLocked(PROJECTION_QUERY_WITHOUT_SHORTCUT, requestString,
+ requestArguments);
+ }
+ }
+
+ private void addWordsFromProjectionLocked(final String[] query, String request,
+ final String[] requestArguments) throws IllegalArgumentException {
Cursor cursor = null;
try {
cursor = mContext.getContentResolver().query(
- Words.CONTENT_URI, PROJECTION_QUERY, request.toString(), requestArguments, null);
+ Words.CONTENT_URI, query, request, requestArguments, null);
addWordsLocked(cursor);
} catch (final SQLiteException e) {
Log.e(TAG, "SQLiteException in the remote User dictionary process.", e);
@@ -198,8 +201,8 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
}
}
- private boolean readIsEnabled() {
- final ContentResolver cr = mContext.getContentResolver();
+ public static boolean isEnabled(final Context context) {
+ final ContentResolver cr = context.getContentResolver();
final ContentProviderClient client = cr.acquireContentProviderClient(Words.CONTENT_URI);
if (client != null) {
client.release();
@@ -212,18 +215,15 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
/**
* Adds a word to the user dictionary and makes it persistent.
*
+ * @param context the context
+ * @param locale the locale
* @param word the word to add. If the word is capitalized, then the dictionary will
* recognize it as a capitalized word when searched.
*/
- public synchronized void addWordToUserDictionary(final String word) {
+ public static void addWordToUserDictionary(final Context context, final Locale locale,
+ final String word) {
// Update the user dictionary provider
- final Locale locale;
- if (USER_DICTIONARY_ALL_LANGUAGES == mLocale) {
- locale = null;
- } else {
- locale = LocaleUtils.constructLocaleFromString(mLocale);
- }
- UserDictionaryCompatUtils.addWord(mContext, word,
+ UserDictionaryCompatUtils.addWord(context, word,
HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY, null, locale);
}
@@ -245,7 +245,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
if (cursor == null) return;
if (cursor.moveToFirst()) {
final int indexWord = cursor.getColumnIndex(Words.WORD);
- final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(SHORTCUT) : 0;
+ final int indexShortcut = hasShortcutColumn ? cursor.getColumnIndex(Words.SHORTCUT) : 0;
final int indexFrequency = cursor.getColumnIndex(Words.FREQUENCY);
while (!cursor.isAfterLast()) {
final String word = cursor.getString(indexWord);
@@ -255,12 +255,12 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
// Safeguard against adding really long words.
if (word.length() < MAX_WORD_LENGTH) {
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addWordDynamicallyLocked(word, adjustedFrequency, null /* shortcutTarget */,
+ addUnigramLocked(word, adjustedFrequency, null /* shortcutTarget */,
0 /* shortcutFreq */, false /* isNotAWord */,
false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
runGCIfRequiredLocked(true /* mindsBlockByGC */);
- addWordDynamicallyLocked(shortcut, adjustedFrequency, word,
+ addUnigramLocked(shortcut, adjustedFrequency, word,
USER_DICT_SHORTCUT_FREQUENCY, true /* isNotAWord */,
false /* isBlacklisted */, BinaryDictionary.NOT_A_VALID_TIMESTAMP);
}
@@ -269,9 +269,4 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
}
}
}
-
- @Override
- protected boolean haveContentsChanged() {
- return true;
- }
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index d755195f2..6ce1f85c5 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin;
import com.android.inputmethod.event.CombinerChain;
import com.android.inputmethod.event.Event;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CoordinateUtils;
import com.android.inputmethod.latin.utils.StringUtils;
@@ -41,15 +40,11 @@ public final class WordComposer {
public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
private CombinerChain mCombinerChain;
+ private String mCombiningSpec; // Memory so that we don't uselessly recreate the combiner chain
// The list of events that served to compose this string.
private final ArrayList<Event> mEvents;
private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
- // The previous word (before the composing word). Used as context for suggestions. May be null
- // after resetting and before starting a new composing word, or when there is no context like
- // at the start of text for example. It can also be set to null externally when the user
- // enters a separator that does not let bigrams across, like a period or a comma.
- private String mPreviousWordForSuggestion;
private String mAutoCorrection;
private boolean mIsResumed;
private boolean mIsBatchMode;
@@ -74,23 +69,36 @@ public final class WordComposer {
private int mCursorPositionWithinWord;
/**
- * Whether the user chose to capitalize the first char of the word.
+ * Whether the composing word has the only first char capitalized.
*/
- private boolean mIsFirstCharCapitalized;
+ private boolean mIsOnlyFirstCharCapitalized;
public WordComposer() {
- mCombinerChain = new CombinerChain();
- mEvents = CollectionUtils.newArrayList();
+ mCombinerChain = new CombinerChain("");
+ mEvents = new ArrayList<>();
mAutoCorrection = null;
mIsResumed = false;
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null;
- mPreviousWordForSuggestion = null;
refreshTypedWordCache();
}
/**
+ * Restart the combiners, possibly with a new spec.
+ * @param combiningSpec The spec string for combining. This is found in the extra value.
+ */
+ public void restartCombining(final String combiningSpec) {
+ final String nonNullCombiningSpec = null == combiningSpec ? "" : combiningSpec;
+ if (!nonNullCombiningSpec.equals(mCombiningSpec)) {
+ mCombinerChain = new CombinerChain(
+ mCombinerChain.getComposingWordWithCombiningFeedback().toString(),
+ CombinerChain.createCombiners(nonNullCombiningSpec));
+ mCombiningSpec = nonNullCombiningSpec;
+ }
+ }
+
+ /**
* Clear out the keys registered so far.
*/
public void reset() {
@@ -99,12 +107,11 @@ public final class WordComposer {
mAutoCorrection = null;
mCapsCount = 0;
mDigitsCount = 0;
- mIsFirstCharCapitalized = false;
+ mIsOnlyFirstCharCapitalized = false;
mIsResumed = false;
mIsBatchMode = false;
mCursorPositionWithinWord = 0;
mRejectedBatchModeSuggestion = null;
- mPreviousWordForSuggestion = null;
refreshTypedWordCache();
}
@@ -133,8 +140,12 @@ public final class WordComposer {
*/
public int copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
final int[] destination) {
+ // This method can be called on a separate thread and mTypedWordCache can change while we
+ // are executing this method.
+ final String typedWord = mTypedWordCache.toString();
// lastIndex is exclusive
- final int lastIndex = mTypedWordCache.length() - trailingSingleQuotesCount();
+ final int lastIndex = typedWord.length()
+ - StringUtils.getTrailingSingleQuotesCount(typedWord);
if (lastIndex <= 0) {
// The string is empty or contains only single quotes.
return 0;
@@ -142,11 +153,11 @@ public final class WordComposer {
// The following function counts the number of code points in the text range which begins
// at index 0 and extends to the character at lastIndex.
- final int codePointSize = Character.codePointCount(mTypedWordCache, 0, lastIndex);
+ final int codePointSize = Character.codePointCount(typedWord, 0, lastIndex);
if (codePointSize > destination.length) {
return -1;
}
- return StringUtils.copyCodePointsAndReturnCodePointCount(destination, mTypedWordCache, 0,
+ return StringUtils.copyCodePointsAndReturnCodePointCount(destination, typedWord, 0,
lastIndex, true /* downCase */);
}
@@ -162,12 +173,6 @@ public final class WordComposer {
return mInputPointers;
}
- private static boolean isFirstCharCapitalized(final int index, final int codePoint,
- final boolean previous) {
- if (index == 0) return Character.isUpperCase(codePoint);
- return previous && !Character.isUpperCase(codePoint);
- }
-
/**
* Process an input event.
*
@@ -187,7 +192,7 @@ public final class WordComposer {
mCursorPositionWithinWord = mCodePointSize;
// We may have deleted the last one.
if (0 == mCodePointSize) {
- mIsFirstCharCapitalized = false;
+ mIsOnlyFirstCharCapitalized = false;
}
if (Constants.CODE_DELETE != event.mKeyCode) {
if (newIndex < MAX_WORD_LENGTH) {
@@ -199,8 +204,12 @@ public final class WordComposer {
mInputPointers.addPointerAt(newIndex, keyX, keyY, 0, 0);
}
}
- mIsFirstCharCapitalized = isFirstCharCapitalized(
- newIndex, primaryCode, mIsFirstCharCapitalized);
+ if (0 == newIndex) {
+ mIsOnlyFirstCharCapitalized = Character.isUpperCase(primaryCode);
+ } else {
+ mIsOnlyFirstCharCapitalized = mIsOnlyFirstCharCapitalized
+ && !Character.isUpperCase(primaryCode);
+ }
if (Character.isUpperCase(primaryCode)) mCapsCount++;
if (Character.isDigit(primaryCode)) mDigitsCount++;
}
@@ -280,11 +289,8 @@ public final class WordComposer {
* This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity.
* @param codePoints the code points to set as the composing word.
* @param coordinates the x, y coordinates of the key in the CoordinateUtils format
- * @param previousWord the previous word, to use as context for suggestions. Can be null if
- * the context is nil (typically, at start of text).
*/
- public void setComposingWord(final int[] codePoints, final int[] coordinates,
- final CharSequence previousWord) {
+ public void setComposingWord(final int[] codePoints, final int[] coordinates) {
reset();
final int length = codePoints.length;
for (int i = 0; i < length; ++i) {
@@ -293,7 +299,6 @@ public final class WordComposer {
CoordinateUtils.yFromArray(coordinates, i)));
}
mIsResumed = true;
- mPreviousWordForSuggestion = null == previousWord ? null : previousWord.toString();
}
/**
@@ -304,25 +309,13 @@ public final class WordComposer {
return mTypedWordCache.toString();
}
- public String getPreviousWordForSuggestion() {
- return mPreviousWordForSuggestion;
- }
-
/**
- * Whether or not the user typed a capital letter as the first letter in the word
+ * Whether or not the user typed a capital letter as the first letter in the word, and no
+ * other letter is capitalized
* @return capitalization preference
*/
- public boolean isFirstCharCapitalized() {
- return mIsFirstCharCapitalized;
- }
-
- public int trailingSingleQuotesCount() {
- final int lastIndex = mTypedWordCache.length() - 1;
- int i = lastIndex;
- while (i >= 0 && mTypedWordCache.charAt(i) == Constants.CODE_SINGLE_QUOTE) {
- --i;
- }
- return lastIndex - i;
+ public boolean isOnlyFirstCharCapitalized() {
+ return mIsOnlyFirstCharCapitalized;
}
/**
@@ -358,7 +351,7 @@ public final class WordComposer {
}
/**
- * Saves the caps mode and the previous word at the start of composing.
+ * Saves the caps mode at the start of composing.
*
* WordComposer needs to know about the caps mode for several reasons. The first is, we need
* to know after the fact what the reason was, to register the correct form into the user
@@ -367,12 +360,9 @@ public final class WordComposer {
* Also, batch input needs to know about the current caps mode to display correctly
* capitalized suggestions.
* @param mode the mode at the time of start
- * @param previousWord the previous word as context for suggestions. May be null if none.
*/
- public void setCapitalizedModeAndPreviousWordAtStartComposingTime(final int mode,
- final CharSequence previousWord) {
+ public void setCapitalizedModeAtStartComposingTime(final int mode) {
mCapitalizedMode = mode;
- mPreviousWordForSuggestion = null == previousWord ? null : previousWord.toString();
}
/**
@@ -408,13 +398,13 @@ public final class WordComposer {
// `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above.
// committedWord should contain suggestion spans if applicable.
public LastComposedWord commitWord(final int type, final CharSequence committedWord,
- final String separatorString, final String prevWord) {
+ final String separatorString, final PrevWordsInfo prevWordsInfo) {
// 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 LastComposedWord lastComposedWord = new LastComposedWord(mEvents,
mInputPointers, mTypedWordCache.toString(), committedWord, separatorString,
- prevWord, mCapitalizedMode);
+ prevWordsInfo, mCapitalizedMode);
mInputPointers.reset();
if (type != LastComposedWord.COMMIT_TYPE_DECIDED_WORD
&& type != LastComposedWord.COMMIT_TYPE_MANUAL_PICK) {
@@ -423,11 +413,10 @@ public final class WordComposer {
mCapsCount = 0;
mDigitsCount = 0;
mIsBatchMode = false;
- mPreviousWordForSuggestion = committedWord.toString();
mCombinerChain.reset();
mEvents.clear();
mCodePointSize = 0;
- mIsFirstCharCapitalized = false;
+ mIsOnlyFirstCharCapitalized = false;
mCapitalizedMode = CAPS_MODE_OFF;
refreshTypedWordCache();
mAutoCorrection = null;
@@ -437,15 +426,7 @@ public final class WordComposer {
return lastComposedWord;
}
- // Call this when the recorded previous word should be discarded. This is typically called
- // when the user inputs a separator that's not whitespace (including the case of the
- // double-space-to-period feature).
- public void discardPreviousWordForSuggestion() {
- mPreviousWordForSuggestion = null;
- }
-
- public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord,
- final String previousWord) {
+ public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) {
mEvents.clear();
Collections.copy(mEvents, lastComposedWord.mEvents);
mInputPointers.set(lastComposedWord.mInputPointers);
@@ -456,7 +437,6 @@ public final class WordComposer {
mCursorPositionWithinWord = mCodePointSize;
mRejectedBatchModeSuggestion = null;
mIsResumed = true;
- mPreviousWordForSuggestion = previousWord;
}
public boolean isBatchMode() {
diff --git a/java/src/com/android/inputmethod/latin/WordListInfo.java b/java/src/com/android/inputmethod/latin/WordListInfo.java
index 5ac806a0c..268fe9818 100644
--- a/java/src/com/android/inputmethod/latin/WordListInfo.java
+++ b/java/src/com/android/inputmethod/latin/WordListInfo.java
@@ -22,8 +22,10 @@ package com.android.inputmethod.latin;
public final class WordListInfo {
public final String mId;
public final String mLocale;
- public WordListInfo(final String id, final String locale) {
+ public final String mRawChecksum;
+ public WordListInfo(final String id, final String locale, final String rawChecksum) {
mId = id;
mLocale = locale;
+ mRawChecksum = rawChecksum;
}
}
diff --git a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
index 139e73aa4..7071d8689 100644
--- a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
+++ b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
@@ -27,7 +27,6 @@ import com.android.inputmethod.latin.BinaryDictionaryFileDumper;
import com.android.inputmethod.latin.BinaryDictionaryGetter;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.DialogUtils;
import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
import com.android.inputmethod.latin.utils.LocaleUtils;
@@ -50,7 +49,7 @@ public class ExternalDictionaryGetterForDebug {
private static String[] findDictionariesInTheDownloadedFolder() {
final File[] files = new File(SOURCE_FOLDER).listFiles();
- final ArrayList<String> eligibleList = CollectionUtils.newArrayList();
+ final ArrayList<String> eligibleList = new ArrayList<>();
for (File f : files) {
final DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(f);
if (null == header) continue;
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index af899c040..972580298 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -21,13 +21,6 @@ public final class ProductionFlag {
// This class is not publicly instantiable.
}
- public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS = false;
-
- // When false, USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG suggests that all guarded
- // class-private DEBUG flags should be false, and any privacy controls should be enforced.
- // USES_DEVELOPMENT_ONLY_DIAGNOSTICS must be false for any production build.
- public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG = false;
-
public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false;
// When true, enable {@link InputMethodService#onUpdateCursor} callback with
@@ -38,4 +31,7 @@ public final class ProductionFlag {
// Include all suggestions from all dictionaries in {@link SuggestedWords#mRawSuggestions}.
public static final boolean INCLUDE_RAW_SUGGESTIONS = false;
+
+ // When false, the metrics logging is not yet ready to be enabled.
+ public static final boolean IS_METRICS_LOGGING_SUPPORTED = false;
}
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index d2100d415..24cc1ef0d 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -32,29 +32,26 @@ import com.android.inputmethod.event.InputTransaction;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
+import com.android.inputmethod.latin.DictionaryFacilitator;
import com.android.inputmethod.latin.InputPointers;
import com.android.inputmethod.latin.LastComposedWord;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.RichInputConnection;
import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.InputTypeUtils;
-import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
import com.android.inputmethod.latin.utils.StringUtils;
import com.android.inputmethod.latin.utils.TextRange;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
import java.util.TreeSet;
@@ -78,7 +75,8 @@ public final class InputLogic {
private int mSpaceState;
// Never null
public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
- public final Suggest mSuggest = new Suggest();
+ public final Suggest mSuggest;
+ private final DictionaryFacilitator mDictionaryFacilitator;
public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
public final WordComposer mWordComposer;
@@ -87,7 +85,7 @@ public final class InputLogic {
private int mDeleteCount;
private long mLastKeyTime;
- public final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
+ public final TreeSet<Long> mCurrentlyPressedHardwareKeys = new TreeSet<>();
// Keeps track of most recently inserted text (multi-character key) for reverting
private String mEnteredText;
@@ -97,13 +95,23 @@ public final class InputLogic {
private boolean mIsAutoCorrectionIndicatorOn;
private long mDoubleSpacePeriodCountdownStart;
+ /**
+ * Create a new instance of the input logic.
+ * @param latinIME the instance of the parent LatinIME. We should remove this when we can.
+ * @param suggestionStripViewAccessor an object to access the suggestion strip view.
+ * @param dictionaryFacilitator facilitator for getting suggestions and updating user history
+ * dictionary.
+ */
public InputLogic(final LatinIME latinIME,
- final SuggestionStripViewAccessor suggestionStripViewAccessor) {
+ final SuggestionStripViewAccessor suggestionStripViewAccessor,
+ final DictionaryFacilitator dictionaryFacilitator) {
mLatinIME = latinIME;
mSuggestionStripViewAccessor = suggestionStripViewAccessor;
mWordComposer = new WordComposer();
mConnection = new RichInputConnection(latinIME);
mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
+ mSuggest = new Suggest(dictionaryFacilitator);
+ mDictionaryFacilitator = dictionaryFacilitator;
}
/**
@@ -117,13 +125,16 @@ public final class InputLogic {
*
* @param restarting whether input is starting in the same field as before. Unused for now.
* @param editorInfo the editorInfo associated with the editor.
+ * @param combiningSpec the combining spec string for this subtype
*/
- public void startInput(final boolean restarting, final EditorInfo editorInfo) {
+ public void startInput(final boolean restarting, final EditorInfo editorInfo,
+ final String combiningSpec) {
mEnteredText = null;
+ mWordComposer.restartCombining(combiningSpec);
resetComposingState(true /* alsoResetLastComposedWord */);
mDeleteCount = 0;
mSpaceState = SpaceState.NONE;
- mRecapitalizeStatus.deactivate();
+ mRecapitalizeStatus.disable(); // Do not perform recapitalize until the cursor is moved once
mCurrentlyPressedHardwareKeys.clear();
mSuggestedWords = SuggestedWords.EMPTY;
// In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying
@@ -138,6 +149,14 @@ public final class InputLogic {
}
/**
+ * Call this when the subtype changes.
+ * @param combiningSpec the spec string for the combining rules
+ */
+ public void onSubtypeChanged(final String combiningSpec) {
+ mWordComposer.restartCombining(combiningSpec);
+ }
+
+ /**
* Clean up the input logic after input is finished.
*/
public void finishInput() {
@@ -156,7 +175,7 @@ public final class InputLogic {
final InputLogicHandler inputLogicHandler = mInputLogicHandler;
mInputLogicHandler = InputLogicHandler.NULL_HANDLER;
inputLogicHandler.destroy();
- mSuggest.mDictionaryFacilitator.closeDictionaries();
+ mDictionaryFacilitator.closeDictionaries();
}
/**
@@ -179,19 +198,11 @@ public final class InputLogic {
resetComposingState(true /* alsoResetLastComposedWord */);
}
handler.postUpdateSuggestionStrip();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
- && ResearchLogger.RESEARCH_KEY_OUTPUT_TEXT.equals(rawText)) {
- ResearchLogger.getInstance().onResearchKeySelected(mLatinIME);
- return;
- }
final String text = performSpecificTldProcessingOnTextInput(rawText);
if (SpaceState.PHANTOM == mSpaceState) {
promotePhantomSpace(settingsValues);
}
mConnection.commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */);
- }
mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.NONE;
@@ -218,14 +229,8 @@ public final class InputLogic {
// If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
if (suggestion.length() == 1 && suggestedWords.isPunctuationSuggestions()) {
// Word separators are suggested before the user inputs something.
- // So, LatinImeLogger logs "" as a user's input.
- LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords);
// Rely on onCodeInput to do the complicated swapping/stripping logic consistently.
final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
- false /* isBatchMode */, suggestedWords.mIsPrediction);
- }
return onCodeInput(settingsValues, event, keyboardShiftState, handler);
}
@@ -248,7 +253,7 @@ public final class InputLogic {
// code path as for other kinds, use commitChosenWord, and do everything normally. We will
// however need to reset the suggestion strip right away, because we know we can't take
// the risk of calling commitCompletion twice because we don't know how the app will react.
- if (SuggestedWordInfo.KIND_APP_DEFINED == suggestionInfo.mKind) {
+ if (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_APP_DEFINED)) {
mSuggestedWords = SuggestedWords.EMPTY;
mSuggestionStripViewAccessor.setNeutralSuggestionStrip();
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
@@ -261,14 +266,8 @@ public final class InputLogic {
// We need to log before we commit, because the word composer will store away the user
// typed word.
final String replacedWord = mWordComposer.getTypedWord();
- LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords);
commitChosenWord(settingsValues, suggestion,
LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
- mWordComposer.isBatchMode(), suggestionInfo.mScore,
- suggestionInfo.mKind, suggestionInfo.mSourceDict.mDictType);
- }
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
mLastComposedWord.deactivate();
@@ -278,18 +277,12 @@ public final class InputLogic {
// We should show the "Touch again to save" hint if the user pressed the first entry
// AND it's in none of our current dictionaries (main, user or otherwise).
- final DictionaryFacilitatorForSuggest dictionaryFacilitator =
- mSuggest.mDictionaryFacilitator;
final boolean showingAddToDictionaryHint =
- (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
- || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
- && !dictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */);
+ (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_TYPED)
+ || suggestionInfo.isKindOf(SuggestedWordInfo.KIND_OOV_CORRECTION))
+ && !mDictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */);
- if (settingsValues.mIsInternal) {
- LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
- }
- if (showingAddToDictionaryHint && dictionaryFacilitator.isUserDictionaryEnabled()) {
+ if (showingAddToDictionaryHint && mDictionaryFacilitator.isUserDictionaryEnabled()) {
mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
} else {
// If we're not showing the "Touch again to save", then update the suggestion strip.
@@ -326,8 +319,16 @@ public final class InputLogic {
|| !mWordComposer.isComposingWord(); // safe to reset
final boolean hasOrHadSelection = (oldSelStart != oldSelEnd || newSelStart != newSelEnd);
final int moveAmount = newSelStart - oldSelStart;
- if (selectionChangedOrSafeToReset && (hasOrHadSelection
- || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
+ // As an added small gift from the framework, it happens upon rotation when there
+ // is a selection that we get a wrong cursor position delivered to startInput() that
+ // does not get reflected in the oldSel{Start,End} parameters to the next call to
+ // onUpdateSelection. In this case, we may have set a composition, and when we're here
+ // we realize we shouldn't have. In theory, in this case, selectionChangedOrSafeToReset
+ // should be true, but that is if the framework had taken that wrong cursor position
+ // into account, which means we have to reset the entire composing state whenever there
+ // is or was a selection regardless of whether it changed or not.
+ if (hasOrHadSelection || (selectionChangedOrSafeToReset
+ && !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
// If we are composing a word and moving the cursor, we would want to set a
// suggestion span for recorrection to work correctly. Unfortunately, that
// would involve the keyboard committing some new text, which would move the
@@ -352,10 +353,12 @@ public final class InputLogic {
newSelStart, newSelEnd, false /* shouldFinishComposition */);
}
+ // The cursor has been moved : we now accept to perform recapitalization
+ mRecapitalizeStatus.enable();
// We moved the cursor. If we are touching a word, we need to resume suggestion.
- mLatinIME.mHandler.postResumeSuggestions();
- // Reset the last recapitalization.
- mRecapitalizeStatus.deactivate();
+ mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */);
+ // Stop the last recapitalization, if started.
+ mRecapitalizeStatus.stop();
return true;
}
@@ -376,16 +379,9 @@ public final class InputLogic {
final int keyboardShiftMode,
// TODO: remove this argument
final LatinIME.UIHandler handler) {
- // TODO: rework the following to not squash the keycode and the code point into the same
- // var because it's confusing. Instead the switch() should handle this in a readable manner.
- final int code =
- Event.NOT_A_CODE_POINT == event.mCodePoint ? event.mKeyCode : event.mCodePoint;
final InputTransaction inputTransaction = new InputTransaction(settingsValues, event,
SystemClock.uptimeMillis(), mSpaceState,
getActualCapsMode(settingsValues, keyboardShiftMode));
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onCodeInput(code, event.mX, event.mY);
- }
if (event.mKeyCode != Constants.CODE_DELETE
|| inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) {
mDeleteCount = 0;
@@ -407,7 +403,6 @@ public final class InputLogic {
switch (event.mKeyCode) {
case Constants.CODE_DELETE:
handleBackspace(inputTransaction);
- LatinImeLogger.logOnDelete(event.mX, event.mY);
break;
case Constants.CODE_SHIFT:
performRecapitalization(inputTransaction.mSettingsValues);
@@ -513,12 +508,6 @@ public final class InputLogic {
++mAutoCommitSequenceNumber;
mConnection.beginBatchEdit();
if (mWordComposer.isComposingWord()) {
- if (settingsValues.mIsInternal) {
- if (mWordComposer.isBatchMode()) {
- LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ",
- mWordComposer);
- }
- }
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the batch input at the current cursor position.
@@ -555,11 +544,8 @@ public final class InputLogic {
}
}
mConnection.endBatchEdit();
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
- getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()),
- // Prev word is 1st word before cursor
- getNthPreviousWordForSuggestion(
- settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */));
+ mWordComposer.setCapitalizedModeAtStartComposingTime(
+ getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()));
}
/* The sequence number member is only used in onUpdateBatchInput. It is increased each time
@@ -588,16 +574,15 @@ public final class InputLogic {
if (null != candidate
&& mSuggestedWords.mSequenceNumber >= mAutoCommitSequenceNumber) {
if (candidate.mSourceDict.shouldAutoCommit(candidate)) {
- final String[] commitParts = candidate.mWord.split(" ", 2);
+ final String[] commitParts = candidate.mWord.split(Constants.WORD_SEPARATOR, 2);
batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
promotePhantomSpace(settingsValues);
mConnection.commitText(commitParts[0], 0);
mSpaceState = SpaceState.PHANTOM;
keyboardSwitcher.requestUpdatingShiftState(
getCurrentAutoCapsState(settingsValues), getCurrentRecapitalizeState());
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
- getActualCapsMode(settingsValues,
- keyboardSwitcher.getKeyboardShiftMode()), commitParts[0]);
+ mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode(
+ settingsValues, keyboardSwitcher.getKeyboardShiftMode()));
++mAutoCommitSequenceNumber;
}
}
@@ -657,19 +642,9 @@ public final class InputLogic {
|| Character.getType(codePoint) == Character.OTHER_SYMBOL) {
didAutoCorrect = handleSeparator(inputTransaction,
inputTransaction.mEvent.isSuggestionStripPress(), handler);
- if (inputTransaction.mSettingsValues.mIsInternal) {
- LatinImeLoggerUtils.onSeparator((char)codePoint,
- inputTransaction.mEvent.mX, inputTransaction.mEvent.mY);
- }
} else {
didAutoCorrect = false;
if (SpaceState.PHANTOM == inputTransaction.mSpaceState) {
- if (inputTransaction.mSettingsValues.mIsInternal) {
- if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
- LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ",
- mWordComposer);
- }
- }
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the character at the current cursor position.
@@ -730,11 +705,10 @@ public final class InputLogic {
(!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)
|| !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces)) {
// Reset entirely the composing state anyway, then start composing a new word unless
- // the character is a single quote or a dash. The idea here is, single quote and dash
- // are not separators and they should be treated as normal characters, except in the
- // first position where they should not start composing a word.
- isComposingWord = (Constants.CODE_SINGLE_QUOTE != codePoint
- && Constants.CODE_DASH != codePoint);
+ // the character is a word connector. The idea here is, word connectors are not
+ // separators and they should be treated as normal characters, except in the first
+ // position where they should not start composing a word.
+ isComposingWord = !settingsValues.mSpacingAndPunctuations.isWordConnector(codePoint);
// 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
@@ -745,11 +719,7 @@ public final class InputLogic {
mWordComposer.processEvent(inputTransaction.mEvent);
// If it's the first letter, make note of auto-caps state
if (mWordComposer.isSingleLetter()) {
- // We pass 1 to getPreviousWordForSuggestion because we were not composing a word
- // yet, so the word we want is the 1st word before the cursor.
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
- inputTransaction.mShiftState, getNthPreviousWordForSuggestion(
- settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */));
+ mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.mShiftState);
}
mConnection.setComposingText(getTextWithUnderline(
mWordComposer.getTypedWord()), 1);
@@ -767,10 +737,6 @@ public final class InputLogic {
mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
}
inputTransaction.setRequiresUpdateSuggestions();
- if (settingsValues.mIsInternal) {
- LatinImeLoggerUtils.onNonSeparator((char)codePoint, inputTransaction.mEvent.mX,
- inputTransaction.mEvent.mY);
- }
}
/**
@@ -784,12 +750,13 @@ public final class InputLogic {
// TODO: remove this argument
final LatinIME.UIHandler handler) {
final int codePoint = inputTransaction.mEvent.mCodePoint;
+ final SettingsValues settingsValues = inputTransaction.mSettingsValues;
boolean didAutoCorrect = false;
+ final boolean wasComposingWord = mWordComposer.isComposingWord();
// We avoid sending spaces in languages without spaces if we were composing.
final boolean shouldAvoidSendingCode = Constants.CODE_SPACE == codePoint
- && !inputTransaction.mSettingsValues.mSpacingAndPunctuations
- .mCurrentLanguageHasSpaces
- && mWordComposer.isComposingWord();
+ && !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
+ && wasComposingWord;
if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
// If we are in the middle of a recorrection, we need to commit the recorrection
// first so that we can insert the separator at the current cursor position.
@@ -798,13 +765,13 @@ public final class InputLogic {
}
// isComposingWord() may have changed since we stored wasComposing
if (mWordComposer.isComposingWord()) {
- if (inputTransaction.mSettingsValues.mCorrectionEnabled) {
+ if (settingsValues.mAutoCorrectionEnabled) {
final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR
: StringUtils.newSingleCodePointString(codePoint);
- commitCurrentAutoCorrection(inputTransaction.mSettingsValues, separator, handler);
+ commitCurrentAutoCorrection(settingsValues, separator, handler);
didAutoCorrect = true;
} else {
- commitTyped(inputTransaction.mSettingsValues,
+ commitTyped(settingsValues,
StringUtils.newSingleCodePointString(codePoint));
}
}
@@ -821,38 +788,41 @@ public final class InputLogic {
// Double quotes behave like they are usually preceded by space iff we are
// not inside a double quote or after a digit.
needsPrecedingSpace = !isInsideDoubleQuoteOrAfterDigit;
+ } else if (settingsValues.mSpacingAndPunctuations.isClusteringSymbol(codePoint)
+ && settingsValues.mSpacingAndPunctuations.isClusteringSymbol(
+ mConnection.getCodePointBeforeCursor())) {
+ needsPrecedingSpace = false;
} else {
- needsPrecedingSpace = inputTransaction.mSettingsValues.isUsuallyPrecededBySpace(
- codePoint);
+ needsPrecedingSpace = settingsValues.isUsuallyPrecededBySpace(codePoint);
}
if (needsPrecedingSpace) {
- promotePhantomSpace(inputTransaction.mSettingsValues);
- }
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleSeparator(codePoint, mWordComposer.isComposingWord());
+ promotePhantomSpace(settingsValues);
}
if (!shouldAvoidSendingCode) {
- sendKeyCodePoint(inputTransaction.mSettingsValues, codePoint);
+ sendKeyCodePoint(settingsValues, codePoint);
}
if (Constants.CODE_SPACE == codePoint) {
if (maybeDoubleSpacePeriod(inputTransaction)) {
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
+ inputTransaction.setRequiresUpdateSuggestions();
mSpaceState = SpaceState.DOUBLE;
} else if (!mSuggestedWords.isPunctuationSuggestions()) {
mSpaceState = SpaceState.WEAK;
}
startDoubleSpacePeriodCountdown(inputTransaction);
- inputTransaction.setRequiresUpdateSuggestions();
+ if (wasComposingWord) {
+ inputTransaction.setRequiresUpdateSuggestions();
+ }
} else {
if (swapWeakSpace) {
swapSwapperAndSpace(inputTransaction);
mSpaceState = SpaceState.SWAP_PUNCTUATION;
} else if ((SpaceState.PHANTOM == inputTransaction.mSpaceState
- && inputTransaction.mSettingsValues.isUsuallyFollowedBySpace(codePoint))
+ && settingsValues.isUsuallyFollowedBySpace(codePoint))
|| (Constants.CODE_DOUBLE_QUOTE == codePoint
&& isInsideDoubleQuoteOrAfterDigit)) {
// If we are in phantom space state, and the user presses a separator, we want to
@@ -907,23 +877,20 @@ public final class InputLogic {
}
if (mWordComposer.isComposingWord()) {
if (mWordComposer.isBatchMode()) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final String word = mWordComposer.getTypedWord();
- ResearchLogger.latinIME_handleBackspace_batch(word, 1);
- }
final String rejectedSuggestion = mWordComposer.getTypedWord();
mWordComposer.reset();
mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
} else {
mWordComposer.processEvent(inputTransaction.mEvent);
}
- mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+ if (mWordComposer.isComposingWord()) {
+ mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+ } else {
+ mConnection.commitText("", 1);
+ }
inputTransaction.setRequiresUpdateSuggestions();
} else {
if (mLastComposedWord.canRevertCommit()) {
- if (inputTransaction.mSettingsValues.mIsInternal) {
- LatinImeLoggerUtils.onAutoCorrectionCancellation();
- }
revertCommit(inputTransaction);
return;
}
@@ -932,9 +899,6 @@ public final class InputLogic {
// This is triggered on backspace after a key that inputs multiple characters,
// like the smiley key or the .com key.
mConnection.deleteSurroundingText(mEnteredText.length(), 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText);
- }
mEnteredText = null;
// If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
// In addition we know that spaceState is false, and that we should not be
@@ -946,6 +910,9 @@ public final class InputLogic {
if (mConnection.revertDoubleSpacePeriod()) {
// No need to reset mSpaceState, it has already be done (that's why we
// receive it as a parameter)
+ inputTransaction.setRequiresUpdateSuggestions();
+ mWordComposer.setCapitalizedModeAtStartComposingTime(
+ WordComposer.CAPS_MODE_OFF);
return;
}
} else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) {
@@ -964,10 +931,6 @@ public final class InputLogic {
mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
mConnection.getExpectedSelectionEnd());
mConnection.deleteSurroundingText(numCharsDeleted, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
- false /* shouldUncommitLogUnit */);
- }
} else {
// There is no selection, just delete one character.
if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) {
@@ -1002,10 +965,6 @@ public final class InputLogic {
final int lengthToDelete =
Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDelete, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(lengthToDelete,
- true /* shouldUncommitLogUnit */);
- }
if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) {
final int codePointBeforeCursorToDeleteAgain =
mConnection.getCodePointBeforeCursor();
@@ -1013,21 +972,18 @@ public final class InputLogic {
final int lengthToDeleteAgain = Character.isSupplementaryCodePoint(
codePointBeforeCursorToDeleteAgain) ? 2 : 1;
mConnection.deleteSurroundingText(lengthToDeleteAgain, 0);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain,
- true /* shouldUncommitLogUnit */);
- }
}
}
}
}
- if (inputTransaction.mSettingsValues.isSuggestionStripVisible()
+ if (inputTransaction.mSettingsValues
+ .isCurrentOrientationAllowingSuggestionsPerUserSettings()
&& inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mCurrentLanguageHasSpaces
&& !mConnection.isCursorFollowedByWordCharacter(
inputTransaction.mSettingsValues.mSpacingAndPunctuations)) {
restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues,
- true /* includeResumedWordInSuggestions */);
+ true /* shouldIncludeResumedWordInSuggestions */);
}
}
}
@@ -1053,9 +1009,6 @@ public final class InputLogic {
mConnection.deleteSurroundingText(2, 0);
final String text = lastTwo.charAt(1) + " ";
mConnection.commitText(text, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
- }
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
}
}
@@ -1139,11 +1092,6 @@ public final class InputLogic {
final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations
.mSentenceSeparatorAndSpace;
mConnection.commitText(textToInsert, 1);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
- false /* isBatchMode */);
- }
- mWordComposer.discardPreviousWordForSuggestion();
return true;
}
return false;
@@ -1180,18 +1128,24 @@ public final class InputLogic {
* @param settingsValues The current settings values.
*/
private void performRecapitalization(final SettingsValues settingsValues) {
- if (!mConnection.hasSelection()) {
- return; // No selection
+ if (!mConnection.hasSelection() || !mRecapitalizeStatus.mIsEnabled()) {
+ return; // No selection or recapitalize is disabled for now
+ }
+ final int selectionStart = mConnection.getExpectedSelectionStart();
+ final int selectionEnd = mConnection.getExpectedSelectionEnd();
+ final int numCharsSelected = selectionEnd - selectionStart;
+ if (numCharsSelected > Constants.MAX_CHARACTERS_FOR_RECAPITALIZATION) {
+ // We bail out if we have too many characters for performance reasons. We don't want
+ // to suck possibly multiple-megabyte data.
+ return;
}
- // If we have a recapitalize in progress, use it; otherwise, create a new one.
- if (!mRecapitalizeStatus.isActive()
- || !mRecapitalizeStatus.isSetAt(mConnection.getExpectedSelectionStart(),
- mConnection.getExpectedSelectionEnd())) {
+ // If we have a recapitalize in progress, use it; otherwise, start a new one.
+ if (!mRecapitalizeStatus.isStarted()
+ || !mRecapitalizeStatus.isSetAt(selectionStart, selectionEnd)) {
final CharSequence selectedText =
mConnection.getSelectedText(0 /* flags, 0 for no styles */);
if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection
- mRecapitalizeStatus.initialize(mConnection.getExpectedSelectionStart(),
- mConnection.getExpectedSelectionEnd(), selectedText.toString(),
+ mRecapitalizeStatus.start(selectionStart, selectionEnd, selectedText.toString(),
settingsValues.mLocale,
settingsValues.mSpacingAndPunctuations.mSortedWordSeparators);
// We trim leading and trailing whitespace.
@@ -1199,30 +1153,27 @@ public final class InputLogic {
}
mConnection.finishComposingText();
mRecapitalizeStatus.rotate();
- final int numCharsDeleted = mConnection.getExpectedSelectionEnd()
- - mConnection.getExpectedSelectionStart();
- mConnection.setSelection(mConnection.getExpectedSelectionEnd(),
- mConnection.getExpectedSelectionEnd());
- mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ mConnection.setSelection(selectionEnd, selectionEnd);
+ mConnection.deleteSurroundingText(numCharsSelected, 0);
mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
mConnection.setSelection(mRecapitalizeStatus.getNewCursorStart(),
mRecapitalizeStatus.getNewCursorEnd());
}
private void performAdditionToUserHistoryDictionary(final SettingsValues settingsValues,
- final String suggestion, final String prevWord) {
+ final String suggestion, final PrevWordsInfo prevWordsInfo) {
// If correction is not enabled, we don't add words to the user history dictionary.
// That's to avoid unintended additions in some sensitive fields, or fields that
// expect to receive non-words.
- if (!settingsValues.mCorrectionEnabled) return;
+ if (!settingsValues.mAutoCorrectionEnabled) return;
if (TextUtils.isEmpty(suggestion)) return;
final boolean wasAutoCapitalized =
mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps();
final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
System.currentTimeMillis());
- mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord,
- timeStampInSeconds);
+ mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized,
+ prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
}
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) {
@@ -1240,7 +1191,7 @@ public final class InputLogic {
return;
}
- final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>();
+ final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>();
mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
@Override
@@ -1271,12 +1222,12 @@ public final class InputLogic {
* do nothing.
*
* @param settingsValues the current values of the settings.
- * @param includeResumedWordInSuggestions whether to include the word on which we resume
+ * @param shouldIncludeResumedWordInSuggestions whether to include the word on which we resume
* suggestions in the suggestion list.
*/
// TODO: make this private.
public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues,
- final boolean includeResumedWordInSuggestions) {
+ final boolean shouldIncludeResumedWordInSuggestions) {
// HACK: We may want to special-case some apps that exhibit bad behavior in case of
// recorrection. This is a temporary, stopgap measure that will be removed later.
// TODO: remove this.
@@ -1300,9 +1251,7 @@ public final class InputLogic {
final int expectedCursorPosition = mConnection.getExpectedSelectionStart();
if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)) {
// Show predictions.
- mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime(
- WordComposer.CAPS_MODE_OFF,
- getNthPreviousWordForSuggestion(settingsValues.mSpacingAndPunctuations, 1));
+ mWordComposer.setCapitalizedModeAtStartComposingTime(WordComposer.CAPS_MODE_OFF);
mLatinIME.mHandler.postUpdateSuggestionStrip();
return;
}
@@ -1319,9 +1268,9 @@ public final class InputLogic {
// we just do not resume because it's safer.
final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return;
- final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+ final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
final String typedWord = range.mWord.toString();
- if (includeResumedWordInSuggestions) {
+ if (shouldIncludeResumedWordInSuggestions) {
suggestions.add(new SuggestedWordInfo(typedWord,
SuggestedWords.MAX_SUGGESTIONS + 1,
SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED,
@@ -1347,20 +1296,22 @@ public final class InputLogic {
}
}
final int[] codePoints = StringUtils.toCodePointArray(typedWord);
+ // We want the previous word for suggestion. If we have chars in the word
+ // before the cursor, then we want the word before that, hence 2; otherwise,
+ // we want the word immediately before the cursor, hence 1.
+ final PrevWordsInfo prevWordsInfo = getPrevWordsInfoFromNthPreviousWordForSuggestion(
+ settingsValues.mSpacingAndPunctuations,
+ 0 == numberOfCharsInWordBeforeCursor ? 1 : 2);
mWordComposer.setComposingWord(codePoints,
- mLatinIME.getCoordinatesForCurrentKeyboard(codePoints),
- getNthPreviousWordForSuggestion(settingsValues.mSpacingAndPunctuations,
- // We want the previous word for suggestion. If we have chars in the word
- // before the cursor, then we want the word before that, hence 2; otherwise,
- // we want the word immediately before the cursor, hence 1.
- 0 == numberOfCharsInWordBeforeCursor ? 1 : 2));
+ mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
mWordComposer.setCursorPositionWithinWord(
typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor));
mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor,
expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor());
- if (suggestions.isEmpty()) {
- // We come here if there weren't any suggestion spans on this word. We will try to
- // compute suggestions for it instead.
+ if (suggestions.size() <= (shouldIncludeResumedWordInSuggestions ? 1 : 0)) {
+ // If there weren't any suggestion spans on this word, suggestions#size() will be 1
+ // if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we
+ // have no useful suggestions, so we will try to compute some for it instead.
mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING,
SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() {
@Override
@@ -1368,7 +1319,7 @@ public final class InputLogic {
final SuggestedWords suggestedWordsIncludingTypedWord) {
final SuggestedWords suggestedWords;
if (suggestedWordsIncludingTypedWord.size() > 1
- && !includeResumedWordInSuggestions) {
+ && !shouldIncludeResumedWordInSuggestions) {
// We were able to compute new suggestions for this word.
// Remove the typed word, since we don't want to display it in this
// case. The #getSuggestedWordsExcludingTypedWord() method sets
@@ -1408,7 +1359,7 @@ public final class InputLogic {
* @param inputTransaction The transaction in progress.
*/
private void revertCommit(final InputTransaction inputTransaction) {
- final String previousWord = mLastComposedWord.mPrevWord;
+ final PrevWordsInfo prevWordsInfo = mLastComposedWord.mPrevWordsInfo;
final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord;
final CharSequence committedWord = mLastComposedWord.mCommittedWord;
final String committedWordString = committedWord.toString();
@@ -1430,9 +1381,8 @@ public final class InputLogic {
}
}
mConnection.deleteSurroundingText(deleteLength, 0);
- if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
- mSuggest.mDictionaryFacilitator.cancelAddingUserHistory(
- previousWord, committedWordString);
+ if (!TextUtils.isEmpty(prevWordsInfo.mPrevWord) && !TextUtils.isEmpty(committedWord)) {
+ mDictionaryFacilitator.cancelAddingUserHistory(prevWordsInfo, committedWordString);
}
final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
final SpannableString textToCommit = new SpannableString(stringToCommit);
@@ -1442,7 +1392,7 @@ public final class InputLogic {
committedWord.length(), Object.class);
final int lastCharIndex = textToCommit.length() - 1;
// We will collect all suggestions in the following array.
- final ArrayList<String> suggestions = CollectionUtils.newArrayList();
+ final ArrayList<String> suggestions = new ArrayList<>();
// First, add the committed word to the list of suggestions.
suggestions.add(committedWordString);
for (final Object span : spans) {
@@ -1481,20 +1431,10 @@ public final class InputLogic {
// with the typed word, so we need to resume suggestions right away.
final int[] codePoints = StringUtils.toCodePointArray(stringToCommit);
mWordComposer.setComposingWord(codePoints,
- mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), previousWord);
+ mLatinIME.getCoordinatesForCurrentKeyboard(codePoints));
mConnection.setComposingText(textToCommit, 1);
}
- if (inputTransaction.mSettingsValues.mIsInternal) {
- LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
- Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
- }
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_revertCommit(committedWord.toString(),
- originallyTypedWord.toString(),
- mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString);
- }
- // Don't restart suggestion yet. We'll restart if the user deletes the
- // separator.
+ // Don't restart suggestion yet. We'll restart if the user deletes the separator.
mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
// We have a separator between the word and the cursor: we should show predictions.
inputTransaction.setRequiresUpdateSuggestions();
@@ -1546,7 +1486,7 @@ public final class InputLogic {
}
public int getCurrentRecapitalizeState() {
- if (!mRecapitalizeStatus.isActive()
+ if (!mRecapitalizeStatus.isStarted()
|| !mRecapitalizeStatus.isSetAt(mConnection.getExpectedSelectionStart(),
mConnection.getExpectedSelectionEnd())) {
// Not recapitalizing at the moment
@@ -1563,21 +1503,24 @@ public final class InputLogic {
}
/**
- * Get the nth previous word before the cursor as context for the suggestion process.
+ * Get information fo previous words from the nth previous word before the cursor as context
+ * for the suggestion process.
* @param spacingAndPunctuations the current spacing and punctuations settings.
* @param nthPreviousWord reverse index of the word to get (1-indexed)
- * @return the nth previous word before the cursor.
+ * @return the information of previous words
*/
// TODO: Make this private
- public CharSequence getNthPreviousWordForSuggestion(
+ public PrevWordsInfo getPrevWordsInfoFromNthPreviousWordForSuggestion(
final SpacingAndPunctuations spacingAndPunctuations, final int nthPreviousWord) {
if (spacingAndPunctuations.mCurrentLanguageHasSpaces) {
// If we are typing in a language with spaces we can just look up the previous
- // word from textview.
- return mConnection.getNthPreviousWord(spacingAndPunctuations, nthPreviousWord);
+ // word information from textview.
+ return mConnection.getPrevWordsInfoFromNthPreviousWord(
+ spacingAndPunctuations, nthPreviousWord);
} else {
- return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? null
- : mLastComposedWord.mCommittedWord;
+ return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ?
+ PrevWordsInfo.BEGINNING_OF_SENTENCE :
+ new PrevWordsInfo(mLastComposedWord.mCommittedWord.toString());
}
}
@@ -1755,9 +1698,6 @@ public final class InputLogic {
*/
// TODO: replace these two parameters with an InputTransaction
private void sendKeyCodePoint(final SettingsValues settingsValues, final int codePoint) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_sendKeyCodePoint(codePoint);
- }
// TODO: Remove this special handling of digit letters.
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (codePoint >= '0' && codePoint <= '9') {
@@ -1789,9 +1729,6 @@ public final class InputLogic {
if (settingsValues.shouldInsertSpacesAutomatically()
&& settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces
&& !mConnection.textBeforeCursorLooksLikeURL()) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_promotePhantomSpace();
- }
sendKeyCodePoint(settingsValues, Constants.CODE_SPACE);
}
}
@@ -1833,9 +1770,6 @@ public final class InputLogic {
mConnection.setComposingText(batchInputText, 1);
}
mConnection.endBatchEdit();
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords);
- }
// Space state must be updated before calling updateShiftState
mSpaceState = SpaceState.PHANTOM;
keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(settingsValues),
@@ -1862,9 +1796,6 @@ public final class InputLogic {
if (!mWordComposer.isComposingWord()) return;
final String typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
- }
commitChosenWord(settingsValues, typedWord,
LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString);
}
@@ -1904,15 +1835,6 @@ public final class InputLogic {
throw new RuntimeException("We have an auto-correction but the typed word "
+ "is empty? Impossible! I must commit suicide.");
}
- if (settingsValues.mIsInternal) {
- LatinImeLoggerUtils.onAutoCorrection(
- typedWord, autoCorrection, separator, mWordComposer);
- }
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- final SuggestedWords suggestedWords = mSuggestedWords;
- ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
- separator, mWordComposer.isBatchMode(), suggestedWords);
- }
commitChosenWord(settingsValues, autoCorrection,
LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator);
if (!typedWord.equals(autoCorrection)) {
@@ -1943,33 +1865,19 @@ public final class InputLogic {
final CharSequence chosenWordWithSuggestions =
SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
suggestedWords);
- mConnection.commitText(chosenWordWithSuggestions, 1);
- // TODO: we pass 2 here, but would it be better to move this above and pass 1 instead?
- final String prevWord = mConnection.getNthPreviousWord(
+ // Use the 2nd previous word as the previous word because the 1st previous word is the word
+ // to be committed.
+ final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord(
settingsValues.mSpacingAndPunctuations, 2);
+ mConnection.commitText(chosenWordWithSuggestions, 1);
// Add the word to the user history dictionary
- performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWord);
+ performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo);
// TODO: figure out here if this is an auto-correct or if the best word is actually
// what user typed. Note: currently this is done much later in
// LastComposedWord#didCommitTypedWord by string equality of the remembered
// strings.
mLastComposedWord = mWordComposer.commitWord(commitType,
- chosenWordWithSuggestions, separatorString, prevWord);
- final boolean shouldDiscardPreviousWordForSuggestion;
- if (0 == StringUtils.codePointCount(separatorString)) {
- // Separator is 0-length, we can keep the previous word for suggestion. Either this
- // was a manual pick or the language has no spaces in which case we want to keep the
- // previous word, or it was the keyboard closing or the cursor moving in which case it
- // will be reset anyway.
- shouldDiscardPreviousWordForSuggestion = false;
- } else {
- // Otherwise, we discard if the separator contains any non-whitespace.
- shouldDiscardPreviousWordForSuggestion =
- !StringUtils.containsOnlyWhitespace(separatorString);
- }
- if (shouldDiscardPreviousWordForSuggestion) {
- mWordComposer.discardPreviousWordForSuggestion();
- }
+ chosenWordWithSuggestions, separatorString, prevWordsInfo);
}
/**
@@ -1989,9 +1897,11 @@ public final class InputLogic {
final boolean tryResumeSuggestions, final int remainingTries,
// TODO: remove these arguments
final LatinIME.UIHandler handler) {
+ final boolean shouldFinishComposition = mConnection.hasSelection()
+ || !mConnection.isCursorPositionKnown();
if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess(
mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(),
- false /* shouldFinishComposition */)) {
+ shouldFinishComposition)) {
if (0 < remainingTries) {
handler.postResetCaches(tryResumeSuggestions, remainingTries - 1);
return false;
@@ -2001,7 +1911,9 @@ public final class InputLogic {
}
mConnection.tryFixLyingCursorPosition();
if (tryResumeSuggestions) {
- handler.postResumeSuggestions();
+ // This is triggered when starting input anew, so we want to include the resumed
+ // word in suggestions.
+ handler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */);
}
return true;
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index f25503488..a2ae74b20 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -186,10 +186,17 @@ public final class FormatSpec {
// From version 4 on, we use version * 100 + revision as a version number. That allows
// us to change the format during development while having testing devices remove
// older files with each upgrade, while still having a readable versioning scheme.
+ // When we bump up the dictionary format version, we should update
+ // ExpandableDictionary.needsToMigrateDictionary() and
+ // ExpandableDictionary.matchesExpectedBinaryDictFormatVersionForThisType().
public static final int VERSION2 = 2;
- public static final int VERSION4 = 401;
+ // Dictionary version used for testing.
+ public static final int VERSION4_ONLY_FOR_TESTING = 399;
+ public static final int VERSION401 = 401;
+ public static final int VERSION4 = 402;
+ public static final int VERSION4_DEV = 403;
static final int MINIMUM_SUPPORTED_VERSION = VERSION2;
- static final int MAXIMUM_SUPPORTED_VERSION = VERSION4;
+ static final int MAXIMUM_SUPPORTED_VERSION = VERSION4_DEV;
// TODO: Make this value adaptative to content data, store it in the header, and
// use it in the reading code.
diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
index 853392200..31cb59756 100644
--- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
+++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java
@@ -18,7 +18,6 @@ package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.BinaryDictionary;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.CombinedFormatUtils;
import com.android.inputmethod.latin.utils.StringUtils;
@@ -35,6 +34,8 @@ public final class WordProperty implements Comparable<WordProperty> {
public final ProbabilityInfo mProbabilityInfo;
public final ArrayList<WeightedString> mShortcutTargets;
public final ArrayList<WeightedString> mBigrams;
+ // TODO: Support mIsBeginningOfSentence.
+ public final boolean mIsBeginningOfSentence;
public final boolean mIsNotAWord;
public final boolean mIsBlacklistEntry;
public final boolean mHasShortcuts;
@@ -51,6 +52,7 @@ public final class WordProperty implements Comparable<WordProperty> {
mProbabilityInfo = probabilityInfo;
mShortcutTargets = shortcutTargets;
mBigrams = bigrams;
+ mIsBeginningOfSentence = false;
mIsNotAWord = isNotAWord;
mIsBlacklistEntry = isBlacklistEntry;
mHasBigrams = bigrams != null && !bigrams.isEmpty();
@@ -75,8 +77,9 @@ public final class WordProperty implements Comparable<WordProperty> {
final ArrayList<Integer> shortcutProbabilities) {
mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo);
- mShortcutTargets = CollectionUtils.newArrayList();
- mBigrams = CollectionUtils.newArrayList();
+ mShortcutTargets = new ArrayList<>();
+ mBigrams = new ArrayList<>();
+ mIsBeginningOfSentence = false;
mIsNotAWord = isNotAWord;
mIsBlacklistEntry = isBlacklisted;
mHasShortcuts = hasShortcuts;
diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
index a446672cb..ab3ef964e 100644
--- a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
+++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
@@ -35,7 +35,7 @@ public class AccountUtils {
}
public static List<String> getDeviceAccountsEmailAddresses(final Context context) {
- final ArrayList<String> retval = new ArrayList<String>();
+ final ArrayList<String> retval = new ArrayList<>();
for (final Account account : getAccounts(context)) {
final String name = account.name;
if (Patterns.EMAIL_ADDRESS.matcher(name).matches()) {
@@ -54,7 +54,7 @@ public class AccountUtils {
*/
public static List<String> getDeviceAccountsWithDomain(
final Context context, final String domain) {
- final ArrayList<String> retval = new ArrayList<String>();
+ final ArrayList<String> retval = new ArrayList<>();
final String atDomain = "@" + domain.toLowerCase(Locale.ROOT);
for (final Account account : getAccounts(context)) {
if (account.name.toLowerCase(Locale.ROOT).endsWith(atDomain)) {
diff --git a/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java b/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
new file mode 100644
index 000000000..a96018fe9
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import android.content.Context;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
+import java.io.File;
+import java.util.Locale;
+
+public class ContextualDictionary extends ExpandableBinaryDictionary {
+ /* package */ static final String NAME = ContextualDictionary.class.getSimpleName();
+
+ private ContextualDictionary(final Context context, final Locale locale,
+ final File dictFile) {
+ super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_CONTEXTUAL,
+ dictFile);
+ // Always reset the contents.
+ clear();
+ }
+
+ @UsedForTesting
+ public static ContextualDictionary getDictionary(final Context context, final Locale locale,
+ final File dictFile, final String dictNamePrefix) {
+ return new ContextualDictionary(context, locale, dictFile);
+ }
+
+ @Override
+ protected boolean enableBeginningOfSentencePrediction() {
+ return true;
+ }
+
+ @Override
+ public boolean isValidWord(final String word) {
+ // Strings out of this dictionary should not be considered existing words.
+ return false;
+ }
+
+ @Override
+ protected void loadInitialContentsLocked() {
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 352288f8b..1ba7b366f 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -18,15 +18,11 @@ package com.android.inputmethod.latin.personalization;
import android.content.Context;
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.ExpandableBinaryDictionary;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
-import com.android.inputmethod.latin.utils.LanguageModelParam;
import java.io.File;
-import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
@@ -35,7 +31,6 @@ import java.util.Map;
* model.
*/
public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableBinaryDictionary {
- private static final String TAG = DecayingExpandableBinaryDictionaryBase.class.getSimpleName();
private static final boolean DBG_DUMP_ON_CLOSE = false;
/** Any pair being typed or picked */
@@ -47,8 +42,6 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
/** The locale for this dictionary. */
public final Locale mLocale;
- private Map<String, String> mAdditionalAttributeMap = null;
-
protected DecayingExpandableBinaryDictionaryBase(final Context context,
final String dictName, final Locale locale, final String dictionaryType,
final File dictFile) {
@@ -72,9 +65,6 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
@Override
protected Map<String, String> getHeaderAttributeMap() {
final Map<String, String> attributeMap = super.getHeaderAttributeMap();
- if (mAdditionalAttributeMap != null) {
- attributeMap.putAll(mAdditionalAttributeMap);
- }
attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY,
DictionaryHeader.ATTRIBUTE_VALUE_TRUE);
attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY,
@@ -83,23 +73,17 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB
}
@Override
- protected boolean haveContentsChanged() {
- return false;
- }
-
- @Override
protected void loadInitialContentsLocked() {
// No initial contents.
}
- @UsedForTesting
- public void clearAndFlushDictionaryWithAdditionalAttributes(
- final Map<String, String> attributeMap) {
- mAdditionalAttributeMap = attributeMap;
- clear();
- }
-
/* package */ void runGCIfRequired() {
runGCIfRequired(false /* mindsBlockByGC */);
}
+
+ @Override
+ public boolean isValidWord(final String word) {
+ // Strings out of this dictionary should not be considered existing words.
+ return false;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
index de2744f29..221bb9a8f 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DictionaryDecayBroadcastReciever.java
@@ -61,6 +61,7 @@ public class DictionaryDecayBroadcastReciever extends BroadcastReceiver {
final String action = intent.getAction();
if (action.equals(DICTIONARY_DECAY_INTENT_ACTION)) {
PersonalizationHelper.runGCOnAllOpenedUserHistoryDictionaries();
+ PersonalizationHelper.runGCOnAllOpenedPersonalizationDictionaries();
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
new file mode 100644
index 000000000..9d72de8c5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+public class PersonalizationDataChunk {
+ public final boolean mInputByUser;
+ public final List<String> mTokens;
+ public final int mTimestampInSeconds;
+ public final String mPackageName;
+ public final Locale mlocale = null;
+
+ public PersonalizationDataChunk(boolean inputByUser, final List<String> tokens,
+ final int timestampInSeconds, final String packageName) {
+ mInputByUser = inputByUser;
+ mTokens = Collections.unmodifiableList(tokens);
+ mTimestampInSeconds = timestampInSeconds;
+ mPackageName = packageName;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index 4afd5b4c9..f2ad22ac7 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -18,6 +18,7 @@ package com.android.inputmethod.latin.personalization;
import android.content.Context;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Dictionary;
import java.io.File;
@@ -26,19 +27,15 @@ import java.util.Locale;
public class PersonalizationDictionary extends DecayingExpandableBinaryDictionaryBase {
/* package */ static final String NAME = PersonalizationDictionary.class.getSimpleName();
+ // TODO: Make this constructor private
/* package */ PersonalizationDictionary(final Context context, final Locale locale) {
- this(context, locale, null /* dictFile */);
+ super(context, getDictName(NAME, locale, null /* dictFile */), locale,
+ Dictionary.TYPE_PERSONALIZATION, null /* dictFile */);
}
- public PersonalizationDictionary(final Context context, final Locale locale,
- final File dictFile) {
- super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_PERSONALIZATION,
- dictFile);
- }
-
- @Override
- public boolean isValidWord(final String word) {
- // Strings out of this dictionary should not be considered existing words.
- return false;
+ @UsedForTesting
+ public static PersonalizationDictionary getDictionary(final Context context,
+ final Locale locale, final File dictFile, final String dictNamePrefix) {
+ return PersonalizationHelper.getPersonalizationDictionary(context, locale);
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
index 9bef7a198..450644032 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java
@@ -19,18 +19,15 @@ package com.android.inputmethod.latin.personalization;
import android.content.Context;
import android.content.res.Configuration;
-import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
-import com.android.inputmethod.latin.utils.DistracterFilter;
+import com.android.inputmethod.latin.DictionaryFacilitator;
public class PersonalizationDictionarySessionRegistrar {
public static void init(final Context context,
- final DictionaryFacilitatorForSuggest dictionaryFacilitator,
- final DistracterFilter distracterFilter) {
+ final DictionaryFacilitator dictionaryFacilitator) {
}
public static void onConfigurationChanged(final Context context, final Configuration conf,
- final DictionaryFacilitatorForSuggest dictionaryFacilitator,
- final DistracterFilter distracterFilter) {
+ final DictionaryFacilitator dictionaryFacilitator) {
}
public static void onUpdateData(final Context context, final String type) {
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 7c43182bc..aac40940b 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -16,13 +16,11 @@
package com.android.inputmethod.latin.personalization;
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.utils.CollectionUtils;
-import com.android.inputmethod.latin.utils.FileUtils;
-
import android.content.Context;
import android.util.Log;
+import com.android.inputmethod.latin.utils.FileUtils;
+
import java.io.File;
import java.io.FilenameFilter;
import java.lang.ref.SoftReference;
@@ -34,9 +32,9 @@ public class PersonalizationHelper {
private static final String TAG = PersonalizationHelper.class.getSimpleName();
private static final boolean DEBUG = false;
private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
- sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
+ sLangUserHistoryDictCache = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>>
- sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
+ sLangPersonalizationDictCache = new ConcurrentHashMap<>();
public static UserHistoryDictionary getUserHistoryDictionary(
final Context context, final Locale locale) {
@@ -55,8 +53,7 @@ public class PersonalizationHelper {
}
}
final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale);
- sLangUserHistoryDictCache.put(localeStr,
- new SoftReference<UserHistoryDictionary>(dict));
+ sLangUserHistoryDictCache.put(localeStr, new SoftReference<>(dict));
return dict;
}
}
@@ -66,8 +63,8 @@ public class PersonalizationHelper {
if (TimeUnit.MILLISECONDS.toSeconds(
DictionaryDecayBroadcastReciever.DICTIONARY_DECAY_INTERVAL)
< currentTimestamp - sCurrentTimestampForTesting) {
- // TODO: Run GC for both PersonalizationDictionary and UserHistoryDictionary.
runGCOnAllOpenedUserHistoryDictionaries();
+ runGCOnAllOpenedPersonalizationDictionaries();
}
}
@@ -75,7 +72,6 @@ public class PersonalizationHelper {
runGCOnAllDictionariesIfRequired(sLangUserHistoryDictCache);
}
- @UsedForTesting
public static void runGCOnAllOpenedPersonalizationDictionaries() {
runGCOnAllDictionariesIfRequired(sLangPersonalizationDictCache);
}
@@ -110,8 +106,7 @@ public class PersonalizationHelper {
}
}
final PersonalizationDictionary dict = new PersonalizationDictionary(context, locale);
- sLangPersonalizationDictCache.put(
- localeStr, new SoftReference<PersonalizationDictionary>(dict));
+ sLangPersonalizationDictCache.put(localeStr, new SoftReference<>(dict));
return dict;
}
}
@@ -140,11 +135,13 @@ public class PersonalizationHelper {
}
}
dictionaryMap.clear();
- if (!FileUtils.deleteFilteredFiles(
- context.getFilesDir(), new DictFilter(dictNamePrefix))) {
+ final File filesDir = context.getFilesDir();
+ if (filesDir == null) {
+ Log.e(TAG, "context.getFilesDir() returned null.");
+ }
+ if (!FileUtils.deleteFilteredFiles(filesDir, new DictFilter(dictNamePrefix))) {
Log.e(TAG, "Cannot remove all existing dictionary files. filesDir: "
- + context.getFilesDir().getAbsolutePath() + ", dictNamePrefix: "
- + dictNamePrefix);
+ + filesDir.getAbsolutePath() + ", dictNamePrefix: " + dictNamePrefix);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 8a29c354d..3916fc24c 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -18,9 +18,12 @@ package com.android.inputmethod.latin.personalization;
import android.content.Context;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.utils.DistracterFilter;
import java.io.File;
import java.util.Locale;
@@ -32,46 +35,47 @@ import java.util.Locale;
public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBase {
/* package */ static final String NAME = UserHistoryDictionary.class.getSimpleName();
+ // TODO: Make this constructor private
/* package */ UserHistoryDictionary(final Context context, final Locale locale) {
- this(context, locale, null /* dictFile */);
+ super(context, getDictName(NAME, locale, null /* dictFile */), locale,
+ Dictionary.TYPE_USER_HISTORY, null /* dictFile */);
}
- public UserHistoryDictionary(final Context context, final Locale locale,
- final File dictFile) {
- super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_USER_HISTORY,
- dictFile);
- }
-
- @Override
- public boolean isValidWord(final String word) {
- // Strings out of this dictionary should not be considered existing words.
- return false;
+ @UsedForTesting
+ public static UserHistoryDictionary getDictionary(final Context context, final Locale locale,
+ final File dictFile, final String dictNamePrefix) {
+ return PersonalizationHelper.getUserHistoryDictionary(context, locale);
}
/**
- * Pair will be added to the user history dictionary.
+ * Add a word to the user history dictionary.
*
- * The first word may be null. That means we don't know the context, in other words,
- * it's only a unigram. The first word may also be an empty string : this means start
- * context, as in beginning of a sentence for example.
- * The second word may not be null (a NullPointerException would be thrown).
+ * @param userHistoryDictionary the user history dictionary
+ * @param prevWordsInfo the information of previous words
+ * @param word the word the user inputted
+ * @param isValid whether the word is valid or not
+ * @param timestamp the timestamp when the word has been inputted
+ * @param distracterFilter the filter to check whether the word is a distracter
*/
public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
- final String word0, final String word1, final boolean isValid, final int timestamp) {
- if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
- (word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
+ final PrevWordsInfo prevWordsInfo, final String word, final boolean isValid,
+ final int timestamp, final DistracterFilter distracterFilter) {
+ final String prevWord = prevWordsInfo.mPrevWord;
+ if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
+ (prevWord != null && prevWord.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
return;
}
final int frequency = isValid ?
FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
- userHistoryDictionary.addWordDynamically(word1, frequency, null /* shortcutTarget */,
- 0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */, timestamp);
+ userHistoryDictionary.addUnigramEntryWithCheckingDistracter(word, frequency,
+ null /* shortcutTarget */, 0 /* shortcutFreq */, false /* isNotAWord */,
+ false /* isBlacklisted */, timestamp, distracterFilter);
// Do not insert a word as a bigram of itself
- if (word1.equals(word0)) {
+ if (word.equals(prevWord)) {
return;
}
- if (null != word0) {
- userHistoryDictionary.addBigramDynamically(word0, word1, frequency, timestamp);
+ if (null != prevWord) {
+ userHistoryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
index 39977e76f..31fa86774 100644
--- a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
@@ -47,7 +47,6 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.DialogUtils;
import com.android.inputmethod.latin.utils.IntentUtils;
import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
@@ -101,7 +100,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
super(context, android.R.layout.simple_spinner_item);
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet();
+ final TreeSet<SubtypeLocaleItem> items = new TreeSet<>();
final InputMethodInfo imi = RichInputMethodManager.getInstance()
.getInputMethodInfoOfThisIme();
final int count = imi.getSubtypeCount();
@@ -369,7 +368,6 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
mSubtype = (InputMethodSubtype)source.readParcelable(null);
}
- @SuppressWarnings("hiding")
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
@Override
@@ -516,8 +514,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
localeString, keyboardLayoutSetName);
}
- private AlertDialog createDialog(
- @SuppressWarnings("unused") final SubtypePreference subtypePref) {
+ private AlertDialog createDialog(final SubtypePreference subtypePref) {
final AlertDialog.Builder builder = new AlertDialog.Builder(
DialogUtils.getPlatformDialogThemeContext(getActivity()));
builder.setTitle(R.string.custom_input_styles_title)
@@ -555,7 +552,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment {
private InputMethodSubtype[] getSubtypes() {
final PreferenceGroup group = getPreferenceScreen();
- final ArrayList<InputMethodSubtype> subtypes = CollectionUtils.newArrayList();
+ final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
final int count = group.getPreferenceCount();
for (int i = 0; i < count; i++) {
final Preference pref = group.getPreference(i);
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index c4c1234fc..845ddb377 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -25,11 +25,12 @@ import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryDumpBroadcastReceiver;
-import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.DictionaryFacilitator;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.debug.ExternalDictionaryGetterForDebug;
import com.android.inputmethod.latin.utils.ApplicationUtils;
@@ -40,8 +41,6 @@ public final class DebugSettings extends PreferenceFragment
public static final String PREF_DEBUG_MODE = "debug_mode";
public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch";
- public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
- public static final String PREF_STATISTICS_LOGGING = "enable_logging";
public static final String PREF_KEY_PREVIEW_SHOW_UP_START_SCALE =
"pref_key_preview_show_up_start_scale";
public static final String PREF_KEY_PREVIEW_DISMISS_END_SCALE =
@@ -51,18 +50,14 @@ public final class DebugSettings extends PreferenceFragment
public static final String PREF_KEY_PREVIEW_DISMISS_DURATION =
"pref_key_preview_dismiss_duration";
private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
- private static final String PREF_DUMP_CONTACTS_DICT = "dump_contacts_dict";
- private static final String PREF_DUMP_USER_DICT = "dump_user_dict";
- private static final String PREF_DUMP_USER_HISTORY_DICT = "dump_user_history_dict";
- private static final String PREF_DUMP_PERSONALIZATION_DICT = "dump_personalization_dict";
+ private static final String PREF_KEY_DUMP_DICTS = "pref_key_dump_dictionaries";
+ private static final String PREF_KEY_DUMP_DICT_PREFIX = "pref_key_dump_dictionaries";
+ private static final String DICT_NAME_KEY_FOR_EXTRAS = "dict_name";
public static final String PREF_SLIDING_KEY_INPUT_PREVIEW = "pref_sliding_key_input_preview";
public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout";
- private static final boolean SHOW_STATISTICS_LOGGING = false;
-
private boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
- private CheckBoxPreference mStatisticsLoggingPref;
@Override
public void onCreate(Bundle icicle) {
@@ -71,21 +66,6 @@ public final class DebugSettings extends PreferenceFragment
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
- final Preference usabilityStudyPref = findPreference(PREF_USABILITY_STUDY_MODE);
- if (usabilityStudyPref instanceof CheckBoxPreference) {
- final CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref;
- checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE,
- LatinImeLogger.getUsabilityStudyMode(prefs)));
- checkbox.setSummary(R.string.settings_warning_researcher_mode);
- }
- final Preference statisticsLoggingPref = findPreference(PREF_STATISTICS_LOGGING);
- if (statisticsLoggingPref instanceof CheckBoxPreference) {
- mStatisticsLoggingPref = (CheckBoxPreference) statisticsLoggingPref;
- if (!SHOW_STATISTICS_LOGGING) {
- getPreferenceScreen().removePreference(statisticsLoggingPref);
- }
- }
-
final PreferenceScreen readExternalDictionary =
(PreferenceScreen) findPreference(PREF_READ_EXTERNAL_DICTIONARY);
if (null != readExternalDictionary) {
@@ -101,16 +81,18 @@ public final class DebugSettings extends PreferenceFragment
});
}
+ final PreferenceGroup dictDumpPreferenceGroup =
+ (PreferenceGroup)findPreference(PREF_KEY_DUMP_DICTS);
final OnPreferenceClickListener dictDumpPrefClickListener =
new DictDumpPrefClickListener(this);
- findPreference(PREF_DUMP_CONTACTS_DICT).setOnPreferenceClickListener(
- dictDumpPrefClickListener);
- findPreference(PREF_DUMP_USER_DICT).setOnPreferenceClickListener(
- dictDumpPrefClickListener);
- findPreference(PREF_DUMP_USER_HISTORY_DICT).setOnPreferenceClickListener(
- dictDumpPrefClickListener);
- findPreference(PREF_DUMP_PERSONALIZATION_DICT).setOnPreferenceClickListener(
- dictDumpPrefClickListener);
+ for (final String dictName : DictionaryFacilitator.DICT_TYPE_TO_CLASS.keySet()) {
+ final Preference preference = new Preference(getActivity());
+ preference.setKey(PREF_KEY_DUMP_DICT_PREFIX + dictName);
+ preference.setTitle("Dump " + dictName + " dictionary");
+ preference.setOnPreferenceClickListener(dictDumpPrefClickListener);
+ preference.getExtras().putString(DICT_NAME_KEY_FOR_EXTRAS, dictName);
+ dictDumpPreferenceGroup.addPreference(preference);
+ }
final Resources res = getResources();
setupKeyLongpressTimeoutSettings(prefs, res);
setupKeyPreviewAnimationDuration(prefs, res, PREF_KEY_PREVIEW_SHOW_UP_DURATION,
@@ -138,18 +120,7 @@ public final class DebugSettings extends PreferenceFragment
@Override
public boolean onPreferenceClick(final Preference arg0) {
- final String dictName;
- if (arg0.getKey().equals(PREF_DUMP_CONTACTS_DICT)) {
- dictName = Dictionary.TYPE_CONTACTS;
- } else if (arg0.getKey().equals(PREF_DUMP_USER_DICT)) {
- dictName = Dictionary.TYPE_USER;
- } else if (arg0.getKey().equals(PREF_DUMP_USER_HISTORY_DICT)) {
- dictName = Dictionary.TYPE_USER_HISTORY;
- } else if (arg0.getKey().equals(PREF_DUMP_PERSONALIZATION_DICT)) {
- dictName = Dictionary.TYPE_PERSONALIZATION;
- } else {
- dictName = null;
- }
+ final String dictName = arg0.getExtras().getString(DICT_NAME_KEY_FOR_EXTRAS);
if (dictName != null) {
final Intent intent =
new Intent(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);
@@ -163,27 +134,22 @@ public final class DebugSettings extends PreferenceFragment
@Override
public void onStop() {
super.onStop();
- if (mServiceNeedsRestart) Process.killProcess(Process.myPid());
+ if (mServiceNeedsRestart) {
+ Process.killProcess(Process.myPid());
+ }
}
@Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
- if (key.equals(PREF_DEBUG_MODE)) {
- if (mDebugMode != null) {
- mDebugMode.setChecked(prefs.getBoolean(PREF_DEBUG_MODE, false));
- final boolean checked = mDebugMode.isChecked();
- if (mStatisticsLoggingPref != null) {
- if (checked) {
- getPreferenceScreen().addPreference(mStatisticsLoggingPref);
- } else {
- getPreferenceScreen().removePreference(mStatisticsLoggingPref);
- }
- }
- updateDebugMode();
- mServiceNeedsRestart = true;
- }
- } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)) {
+ if (key.equals(PREF_DEBUG_MODE) && mDebugMode != null) {
+ mDebugMode.setChecked(prefs.getBoolean(PREF_DEBUG_MODE, false));
+ updateDebugMode();
mServiceNeedsRestart = true;
+ return;
+ }
+ if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)) {
+ mServiceNeedsRestart = true;
+ return;
}
}
diff --git a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
index cd726c969..04a2ee3ce 100644
--- a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
+++ b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
@@ -20,7 +20,8 @@ public class NativeSuggestOptions {
// Need to update suggest_options.h when you add, remove or reorder options.
private static final int IS_GESTURE = 0;
private static final int USE_FULL_EDIT_DISTANCE = 1;
- private static final int OPTIONS_SIZE = 2;
+ private static final int BLOCK_OFFENSIVE_WORDS = 2;
+ private static final int OPTIONS_SIZE = 3;
private final int[] mOptions = new int[OPTIONS_SIZE
+ AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
@@ -33,6 +34,10 @@ public class NativeSuggestOptions {
setBooleanOption(USE_FULL_EDIT_DISTANCE, value);
}
+ public void setBlockOffensiveWords(final boolean value) {
+ setBooleanOption(BLOCK_OFFENSIVE_WORDS, value);
+ }
+
public void setAdditionalFeaturesOptions(final int[] additionalOptions) {
if (additionalOptions == null) {
return;
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index a3aae8cb3..235847799 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
+import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
@@ -60,11 +61,15 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
"pref_key_use_double_space_period";
public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE =
"pref_key_block_potentially_offensive";
+ public static final boolean ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS =
+ (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
+ || (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT
+ && Build.VERSION.CODENAME.equals("REL"));
public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY =
"pref_show_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_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
+ public static final String PREF_KEYBOARD_THEME = "pref_keyboard_theme";
public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
// TODO: consolidate key preview dismiss delay with the key preview animation parameters.
public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
@@ -87,6 +92,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_DEBUG_SETTINGS = "debug_settings";
public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal";
+ public static final String PREF_ENABLE_METRICS_LOGGING = "pref_enable_metrics_logging";
+
// This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead.
// This is being used only for the backward compatibility.
private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
@@ -325,10 +332,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
R.array.keypress_vibration_durations, DEFAULT_KEYPRESS_VIBRATION_DURATION));
}
- public static boolean readUsabilityStudyMode(final SharedPreferences prefs) {
- return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true);
- }
-
public static float readKeyPreviewAnimationScale(final SharedPreferences prefs,
final String prefKey, final float defaultValue) {
final float fraction = prefs.getFloat(prefKey, UNDEFINED_PREFERENCE_VALUE_FLOAT);
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 22cbd204c..5eb0377c7 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -37,6 +37,7 @@ import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.dictionarypack.DictionarySettingsActivity;
+import com.android.inputmethod.keyboard.KeyboardTheme;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -58,7 +59,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
private static final boolean DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS = false;
private static final boolean USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS =
DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
- || Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */;
+ || Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2;
private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) {
final Preference preference = findPreference(preferenceKey);
@@ -151,10 +152,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
miscSettings.removePreference(aboutSettings);
}
}
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- // The about screen contains items that may be confusing in development-only versions.
- miscSettings.removePreference(aboutSettings);
- }
final boolean showVoiceKeyOption = res.getBoolean(
R.bool.config_enable_show_voice_key_option);
@@ -168,6 +165,13 @@ public final class SettingsFragment extends InputMethodSettingsFragment
removePreference(Settings.PREF_VIBRATE_ON, generalSettings);
removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings);
}
+ if (!Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS) {
+ removePreference(
+ Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, advancedSettings);
+ removePreference(
+ Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, advancedSettings);
+ }
+
// TODO: consolidate key preview dismiss delay with the key preview animation parameters.
if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
@@ -198,9 +202,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON, advancedSettings);
}
- setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST,
- Settings.readShowsLanguageSwitchKey(prefs));
-
final PreferenceGroup textCorrectionGroup =
(PreferenceGroup) findPreference(Settings.PREF_CORRECTION_SETTINGS);
final PreferenceScreen dictionaryLink =
@@ -212,6 +213,20 @@ public final class SettingsFragment extends InputMethodSettingsFragment
textCorrectionGroup.removePreference(dictionaryLink);
}
+ if (ProductionFlag.IS_METRICS_LOGGING_SUPPORTED) {
+ final Preference enableMetricsLogging =
+ findPreference(Settings.PREF_ENABLE_METRICS_LOGGING);
+ if (enableMetricsLogging != null) {
+ final int applicationLabelRes = context.getApplicationInfo().labelRes;
+ final String applicationName = res.getString(applicationLabelRes);
+ final String enableMetricsLoggingTitle = res.getString(
+ R.string.enable_metrics_logging, applicationName);
+ enableMetricsLogging.setTitle(enableMetricsLoggingTitle);
+ }
+ } else {
+ removePreference(Settings.PREF_ENABLE_METRICS_LOGGING, textCorrectionGroup);
+ }
+
final Preference editPersonalDictionary =
findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY);
final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent();
@@ -253,11 +268,31 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
- updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
+ final ListPreference keyboardThemePref = (ListPreference)findPreference(
+ Settings.PREF_KEYBOARD_THEME);
+ if (keyboardThemePref != null) {
+ final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
+ final String value = Integer.toString(keyboardTheme.mThemeId);
+ final CharSequence entries[] = keyboardThemePref.getEntries();
+ final int entryIndex = keyboardThemePref.findIndexOfValue(value);
+ keyboardThemePref.setSummary(entryIndex < 0 ? null : entries[entryIndex]);
+ keyboardThemePref.setValue(value);
+ }
updateCustomInputStylesSummary(prefs, res);
}
@Override
+ public void onPause() {
+ super.onPause();
+ final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+ final ListPreference keyboardThemePref = (ListPreference)findPreference(
+ Settings.PREF_KEYBOARD_THEME);
+ if (keyboardThemePref != null) {
+ KeyboardTheme.saveKeyboardThemeId(keyboardThemePref.getValue(), prefs);
+ }
+ }
+
+ @Override
public void onDestroy() {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
this);
@@ -278,16 +313,13 @@ public final class SettingsFragment extends InputMethodSettingsFragment
if (key.equals(Settings.PREF_POPUP_ON)) {
setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
Settings.readKeyPreviewPopupEnabled(prefs, res));
- } else if (key.equals(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY)) {
- setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST,
- Settings.readShowsLanguageSwitchKey(prefs));
} else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
}
ensureConsistencyOfAutoCorrectionSettings();
updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
- updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_THEME);
refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index dde50ccaf..44104019b 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -28,6 +28,7 @@ import com.android.inputmethod.compat.AppWorkaroundsUtils;
import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.utils.AsyncResultHolder;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
@@ -73,6 +74,7 @@ public final class SettingsValues {
public final boolean mPhraseGestureEnabled;
public final int mKeyLongpressTimeout;
public final Locale mLocale;
+ public final boolean mEnableMetricsLogging;
// From the input box
public final InputAttributes mInputAttributes;
@@ -83,7 +85,8 @@ public final class SettingsValues {
public final int mKeyPreviewPopupDismissDelay;
private final boolean mAutoCorrectEnabled;
public final float mAutoCorrectionThreshold;
- public final boolean mCorrectionEnabled;
+ // TODO: Rename this to mAutoCorrectionEnabledPerUserSettings.
+ public final boolean mAutoCorrectionEnabled;
public final int mSuggestionVisibility;
public final int mDisplayOrientation;
private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds;
@@ -108,7 +111,8 @@ public final class SettingsValues {
// Store the input attributes
if (null == inputAttributes) {
- mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */);
+ mInputAttributes = new InputAttributes(
+ null, false /* isFullscreenMode */, context.getPackageName());
} else {
mInputAttributes = inputAttributes;
}
@@ -120,13 +124,18 @@ public final class SettingsValues {
mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res);
mSlidingKeyInputPreviewEnabled = prefs.getBoolean(
DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
- mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res);
+ mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res)
+ && !mInputAttributes.mIsPasswordField
+ && !mInputAttributes.hasNoMicrophoneKeyOption()
+ && SubtypeSwitcher.getInstance().isShortcutImeEnabled();
final String autoCorrectionThresholdRawValue = prefs.getString(
Settings.PREF_AUTO_CORRECTION_THRESHOLD,
res.getString(R.string.auto_correction_threshold_mode_index_modest));
- mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean(
- Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
- mShowsLanguageSwitchKey = Settings.readShowsLanguageSwitchKey(prefs);
+ mIncludesOtherImesInLanguageSwitchList = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS
+ ? prefs.getBoolean(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false)
+ : true /* forcibly */;
+ mShowsLanguageSwitchKey = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS
+ ? Settings.readShowsLanguageSwitchKey(prefs) : true /* forcibly */;
mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
mUsePersonalizedDicts = prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true);
mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true);
@@ -134,7 +143,7 @@ public final class SettingsValues {
mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(autoCorrectionThresholdRawValue, res);
mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res);
mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout);
-
+ mEnableMetricsLogging = prefs.getBoolean(Settings.PREF_ENABLE_METRICS_LOGGING, true);
// Compute other readable settings
mKeyLongpressTimeout = Settings.readKeyLongpressTimeout(prefs, res);
mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res);
@@ -147,7 +156,7 @@ public final class SettingsValues {
mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res);
- mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
+ mAutoCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
final String showSuggestionsSetting = prefs.getString(
Settings.PREF_SHOW_SUGGESTIONS_SETTING,
res.getString(R.string.prefs_suggestion_visibility_default_value));
@@ -170,7 +179,7 @@ public final class SettingsValues {
ResourceUtils.getFloatFromFraction(
res, R.fraction.config_key_preview_dismiss_end_scale));
mDisplayOrientation = res.getConfiguration().orientation;
- mAppWorkarounds = new AsyncResultHolder<AppWorkaroundsUtils>();
+ mAppWorkarounds = new AsyncResultHolder<>();
final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo(
mInputAttributes.mTargetApplicationPackageName);
if (null != packageInfo) {
@@ -185,12 +194,14 @@ public final class SettingsValues {
return mInputAttributes.mApplicationSpecifiedCompletionOn;
}
+ // TODO: Rename this to needsToLookupSuggestions().
public boolean isSuggestionsRequested() {
- return mInputAttributes.mIsSettingsSuggestionStripOn
- && (mCorrectionEnabled || isSuggestionStripVisible());
+ return mInputAttributes.mShouldShowSuggestions
+ && (mAutoCorrectionEnabled
+ || isCurrentOrientationAllowingSuggestionsPerUserSettings());
}
- public boolean isSuggestionStripVisible() {
+ public boolean isCurrentOrientationAllowingSuggestionsPerUserSettings() {
return (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_VALUE)
|| (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE
&& mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT);
@@ -205,7 +216,8 @@ public final class SettingsValues {
}
public boolean isWordCodePoint(final int code) {
- return Character.isLetter(code) || isWordConnector(code);
+ return Character.isLetter(code) || isWordConnector(code)
+ || Character.COMBINING_SPACING_MARK == Character.getType(code);
}
public boolean isUsuallyPrecededBySpace(final int code) {
@@ -313,18 +325,18 @@ public final class SettingsValues {
private static boolean needsToShowVoiceInputKey(final SharedPreferences prefs,
final Resources res) {
- if (!prefs.contains(Settings.PREF_VOICE_INPUT_KEY)) {
- // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to
- // {@link Settings#PREF_VOICE_INPUT_KEY}.
+ // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to
+ // {@link Settings#PREF_VOICE_INPUT_KEY}.
+ if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) {
final String voiceModeMain = res.getString(R.string.voice_mode_main);
final String voiceMode = prefs.getString(
Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain);
final boolean shouldShowVoiceInputKey = voiceModeMain.equals(voiceMode);
- prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey).apply();
- }
- // Remove the obsolete preference if exists.
- if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) {
- prefs.edit().remove(Settings.PREF_VOICE_MODE_OBSOLETE).apply();
+ prefs.edit()
+ .putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey)
+ // Remove the obsolete preference if exists.
+ .remove(Settings.PREF_VOICE_MODE_OBSOLETE)
+ .apply();
}
return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true);
}
@@ -385,8 +397,8 @@ public final class SettingsValues {
sb.append("" + mAutoCorrectEnabled);
sb.append("\n mAutoCorrectionThreshold = ");
sb.append("" + mAutoCorrectionThreshold);
- sb.append("\n mCorrectionEnabled = ");
- sb.append("" + mCorrectionEnabled);
+ sb.append("\n mAutoCorrectionEnabled = ");
+ sb.append("" + mAutoCorrectionEnabled);
sb.append("\n mSuggestionVisibility = ");
sb.append("" + mSuggestionVisibility);
sb.append("\n mDisplayOrientation = ");
diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
index 796921f71..b8d2a2248 100644
--- a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
+++ b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java
@@ -30,6 +30,7 @@ import java.util.Locale;
public final class SpacingAndPunctuations {
private final int[] mSortedSymbolsPrecededBySpace;
private final int[] mSortedSymbolsFollowedBySpace;
+ private final int[] mSortedSymbolsClusteringTogether;
private final int[] mSortedWordConnectors;
public final int[] mSortedWordSeparators;
public final PunctuationSuggestions mSuggestPuncList;
@@ -46,6 +47,8 @@ public final class SpacingAndPunctuations {
// To be able to binary search the code point. See {@link #isUsuallyFollowedBySpace(int)}.
mSortedSymbolsFollowedBySpace = StringUtils.toSortedCodePointArray(
res.getString(R.string.symbols_followed_by_space));
+ mSortedSymbolsClusteringTogether = StringUtils.toSortedCodePointArray(
+ res.getString(R.string.symbols_clustering_together));
// To be able to binary search the code point. See {@link #isWordConnector(int)}.
mSortedWordConnectors = StringUtils.toSortedCodePointArray(
res.getString(R.string.symbols_word_connectors));
@@ -85,6 +88,10 @@ public final class SpacingAndPunctuations {
return Arrays.binarySearch(mSortedSymbolsFollowedBySpace, code) >= 0;
}
+ public boolean isClusteringSymbol(final int code) {
+ return Arrays.binarySearch(mSortedSymbolsClusteringTogether, code) >= 0;
+ }
+
public boolean isSentenceSeparator(final int code) {
return code == mSentenceSeparator;
}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java
index 974dfddd3..73d25f6aa 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java
@@ -21,13 +21,13 @@ import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
+import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
-import com.android.inputmethod.compat.ViewCompatUtils;
import com.android.inputmethod.latin.R;
public final class SetupStartIndicatorView extends LinearLayout {
@@ -96,13 +96,13 @@ public final class SetupStartIndicatorView extends LinearLayout {
@Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
- final int layoutDirection = ViewCompatUtils.getLayoutDirection(this);
+ final int layoutDirection = ViewCompat.getLayoutDirection(this);
final int width = getWidth();
final int height = getHeight();
final float halfHeight = height / 2.0f;
final Path path = mIndicatorPath;
path.rewind();
- if (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) {
+ if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
// Left arrow
path.moveTo(width, 0.0f);
path.lineTo(0.0f, halfHeight);
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
index c909507c6..6734e61b8 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
@@ -20,10 +20,10 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
+import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.View;
-import com.android.inputmethod.compat.ViewCompatUtils;
import com.android.inputmethod.latin.R;
public final class SetupStepIndicatorView extends View {
@@ -38,12 +38,12 @@ public final class SetupStepIndicatorView extends View {
}
public void setIndicatorPosition(final int stepPos, final int totalStepNum) {
- final int layoutDirection = ViewCompatUtils.getLayoutDirection(this);
+ final int layoutDirection = ViewCompat.getLayoutDirection(this);
// The indicator position is the center of the partition that is equally divided into
// the total step number.
final float partionWidth = 1.0f / totalStepNum;
final float pos = stepPos * partionWidth + partionWidth / 2.0f;
- mXRatio = (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos;
+ mXRatio = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos;
invalidate();
}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
index 5072fabd6..bcac05a6a 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
@@ -37,7 +37,6 @@ import com.android.inputmethod.compat.TextViewCompatUtils;
import com.android.inputmethod.compat.ViewCompatUtils;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.settings.SettingsActivity;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import java.util.ArrayList;
@@ -482,7 +481,7 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
static final class SetupStepGroup {
private final SetupStepIndicatorView mIndicatorView;
- private final ArrayList<SetupStep> mGroup = CollectionUtils.newArrayList();
+ private final ArrayList<SetupStep> mGroup = new ArrayList<>();
public SetupStepGroup(final SetupStepIndicatorView indicatorView) {
mIndicatorView = indicatorView;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 65ebcf5f1..8d495646d 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -27,7 +27,6 @@ import android.view.inputmethod.InputMethodSubtype;
import android.view.textservice.SuggestionsInfo;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.latin.BinaryDictionary;
import com.android.inputmethod.latin.ContactsBinaryDictionary;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryCollection;
@@ -77,7 +76,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
private final Object mUseContactsLock = new Object();
private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList =
- CollectionUtils.newHashSet();
+ new HashSet<>();
public static final int SCRIPT_LATIN = 0;
public static final int SCRIPT_CYRILLIC = 1;
@@ -94,7 +93,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
// proximity to pass to the dictionary descent algorithm.
// IMPORTANT: this only contains languages - do not write countries in there.
// Only the language is searched from the map.
- mLanguageToScript = CollectionUtils.newTreeMap();
+ mLanguageToScript = new TreeMap<>();
mLanguageToScript.put("cs", SCRIPT_LATIN);
mLanguageToScript.put("da", SCRIPT_LATIN);
mLanguageToScript.put("de", SCRIPT_LATIN);
@@ -255,7 +254,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
mOriginalText = originalText;
mRecommendedThreshold = recommendedThreshold;
mMaxLength = maxLength;
- mSuggestions = CollectionUtils.newArrayList(maxLength + 1);
+ mSuggestions = new ArrayList<>(maxLength + 1);
mScores = new int[mMaxLength];
}
@@ -441,8 +440,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
}
}
dictionaryCollection.addDictionary(mContactsDictionary);
- mDictionaryCollectionsList.add(
- new WeakReference<DictionaryCollection>(dictionaryCollection));
+ mDictionaryCollectionsList.add(new WeakReference<>(dictionaryCollection));
}
return new DictAndKeyboard(dictionaryCollection, keyboardLayoutSet);
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index ddda52d71..55274cfe2 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.latin.spellcheck;
+import android.content.res.Resources;
import android.os.Binder;
import android.text.TextUtils;
import android.util.Log;
@@ -23,17 +24,21 @@ import android.view.textservice.SentenceSuggestionsInfo;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
-import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.PrevWordsInfo;
import java.util.ArrayList;
+import java.util.Locale;
public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName();
private static final boolean DBG = false;
private final static String[] EMPTY_STRING_ARRAY = new String[0];
+ private final Resources mResources;
+ private SentenceLevelAdapter mSentenceLevelAdapter;
public AndroidSpellCheckerSession(AndroidSpellCheckerService service) {
super(service);
+ mResources = service.getResources();
}
private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(TextInfo ti,
@@ -43,10 +48,9 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
return null;
}
final int N = ssi.getSuggestionsCount();
- final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList();
- final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList();
- final ArrayList<SuggestionsInfo> additionalSuggestionsInfos =
- CollectionUtils.newArrayList();
+ final ArrayList<Integer> additionalOffsets = new ArrayList<>();
+ final ArrayList<Integer> additionalLengths = new ArrayList<>();
+ final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = new ArrayList<>();
String currentWord = null;
for (int i = 0; i < N; ++i) {
final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i);
@@ -57,7 +61,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
final int offset = ssi.getOffsetAt(i);
final int length = ssi.getLengthAt(i);
final String subText = typedText.substring(offset, offset + length);
- final String prevWord = currentWord;
+ final PrevWordsInfo prevWordsInfo = new PrevWordsInfo(currentWord);
currentWord = subText;
if (!subText.contains(AndroidSpellCheckerService.SINGLE_QUOTE)) {
continue;
@@ -73,7 +77,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
if (TextUtils.isEmpty(splitText)) {
continue;
}
- if (mSuggestionsCache.getSuggestionsFromCache(splitText, prevWord) == null) {
+ if (mSuggestionsCache.getSuggestionsFromCache(splitText, prevWordsInfo) == null) {
continue;
}
final int newLength = splitText.length();
@@ -116,8 +120,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
@Override
public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos,
int suggestionsLimit) {
- final SentenceSuggestionsInfo[] retval =
- super.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit);
+ final SentenceSuggestionsInfo[] retval = splitAndSuggest(textInfos, suggestionsLimit);
if (retval == null || retval.length != textInfos.length) {
return retval;
}
@@ -131,6 +134,58 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
return retval;
}
+ /**
+ * Get sentence suggestions for specified texts in an array of TextInfo. This is taken from
+ * SpellCheckerService#onGetSentenceSuggestionsMultiple that we can't use because it's
+ * using private variables.
+ * The default implementation splits the input text to words and returns
+ * {@link SentenceSuggestionsInfo} which contains suggestions for each word.
+ * This function will run on the incoming IPC thread.
+ * So, this is not called on the main thread,
+ * but will be called in series on another thread.
+ * @param textInfos an array of the text metadata
+ * @param suggestionsLimit the maximum number of suggestions to be returned
+ * @return an array of {@link SentenceSuggestionsInfo} returned by
+ * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
+ */
+ private SentenceSuggestionsInfo[] splitAndSuggest(TextInfo[] textInfos, int suggestionsLimit) {
+ if (textInfos == null || textInfos.length == 0) {
+ return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
+ }
+ SentenceLevelAdapter sentenceLevelAdapter;
+ synchronized(this) {
+ sentenceLevelAdapter = mSentenceLevelAdapter;
+ if (sentenceLevelAdapter == null) {
+ final String localeStr = getLocale();
+ if (!TextUtils.isEmpty(localeStr)) {
+ sentenceLevelAdapter = new SentenceLevelAdapter(mResources,
+ new Locale(localeStr));
+ mSentenceLevelAdapter = sentenceLevelAdapter;
+ }
+ }
+ }
+ if (sentenceLevelAdapter == null) {
+ return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
+ }
+ final int infosSize = textInfos.length;
+ final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[infosSize];
+ for (int i = 0; i < infosSize; ++i) {
+ final SentenceLevelAdapter.SentenceTextInfoParams textInfoParams =
+ sentenceLevelAdapter.getSplitWords(textInfos[i]);
+ final ArrayList<SentenceLevelAdapter.SentenceWordItem> mItems =
+ textInfoParams.mItems;
+ final int itemsSize = mItems.size();
+ final TextInfo[] splitTextInfos = new TextInfo[itemsSize];
+ for (int j = 0; j < itemsSize; ++j) {
+ splitTextInfos[j] = mItems.get(j).mTextInfo;
+ }
+ retval[i] = SentenceLevelAdapter.reconstructSuggestions(
+ textInfoParams, onGetSuggestionsMultiple(
+ splitTextInfos, suggestionsLimit, true));
+ }
+ return retval;
+ }
+
@Override
public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos,
int suggestionsLimit, boolean sequentialWords) {
@@ -148,7 +203,8 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck
} else {
prevWord = null;
}
- retval[i] = onGetSuggestionsInternal(textInfos[i], prevWord, suggestionsLimit);
+ final PrevWordsInfo prevWordsInfo = new PrevWordsInfo(prevWord);
+ retval[i] = onGetSuggestionsInternal(textInfos[i], prevWordsInfo, suggestionsLimit);
retval[i].setCookieAndSequence(textInfos[i].getCookie(),
textInfos[i].getSequence());
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 69d092751..54eebe399 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -28,9 +28,9 @@ import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
-import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer;
@@ -68,29 +68,29 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
private static final char CHAR_DELIMITER = '\uFFFC';
private static final int MAX_CACHE_SIZE = 50;
private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache =
- new LruCache<String, SuggestionsParams>(MAX_CACHE_SIZE);
+ new LruCache<>(MAX_CACHE_SIZE);
// TODO: Support n-gram input
- private static String generateKey(String query, String prevWord) {
- if (TextUtils.isEmpty(query) || TextUtils.isEmpty(prevWord)) {
+ private static String generateKey(final String query, final PrevWordsInfo prevWordsInfo) {
+ if (TextUtils.isEmpty(query) || TextUtils.isEmpty(prevWordsInfo.mPrevWord)) {
return query;
}
- return query + CHAR_DELIMITER + prevWord;
+ return query + CHAR_DELIMITER + prevWordsInfo.mPrevWord;
}
- // TODO: Support n-gram input
- public SuggestionsParams getSuggestionsFromCache(String query, String prevWord) {
- return mUnigramSuggestionsInfoCache.get(generateKey(query, prevWord));
+ public SuggestionsParams getSuggestionsFromCache(String query,
+ final PrevWordsInfo prevWordsInfo) {
+ return mUnigramSuggestionsInfoCache.get(generateKey(query, prevWordsInfo));
}
- // TODO: Support n-gram input
public void putSuggestionsToCache(
- String query, String prevWord, String[] suggestions, int flags) {
+ final String query, final PrevWordsInfo prevWordsInfo,
+ final String[] suggestions, final int flags) {
if (suggestions == null || TextUtils.isEmpty(query)) {
return;
}
mUnigramSuggestionsInfoCache.put(
- generateKey(query, prevWord), new SuggestionsParams(suggestions, flags));
+ generateKey(query, prevWordsInfo), new SuggestionsParams(suggestions, flags));
}
public void clearCache() {
@@ -259,11 +259,12 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
}
protected SuggestionsInfo onGetSuggestionsInternal(
- final TextInfo textInfo, final String prevWord, final int suggestionsLimit) {
+ final TextInfo textInfo, final PrevWordsInfo prevWordsInfo,
+ final int suggestionsLimit) {
try {
final String inText = textInfo.getText();
final SuggestionsParams cachedSuggestionsParams =
- mSuggestionsCache.getSuggestionsFromCache(inText, prevWord);
+ mSuggestionsCache.getSuggestionsFromCache(inText, prevWordsInfo);
if (cachedSuggestionsParams != null) {
if (DBG) {
Log.d(TAG, "Cache hit: " + inText + ", " + cachedSuggestionsParams.mFlags);
@@ -281,6 +282,22 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
return AndroidSpellCheckerService.getNotInDictEmptySuggestions(
false /* reportAsTypo */);
}
+ if (CHECKABILITY_CONTAINS_PERIOD == checkability) {
+ final String[] splitText = inText.split(Constants.REGEXP_PERIOD);
+ boolean allWordsAreValid = true;
+ for (final String word : splitText) {
+ if (!dictInfo.mDictionary.isValidWord(word)) {
+ allWordsAreValid = false;
+ break;
+ }
+ }
+ if (allWordsAreValid) {
+ return new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
+ | SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS,
+ new String[] {
+ TextUtils.join(Constants.STRING_SPACE, splitText) });
+ }
+ }
return dictInfo.mDictionary.isValidWord(inText)
? AndroidSpellCheckerService.getInDictEmptySuggestions()
: AndroidSpellCheckerService.getNotInDictEmptySuggestions(
@@ -322,12 +339,12 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
} else {
coordinates = dictInfo.mKeyboard.getCoordinates(codePoints);
}
- composer.setComposingWord(codePoints, coordinates, null /* previousWord */);
+ composer.setComposingWord(codePoints, coordinates);
// TODO: make a spell checker option to block offensive words or not
final ArrayList<SuggestedWordInfo> suggestions =
- dictInfo.mDictionary.getSuggestions(composer, prevWord,
+ dictInfo.mDictionary.getSuggestions(composer, prevWordsInfo,
dictInfo.getProximityInfo(), true /* blockOffensiveWords */,
- null /* additionalFeaturesOptions */,
+ null /* additionalFeaturesOptions */, 0 /* sessionId */,
null /* inOutLanguageWeight */);
if (suggestions != null) {
for (final SuggestedWordInfo suggestion : suggestions) {
@@ -369,7 +386,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
.getValueOf_RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS()
: 0);
final SuggestionsInfo retval = new SuggestionsInfo(flags, result.mSuggestions);
- mSuggestionsCache.putSuggestionsToCache(text, prevWord, result.mSuggestions, flags);
+ mSuggestionsCache.putSuggestionsToCache(text, prevWordsInfo, result.mSuggestions,
+ flags);
return retval;
} 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/DictAndKeyboard.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
index 1ffe50681..b33739fc1 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java
@@ -16,11 +16,11 @@
package com.android.inputmethod.latin.spellcheck;
-import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardLayoutSet;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.Dictionary;
/**
* A container for a Dictionary and a Keyboard.
@@ -28,19 +28,15 @@ import com.android.inputmethod.keyboard.ProximityInfo;
public final class DictAndKeyboard {
public final Dictionary mDictionary;
public final Keyboard mKeyboard;
- private final Keyboard mManualShiftedKeyboard;
public DictAndKeyboard(
final Dictionary dictionary, final KeyboardLayoutSet keyboardLayoutSet) {
mDictionary = dictionary;
if (keyboardLayoutSet == null) {
mKeyboard = null;
- mManualShiftedKeyboard = null;
return;
}
mKeyboard = keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
- mManualShiftedKeyboard =
- keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED);
}
public ProximityInfo getProximityInfo() {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index c99264347..cc52a3e0f 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -20,9 +20,9 @@ import android.util.Log;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
import java.util.Locale;
@@ -46,19 +46,19 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> {
private final Locale mLocale;
private int mSize;
private volatile boolean mClosed;
- final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList();
+ final static ArrayList<SuggestedWordInfo> noSuggestions = new ArrayList<>();
private final static DictAndKeyboard dummyDict = new DictAndKeyboard(
new Dictionary(Dictionary.TYPE_MAIN) {
// TODO: this dummy dictionary should be a singleton in the Dictionary class.
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
- final String prevWord, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final float[] inOutLanguageWeight) {
+ final int sessionId, final float[] inOutLanguageWeight) {
return noSuggestions;
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
// This is never called. However if for some strange reason it ever gets
// called, returning true is less destructive (it will not underline the
// word in red).
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
new file mode 100644
index 000000000..13352f39e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.spellcheck;
+
+import android.content.res.Resources;
+import android.view.textservice.SentenceSuggestionsInfo;
+import android.view.textservice.SuggestionsInfo;
+import android.view.textservice.TextInfo;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.RunInLocale;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+/**
+ * This code is mostly lifted directly from android.service.textservice.SpellCheckerService in
+ * the framework; maybe that should be protected instead, so that implementers don't have to
+ * rewrite everything for any small change.
+ */
+public class SentenceLevelAdapter {
+ public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS =
+ new SentenceSuggestionsInfo[] {};
+ private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null);
+ /**
+ * Container for split TextInfo parameters
+ */
+ public static class SentenceWordItem {
+ public final TextInfo mTextInfo;
+ public final int mStart;
+ public final int mLength;
+ public SentenceWordItem(TextInfo ti, int start, int end) {
+ mTextInfo = ti;
+ mStart = start;
+ mLength = end - start;
+ }
+ }
+
+ /**
+ * Container for originally queried TextInfo and parameters
+ */
+ public static class SentenceTextInfoParams {
+ final TextInfo mOriginalTextInfo;
+ final ArrayList<SentenceWordItem> mItems;
+ final int mSize;
+ public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) {
+ mOriginalTextInfo = ti;
+ mItems = items;
+ mSize = items.size();
+ }
+ }
+
+ private static class WordIterator {
+ private final SpacingAndPunctuations mSpacingAndPunctuations;
+ public WordIterator(final Resources res, final Locale locale) {
+ final RunInLocale<SpacingAndPunctuations> job
+ = new RunInLocale<SpacingAndPunctuations>() {
+ @Override
+ protected SpacingAndPunctuations job(final Resources res) {
+ return new SpacingAndPunctuations(res);
+ }
+ };
+ mSpacingAndPunctuations = job.runInLocale(res, locale);
+ }
+
+ public int getEndOfWord(final CharSequence sequence, int index) {
+ final int length = sequence.length();
+ index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1);
+ while (index < length) {
+ final int codePoint = Character.codePointAt(sequence, index);
+ if (mSpacingAndPunctuations.isWordSeparator(codePoint)) {
+ // If it's a period, we want to stop here only if it's followed by another
+ // word separator. In all other cases we stop here.
+ if (Constants.CODE_PERIOD == codePoint) {
+ final int indexOfNextCodePoint =
+ index + Character.charCount(Constants.CODE_PERIOD);
+ if (indexOfNextCodePoint < length
+ && mSpacingAndPunctuations.isWordSeparator(
+ Character.codePointAt(sequence, indexOfNextCodePoint))) {
+ return index;
+ }
+ } else {
+ return index;
+ }
+ }
+ index += Character.charCount(codePoint);
+ }
+ return index;
+ }
+
+ public int getBeginningOfNextWord(final CharSequence sequence, int index) {
+ final int length = sequence.length();
+ if (index >= length) {
+ return -1;
+ }
+ index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1);
+ while (index < length) {
+ final int codePoint = Character.codePointAt(sequence, index);
+ if (!mSpacingAndPunctuations.isWordSeparator(codePoint)) {
+ return index;
+ }
+ index += Character.charCount(codePoint);
+ }
+ return -1;
+ }
+ }
+
+ private final WordIterator mWordIterator;
+ public SentenceLevelAdapter(final Resources res, final Locale locale) {
+ mWordIterator = new WordIterator(res, locale);
+ }
+
+ public SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) {
+ final WordIterator wordIterator = mWordIterator;
+ final CharSequence originalText = originalTextInfo.getText();
+ final int cookie = originalTextInfo.getCookie();
+ final int start = -1;
+ final int end = originalText.length();
+ final ArrayList<SentenceWordItem> wordItems = new ArrayList<SentenceWordItem>();
+ int wordStart = wordIterator.getBeginningOfNextWord(originalText, start);
+ int wordEnd = wordIterator.getEndOfWord(originalText, wordStart);
+ while (wordStart <= end && wordEnd != -1 && wordStart != -1) {
+ if (wordEnd >= start && wordEnd > wordStart) {
+ final String query = originalText.subSequence(wordStart, wordEnd).toString();
+ final TextInfo ti = new TextInfo(query, cookie, query.hashCode());
+ wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd));
+ }
+ wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd);
+ if (wordStart == -1) {
+ break;
+ }
+ wordEnd = wordIterator.getEndOfWord(originalText, wordStart);
+ }
+ return new SentenceTextInfoParams(originalTextInfo, wordItems);
+ }
+
+ public static SentenceSuggestionsInfo reconstructSuggestions(
+ SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) {
+ if (results == null || results.length == 0) {
+ return null;
+ }
+ if (originalTextInfoParams == null) {
+ return null;
+ }
+ final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie();
+ final int originalSequence =
+ originalTextInfoParams.mOriginalTextInfo.getSequence();
+
+ final int querySize = originalTextInfoParams.mSize;
+ final int[] offsets = new int[querySize];
+ final int[] lengths = new int[querySize];
+ final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize];
+ for (int i = 0; i < querySize; ++i) {
+ final SentenceWordItem item = originalTextInfoParams.mItems.get(i);
+ SuggestionsInfo result = null;
+ for (int j = 0; j < results.length; ++j) {
+ final SuggestionsInfo cur = results[j];
+ if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) {
+ result = cur;
+ result.setCookieAndSequence(originalCookie, originalSequence);
+ break;
+ }
+ }
+ offsets[i] = item.mStart;
+ lengths[i] = item.mLength;
+ reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO;
+ }
+ return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java
index a694bf47d..a6437bac3 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java
@@ -20,6 +20,7 @@ import android.content.Context;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.ContactsBinaryDictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
@@ -36,19 +37,19 @@ public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsB
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
- final String prevWordForBigrams, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final float[] inOutLanguageWeight) {
+ final int sessionId, final float[] inOutLanguageWeight) {
synchronized (mLock) {
- return super.getSuggestions(codes, prevWordForBigrams, proximityInfo,
- blockOffensiveWords, additionalFeaturesOptions, inOutLanguageWeight);
+ return super.getSuggestions(codes, prevWordsInfo, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions, sessionId, inOutLanguageWeight);
}
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
synchronized (mLock) {
- return super.isValidWord(word);
+ return super.isInDictionary(word);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java
index 1a6dd5818..8c9d5d681 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java
@@ -19,6 +19,7 @@ package com.android.inputmethod.latin.spellcheck;
import android.content.Context;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.UserBinaryDictionary;
import com.android.inputmethod.latin.WordComposer;
@@ -41,19 +42,19 @@ public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDic
@Override
public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes,
- final String prevWordForBigrams, final ProximityInfo proximityInfo,
+ final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
- final float[] inOutLanguageWeight) {
+ final int sessionId, final float[] inOutLanguageWeight) {
synchronized (mLock) {
- return super.getSuggestions(codes, prevWordForBigrams, proximityInfo,
- blockOffensiveWords, additionalFeaturesOptions, inOutLanguageWeight);
+ return super.getSuggestions(codes, prevWordsInfo, proximityInfo,
+ blockOffensiveWords, additionalFeaturesOptions, sessionId, inOutLanguageWeight);
}
}
@Override
- public boolean isValidWord(final String word) {
+ public boolean isInDictionary(final String word) {
synchronized (mLock) {
- return super.isValidWord(word);
+ return super.isInDictionary(word);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 5a325ea82..346aea34a 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -23,24 +23,17 @@ import android.graphics.drawable.Drawable;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.utils.TypefaceUtils;
public final class MoreSuggestions extends Keyboard {
- public static final int SUGGESTION_CODE_BASE = 1024;
-
public final SuggestedWords mSuggestedWords;
- public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter {
- public abstract void onSuggestionSelected(final int index, final SuggestedWordInfo info);
- }
-
MoreSuggestions(final MoreSuggestionsParam params, final SuggestedWords suggestedWords) {
super(params);
mSuggestedWords = suggestedWords;
@@ -178,7 +171,7 @@ public final class MoreSuggestions extends Keyboard {
}
}
- private static boolean isIndexSubjectToAutoCorrection(final SuggestedWords suggestedWords,
+ static boolean isIndexSubjectToAutoCorrection(final SuggestedWords suggestedWords,
final int index) {
return suggestedWords.mWillAutoCorrect && index == SuggestedWords.INDEX_OF_AUTO_CORRECTION;
}
@@ -226,11 +219,7 @@ public final class MoreSuggestions extends Keyboard {
word = mSuggestedWords.getLabel(index);
info = mSuggestedWords.getDebugString(index);
}
- final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
- final Key key = new Key(word, KeyboardIconsSet.ICON_UNDEFINED,
- indexInMoreSuggestions, null /* outputText */, info, 0 /* labelFlags */,
- Key.BACKGROUND_TYPE_NORMAL, x, y, width, params.mDefaultRowHeight,
- params.mHorizontalGap, params.mVerticalGap);
+ final Key key = new MoreSuggestionKey(word, info, index, params);
params.markAsEdgeKey(key, index);
params.onAddKey(key);
final int columnNumber = params.getColumnNumber(index);
@@ -245,6 +234,19 @@ public final class MoreSuggestions extends Keyboard {
}
}
+ static final class MoreSuggestionKey extends Key {
+ public final int mSuggestedWordIndex;
+
+ public MoreSuggestionKey(final String word, final String info, final int index,
+ final MoreSuggestionsParam params) {
+ super(word /* label */, KeyboardIconsSet.ICON_UNDEFINED, Constants.CODE_OUTPUT_TEXT,
+ word /* outputText */, info, 0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL,
+ params.getX(index), params.getY(index), params.getWidth(index),
+ params.mDefaultRowHeight, params.mHorizontalGap, params.mVerticalGap);
+ mSuggestedWordIndex = index;
+ }
+ }
+
private static final class Divider extends Key.Spacer {
private final Drawable mIcon;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 549ff0d9d..528d500d2 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -20,11 +20,16 @@ import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
+import com.android.inputmethod.accessibility.AccessibilityUtils;
+import com.android.inputmethod.accessibility.MoreSuggestionsAccessibilityDelegate;
+import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.MoreKeysKeyboardView;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionKey;
/**
* A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting
@@ -33,6 +38,10 @@ import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestions
public final class MoreSuggestionsView extends MoreKeysKeyboardView {
private static final String TAG = MoreSuggestionsView.class.getSimpleName();
+ public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter {
+ public abstract void onSuggestionSelected(final int index, final SuggestedWordInfo info);
+ }
+
public MoreSuggestionsView(final Context context, final AttributeSet attrs) {
this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
}
@@ -43,6 +52,26 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
}
@Override
+ public void setKeyboard(final Keyboard keyboard) {
+ super.setKeyboard(keyboard);
+ // With accessibility mode off, {@link #mAccessibilityDelegate} is set to null at the
+ // above {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call.
+ // With accessibility mode on, {@link #mAccessibilityDelegate} is set to a
+ // {@link MoreKeysKeyboardAccessibilityDelegate} object at the above
+ // {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call. And the object has to be
+ // overwritten by a {@link MoreSuggestionsAccessibilityDelegate} object here.
+ if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+ if (!(mAccessibilityDelegate instanceof MoreSuggestionsAccessibilityDelegate)) {
+ mAccessibilityDelegate = new MoreSuggestionsAccessibilityDelegate(
+ this, mKeyDetector);
+ mAccessibilityDelegate.setOpenAnnounce(R.string.spoken_open_more_suggestions);
+ mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_suggestions);
+ }
+ mAccessibilityDelegate.setKeyboard(keyboard);
+ }
+ }
+
+ @Override
protected int getDefaultCoordX() {
final MoreSuggestions pane = (MoreSuggestions)getKeyboard();
return pane.mOccupiedWidth / 2;
@@ -59,7 +88,12 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
}
@Override
- public void onCodeInput(final int code, final int x, final int y) {
+ protected void onKeyInput(final Key key, final int x, final int y) {
+ if (!(key instanceof MoreSuggestionKey)) {
+ Log.e(TAG, "Expected key is MoreSuggestionKey, but found "
+ + key.getClass().getName());
+ return;
+ }
final Keyboard keyboard = getKeyboard();
if (!(keyboard instanceof MoreSuggestions)) {
Log.e(TAG, "Expected keyboard is MoreSuggestions, but found "
@@ -67,7 +101,7 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
return;
}
final SuggestedWords suggestedWords = ((MoreSuggestions)keyboard).mSuggestedWords;
- final int index = code - MoreSuggestions.SUGGESTION_CODE_BASE;
+ final int index = ((MoreSuggestionKey)key).mSuggestedWordIndex;
if (index < 0 || index >= suggestedWords.size()) {
Log.e(TAG, "Selected suggestion has an illegal index: " + index);
return;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 1d84bb59f..19b48f081 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -309,9 +309,8 @@ final class SuggestionStripLayoutHelper {
setupWordViewsTextAndColor(suggestedWords, mSuggestionsCountInStrip);
final TextView centerWordView = mWordViews.get(mCenterPositionInStrip);
- final int availableStripWidth = placerView.getWidth()
- - placerView.getPaddingRight() - placerView.getPaddingLeft();
- final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, availableStripWidth);
+ final int stripWidth = stripView.getWidth();
+ final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth);
final int countInStrip;
if (suggestedWords.size() == 1 || getTextScaleX(centerWordView.getText(), centerWidth,
centerWordView.getPaint()) < MIN_TEXT_XSCALE) {
@@ -319,11 +318,11 @@ final class SuggestionStripLayoutHelper {
// by consolidating all slots in the strip.
countInStrip = 1;
mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
- layoutWord(mCenterPositionInStrip, availableStripWidth - mPadding);
+ layoutWord(mCenterPositionInStrip, stripWidth - mPadding);
stripView.addView(centerWordView);
setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT);
if (SuggestionStripView.DBG) {
- layoutDebugInfo(mCenterPositionInStrip, placerView, availableStripWidth);
+ layoutDebugInfo(mCenterPositionInStrip, placerView, stripWidth);
}
} else {
countInStrip = mSuggestionsCountInStrip;
@@ -337,7 +336,7 @@ final class SuggestionStripLayoutHelper {
x += divider.getMeasuredWidth();
}
- final int width = getSuggestionWidth(positionInStrip, availableStripWidth);
+ final int width = getSuggestionWidth(positionInStrip, stripWidth);
final TextView wordView = layoutWord(positionInStrip, width);
stripView.addView(wordView);
setLayoutWeight(wordView, getSuggestionWeight(positionInStrip),
@@ -380,9 +379,9 @@ final class SuggestionStripLayoutHelper {
} else {
wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
}
-
- // Disable this suggestion if the suggestion is null or empty.
- wordView.setEnabled(!TextUtils.isEmpty(word));
+ // {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack.
+ // Use a simple {@link String} to avoid the issue.
+ wordView.setContentDescription(TextUtils.isEmpty(word) ? null : word.toString());
final CharSequence text = getEllipsizedText(word, width, wordView.getPaint());
final float scaleX = getTextScaleX(word, width, wordView.getPaint());
wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
@@ -425,7 +424,9 @@ final class SuggestionStripLayoutHelper {
final int countInStrip) {
// Clear all suggestions first
for (int positionInStrip = 0; positionInStrip < countInStrip; ++positionInStrip) {
- mWordViews.get(positionInStrip).setText(null);
+ final TextView wordView = mWordViews.get(positionInStrip);
+ wordView.setText(null);
+ wordView.setTag(null);
// Make this inactive for touches in {@link #layoutWord(int,int)}.
if (SuggestionStripView.DBG) {
mDebugInfoViews.get(positionInStrip).setText(null);
@@ -459,14 +460,15 @@ final class SuggestionStripLayoutHelper {
}
final TextView wordView = mWordViews.get(positionInStrip);
- wordView.setEnabled(true);
- wordView.setTextColor(mColorAutoCorrect);
+ final String punctuation = punctuationSuggestions.getLabel(positionInStrip);
// {@link TextView#getTag()} is used to get the index in suggestedWords at
// {@link SuggestionStripView#onClick(View)}.
wordView.setTag(positionInStrip);
- wordView.setText(punctuationSuggestions.getLabel(positionInStrip));
+ wordView.setText(punctuation);
+ wordView.setContentDescription(punctuation);
wordView.setTextScaleX(1.0f);
wordView.setCompoundDrawables(null, null, null, null);
+ wordView.setTextColor(mColorAutoCorrect);
stripView.addView(wordView);
setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight);
}
@@ -474,8 +476,8 @@ final class SuggestionStripLayoutHelper {
return countInStrip;
}
- public void layoutAddToDictionaryHint(final String word, final ViewGroup addToDictionaryStrip,
- final int stripWidth) {
+ public void layoutAddToDictionaryHint(final String word, final ViewGroup addToDictionaryStrip) {
+ final int stripWidth = addToDictionaryStrip.getWidth();
final int width = stripWidth - mDividerWidth - mPadding * 2;
final TextView wordView = (TextView)addToDictionaryStrip.findViewById(R.id.word_to_save);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index a0793b133..3be933ff7 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -18,7 +18,9 @@ package com.android.inputmethod.latin.suggestions;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -31,6 +33,7 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -39,17 +42,14 @@ import com.android.inputmethod.keyboard.MainKeyboardView;
import com.android.inputmethod.keyboard.MoreKeysPanel;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
-import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.suggestions.MoreSuggestionsView.MoreSuggestionsListener;
import com.android.inputmethod.latin.utils.ImportantNoticeUtils;
-import com.android.inputmethod.research.ResearchLogger;
import java.util.ArrayList;
@@ -59,12 +59,14 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
public void addWordToUserDictionary(String word);
public void showImportantNoticeContents();
public void pickSuggestionManually(int index, SuggestedWordInfo word);
+ public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat);
}
static final boolean DBG = LatinImeLogger.sDBG;
private static final float DEBUG_INFO_TEXT_SIZE_IN_DIP = 6.0f;
private final ViewGroup mSuggestionsStrip;
+ private final ImageButton mVoiceKey;
private final ViewGroup mAddToDictionaryStrip;
private final View mImportantNoticeStrip;
MainKeyboardView mMainKeyboardView;
@@ -73,9 +75,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private final MoreSuggestionsView mMoreSuggestionsView;
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
- private final ArrayList<TextView> mWordViews = CollectionUtils.newArrayList();
- private final ArrayList<TextView> mDebugInfoViews = CollectionUtils.newArrayList();
- private final ArrayList<View> mDividerViews = CollectionUtils.newArrayList();
+ private final ArrayList<TextView> mWordViews = new ArrayList<>();
+ private final ArrayList<TextView> mDebugInfoViews = new ArrayList<>();
+ private final ArrayList<View> mDividerViews = new ArrayList<>();
Listener mListener;
private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
@@ -85,12 +87,15 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private final StripVisibilityGroup mStripVisibilityGroup;
private static class StripVisibilityGroup {
+ private final View mSuggestionStripView;
private final View mSuggestionsStrip;
private final View mAddToDictionaryStrip;
private final View mImportantNoticeStrip;
- public StripVisibilityGroup(final View suggestionsStrip, final View addToDictionaryStrip,
+ public StripVisibilityGroup(final View suggestionStripView,
+ final ViewGroup suggestionsStrip, final ViewGroup addToDictionaryStrip,
final View importantNoticeStrip) {
+ mSuggestionStripView = suggestionStripView;
mSuggestionsStrip = suggestionsStrip;
mAddToDictionaryStrip = addToDictionaryStrip;
mImportantNoticeStrip = importantNoticeStrip;
@@ -100,6 +105,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
public void setLayoutDirection(final boolean isRtlLanguage) {
final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL
: ViewCompat.LAYOUT_DIRECTION_LTR;
+ ViewCompat.setLayoutDirection(mSuggestionStripView, layoutDirection);
ViewCompat.setLayoutDirection(mSuggestionsStrip, layoutDirection);
ViewCompat.setLayoutDirection(mAddToDictionaryStrip, layoutDirection);
ViewCompat.setLayoutDirection(mImportantNoticeStrip, layoutDirection);
@@ -145,10 +151,11 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
inflater.inflate(R.layout.suggestions_strip, this);
mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
+ mVoiceKey = (ImageButton)findViewById(R.id.suggestions_strip_voice_key);
mAddToDictionaryStrip = (ViewGroup)findViewById(R.id.add_to_dictionary_strip);
mImportantNoticeStrip = findViewById(R.id.important_notice_strip);
- mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mAddToDictionaryStrip,
- mImportantNoticeStrip);
+ mStripVisibilityGroup = new StripVisibilityGroup(this, mSuggestionsStrip,
+ mAddToDictionaryStrip, mImportantNoticeStrip);
for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) {
final TextView word = new TextView(context, null, R.attr.suggestionWordStyle);
@@ -156,7 +163,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
word.setOnLongClickListener(this);
mWordViews.add(word);
final View divider = inflater.inflate(R.layout.suggestion_divider, null);
- divider.setOnClickListener(this);
mDividerViews.add(divider);
final TextView info = new TextView(context, null, R.attr.suggestionWordStyle);
info.setTextColor(Color.WHITE);
@@ -177,6 +183,13 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
R.dimen.config_more_suggestions_modal_tolerance);
mMoreSuggestionsSlidingDetector = new GestureDetector(
context, mMoreSuggestionsSlidingListener);
+
+ final TypedArray keyboardAttr = context.obtainStyledAttributes(attrs,
+ R.styleable.Keyboard, defStyle, R.style.SuggestionStripView);
+ final Drawable iconVoice = keyboardAttr.getDrawable(R.styleable.Keyboard_iconShortcutKey);
+ keyboardAttr.recycle();
+ mVoiceKey.setImageDrawable(iconVoice);
+ mVoiceKey.setOnClickListener(this);
}
/**
@@ -188,15 +201,19 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
}
+ public void updateVisibility(final boolean shouldBeVisible, final boolean isFullscreenMode) {
+ final int visibility = shouldBeVisible ? VISIBLE : (isFullscreenMode ? GONE : INVISIBLE);
+ setVisibility(visibility);
+ final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent();
+ mVoiceKey.setVisibility(currentSettingsValues.mShowsVoiceInputKey ? VISIBLE : INVISIBLE);
+ }
+
public void setSuggestions(final SuggestedWords suggestedWords, final boolean isRtlLanguage) {
clear();
mStripVisibilityGroup.setLayoutDirection(isRtlLanguage);
mSuggestedWords = suggestedWords;
mSuggestionsCountInStrip = mLayoutHelper.layoutAndReturnSuggestionCountInStrip(
mSuggestedWords, mSuggestionsStrip, this);
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
- }
mStripVisibilityGroup.showSuggestionsStrip();
}
@@ -209,7 +226,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
}
public void showAddToDictionaryHint(final String word) {
- mLayoutHelper.layoutAddToDictionaryHint(word, mAddToDictionaryStrip, getWidth());
+ mLayoutHelper.layoutAddToDictionaryHint(word, mAddToDictionaryStrip);
// {@link TextView#setTag()} is used to hold the word to be added to dictionary. The word
// will be extracted at {@link #onClick(View)}.
mAddToDictionaryStrip.setTag(word);
@@ -228,8 +245,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
// This method checks if we should show the important notice (checks on permanent storage if
// it has been shown once already or not, and if in the setup wizard). If applicable, it shows
// the notice. In all cases, it returns true if it was shown, false otherwise.
- public boolean maybeShowImportantNoticeTitle(final InputAttributes inputAttributes) {
- if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext(), inputAttributes)) {
+ public boolean maybeShowImportantNoticeTitle() {
+ if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext())) {
return false;
}
if (getWidth() <= 0) {
@@ -411,10 +428,18 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public void onClick(final View view) {
+ AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
+ Constants.CODE_UNSPECIFIED, this);
if (view == mImportantNoticeStrip) {
mListener.showImportantNoticeContents();
return;
}
+ if (view == mVoiceKey) {
+ mListener.onCodeInput(Constants.CODE_SHORTCUT,
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
+ false /* isKeyRepeat */);
+ return;
+ }
final Object tag = view.getTag();
// {@link String} tag is set at {@link #showAddToDictionaryHint(String,CharSequence)}.
if (tag instanceof String) {
@@ -448,7 +473,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
// Called by the framework when the size is known. Show the important notice if applicable.
// This may be overriden by showing suggestions later, if applicable.
if (oldw <= 0 && w > 0) {
- maybeShowImportantNoticeTitle(Settings.getInstance().getCurrent().mInputAttributes);
+ maybeShowImportantNoticeTitle();
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index 21426d1eb..eda81940f 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -167,7 +167,9 @@ public class UserDictionaryAddWordContents {
// should not insert, because either A. the word exists with no shortcut, in which
// case the exact same thing we want to insert is already there, or B. the word
// exists with at least one shortcut, in which case it has priority on our word.
- if (hasWord(newWord, context)) return CODE_ALREADY_PRESENT;
+ if (TextUtils.isEmpty(newShortcut) && hasWord(newWord, context)) {
+ return CODE_ALREADY_PRESENT;
+ }
// Disallow duplicates. If the same word with no shortcut is defined, remove it; if
// the same word with the same shortcut is defined, remove it; but we don't mind if
@@ -256,7 +258,7 @@ public class UserDictionaryAddWordContents {
// The system locale should be inside. We want it at the 2nd spot.
locales.remove(systemLocale); // system locale may not be null
locales.remove(""); // Remove the empty string if it's there
- final ArrayList<LocaleRenderer> localesList = new ArrayList<LocaleRenderer>();
+ final ArrayList<LocaleRenderer> localesList = new ArrayList<>();
// Add the passed locale, then the system locale at the top of the list. Add an
// "all languages" entry at the bottom of the list.
addLocaleDisplayNameToList(activity, localesList, mLocale);
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
index 4fc132f68..163443036 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
@@ -134,8 +134,8 @@ public class UserDictionaryAddWordFragment extends Fragment
final Spinner localeSpinner =
(Spinner)mRootView.findViewById(R.id.user_dictionary_add_locale);
- final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<LocaleRenderer>(getActivity(),
- android.R.layout.simple_spinner_item, localesList);
+ final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<>(
+ getActivity(), android.R.layout.simple_spinner_item, localesList);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
localeSpinner.setAdapter(adapter);
localeSpinner.setOnItemSelectedListener(this);
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index 97a924d7b..624783a70 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -56,7 +56,7 @@ public class UserDictionaryList extends PreferenceFragment {
final Cursor cursor = activity.getContentResolver().query(UserDictionary.Words.CONTENT_URI,
new String[] { UserDictionary.Words.LOCALE },
null, null, null);
- final TreeSet<String> localeSet = new TreeSet<String>();
+ final TreeSet<String> localeSet = new TreeSet<>();
if (null == cursor) {
// The user dictionary service is not present or disabled. Return null.
return null;
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index 2bb30a2ba..3ca7c7e1c 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -95,8 +95,7 @@ public final class AdditionalSubtypeUtils {
return EMPTY_SUBTYPE_ARRAY;
}
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
- final ArrayList<InputMethodSubtype> subtypesList =
- CollectionUtils.newArrayList(prefSubtypeArray.length);
+ final ArrayList<InputMethodSubtype> subtypesList = new ArrayList<>(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) {
final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE
diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index 22b9b77d2..34ee2152a 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -16,12 +16,11 @@
package com.android.inputmethod.latin.utils;
-import com.android.inputmethod.latin.BinaryDictionary;
+import android.util.Log;
+
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import android.util.Log;
-
public final class AutoCorrectionUtils {
private static final boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
@@ -36,7 +35,9 @@ public final class AutoCorrectionUtils {
final float autoCorrectionThreshold) {
if (null != suggestion) {
// Shortlist a whitelisted word
- if (suggestion.mKind == SuggestedWordInfo.KIND_WHITELIST) return true;
+ if (suggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
+ return true;
+ }
final int autoCorrectionSuggestionScore = suggestion.mScore;
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 702688f93..936219332 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -62,6 +62,22 @@ public final class CapsModeUtils {
}
/**
+ * Helper method to find out if a code point is starting punctuation.
+ *
+ * This include the Unicode START_PUNCTUATION category, but also some other symbols that are
+ * starting, like the inverted question mark or the double quote.
+ *
+ * @param codePoint the code point
+ * @return true if it's starting punctuation, false otherwise.
+ */
+ private static boolean isStartPunctuation(final int codePoint) {
+ return (codePoint == Constants.CODE_DOUBLE_QUOTE || codePoint == Constants.CODE_SINGLE_QUOTE
+ || codePoint == Constants.CODE_INVERTED_QUESTION_MARK
+ || codePoint == Constants.CODE_INVERTED_EXCLAMATION_MARK
+ || Character.getType(codePoint) == Character.START_PUNCTUATION);
+ }
+
+ /**
* Determine what caps mode should be in effect at the current offset in
* the text. Only the mode bits set in <var>reqModes</var> will be
* checked. Note that the caps mode flags here are explicitly defined
@@ -115,8 +131,7 @@ public final class CapsModeUtils {
} else {
for (i = cs.length(); i > 0; i--) {
final char c = cs.charAt(i - 1);
- if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE
- && Character.getType(c) != Character.START_PUNCTUATION) {
+ if (!isStartPunctuation(c)) {
break;
}
}
@@ -210,11 +225,14 @@ public final class CapsModeUtils {
// We found out that we have a period. We need to determine if this is a full stop or
// otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation
- // looks like (\w\.){2,}
+ // looks like (\w\.){2,}. Moreover, in German, you put periods after digits for dates
+ // and some other things, and in German specifically we need to not go into autocaps after
+ // a whitespace-digits-period sequence.
// To find out, we will have a simple state machine with the following states :
- // START, WORD, PERIOD, ABBREVIATION
+ // START, WORD, PERIOD, ABBREVIATION, NUMBER
// On START : (just before the first period)
// letter => WORD
+ // digit => NUMBER if German; end with caps otherwise
// whitespace => end with no caps (it was a stand-alone period)
// otherwise => end with caps (several periods/symbols in a row)
// On WORD : (within the word just before the first period)
@@ -228,6 +246,11 @@ public final class CapsModeUtils {
// letter => LETTER
// period => PERIOD
// otherwise => end with no caps (it was an abbreviation)
+ // On NUMBER : (period immediately preceded by one or more digits)
+ // digit => NUMBER
+ // letter => LETTER (promote to word)
+ // otherwise => end with no caps (it was a whitespace-digits-period sequence,
+ // or a punctuation-digits-period sequence like "11.11.")
// "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This
// should capitalize.
@@ -235,6 +258,7 @@ public final class CapsModeUtils {
final int WORD = 1;
final int PERIOD = 2;
final int LETTER = 3;
+ final int NUMBER = 4;
final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
| TextUtils.CAP_MODE_SENTENCES) & reqModes;
final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
@@ -247,6 +271,8 @@ public final class CapsModeUtils {
state = WORD;
} else if (Character.isWhitespace(c)) {
return noCaps;
+ } else if (Character.isDigit(c) && spacingAndPunctuations.mUsesGermanRules) {
+ state = NUMBER;
} else {
return caps;
}
@@ -275,6 +301,15 @@ public final class CapsModeUtils {
} else {
return noCaps;
}
+ break;
+ case NUMBER:
+ if (Character.isLetter(c)) {
+ state = WORD;
+ } else if (Character.isDigit(c)) {
+ state = NUMBER;
+ } else {
+ return noCaps;
+ }
}
}
// Here we arrived at the start of the line. This should behave exactly like whitespace.
diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
index bbfa0f091..e3aef29ba 100644
--- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
@@ -16,93 +16,21 @@
package com.android.inputmethod.latin.utils;
-import android.util.SparseArray;
-
-import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.WeakHashMap;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
public final class CollectionUtils {
private CollectionUtils() {
// This utility class is not publicly instantiable.
}
- public static <K,V> HashMap<K,V> newHashMap() {
- return new HashMap<K,V>();
- }
-
- public static <K, V> WeakHashMap<K, V> newWeakHashMap() {
- return new WeakHashMap<K, V>();
- }
-
- public static <K,V> TreeMap<K,V> newTreeMap() {
- return new TreeMap<K,V>();
- }
-
public static <K, V> Map<K,V> newSynchronizedTreeMap() {
- final TreeMap<K,V> treeMap = newTreeMap();
+ final TreeMap<K,V> treeMap = new TreeMap<>();
return Collections.synchronizedMap(treeMap);
}
- public static <K,V> ConcurrentHashMap<K,V> newConcurrentHashMap() {
- return new ConcurrentHashMap<K,V>();
- }
-
- public static <E> HashSet<E> newHashSet() {
- return new HashSet<E>();
- }
-
- public static <E> TreeSet<E> newTreeSet() {
- return new TreeSet<E>();
- }
-
- public static <E> ArrayList<E> newArrayList() {
- return new ArrayList<E>();
- }
-
- public static <E> ArrayList<E> newArrayList(final int initialCapacity) {
- return new ArrayList<E>(initialCapacity);
- }
-
- public static <E> ArrayList<E> newArrayList(final Collection<E> collection) {
- return new ArrayList<E>(collection);
- }
-
- public static <E> LinkedList<E> newLinkedList() {
- return new LinkedList<E>();
- }
-
- public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList() {
- return new CopyOnWriteArrayList<E>();
- }
-
- public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(
- final Collection<E> collection) {
- return new CopyOnWriteArrayList<E>(collection);
- }
-
- public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(final E[] array) {
- return new CopyOnWriteArrayList<E>(array);
- }
-
- public static <E> ArrayDeque<E> newArrayDeque() {
- return new ArrayDeque<E>();
- }
-
- public static <E> SparseArray<E> newSparseArray() {
- return new SparseArray<E>();
- }
-
public static <E> ArrayList<E> arrayAsList(final E[] array, final int start, final int end) {
if (array == null) {
throw new NullPointerException();
@@ -111,7 +39,7 @@ public final class CollectionUtils {
throw new IllegalArgumentException();
}
- final ArrayList<E> list = newArrayList(end - start);
+ final ArrayList<E> list = new ArrayList<>(end - start);
for (int i = start; i < end; i++) {
list.add(array[i]);
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
index b18a1d83b..a21a1373b 100644
--- a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
@@ -209,7 +209,7 @@ public final class CsvUtils {
@UsedForTesting
public static String[] split(final int splitFlags, final String line) throws CsvParseException {
final boolean trimSpaces = (splitFlags & SPLIT_FLAGS_TRIM_SPACES) != 0;
- final ArrayList<String> fields = CollectionUtils.newArrayList();
+ final ArrayList<String> fields = new ArrayList<>();
final int length = line.length();
int start = 0;
do {
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 315913e2f..d76ea10c0 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -336,7 +336,7 @@ public class DictionaryInfoUtils {
public static ArrayList<DictionaryInfo> getCurrentDictionaryFileNameAndVersionInfo(
final Context context) {
- final ArrayList<DictionaryInfo> dictList = CollectionUtils.newArrayList();
+ final ArrayList<DictionaryInfo> dictList = new ArrayList<>();
// Retrieve downloaded dictionaries
final File[] directoryList = getCachedDirectoryList(context);
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index f2a1e524d..787e4a59d 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -16,33 +16,43 @@
package com.android.inputmethod.latin.utils;
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.latin.Suggest;
+import java.util.List;
+import java.util.Locale;
-/**
- * This class is used to prevent distracters/misspellings being added to personalization
- * or user history dictionaries
- */
-public class DistracterFilter {
- private final Suggest mSuggest;
- private final Keyboard mKeyboard;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.PrevWordsInfo;
+public interface DistracterFilter {
/**
- * Create a DistracterFilter instance.
+ * Determine whether a word is a distracter to words in dictionaries.
*
- * @param suggest an instance of Suggest which will be used to obtain a list of suggestions
- * for a potential distracter/misspelling
- * @param keyboard the keyboard that is currently being used. This information is needed
- * when calling mSuggest.getSuggestedWords(...) to obtain a list of suggestions.
+ * @param prevWordsInfo the information of previous words.
+ * @param testedWord the word that will be tested to see whether it is a distracter to words
+ * in dictionaries.
+ * @param locale the locale of word.
+ * @return true if testedWord is a distracter, otherwise false.
*/
- public DistracterFilter(final Suggest suggest, final Keyboard keyboard) {
- mSuggest = suggest;
- mKeyboard = keyboard;
- }
-
- public boolean isDistractorToWordsInDictionaries(final String prevWord,
- final String targetWord) {
- // TODO: to be implemented
- return false;
- }
+ public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
+ final String testedWord, final Locale locale);
+
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
+
+ public void close();
+
+ public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() {
+ @Override
+ public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
+ String testedWord, Locale locale) {
+ return false;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
+ }
+ };
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java
new file mode 100644
index 000000000..0ee6236b1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.LruCache;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.DictionaryFacilitator;
+import com.android.inputmethod.latin.PrevWordsInfo;
+
+/**
+ * This class is used to prevent distracters being added to personalization
+ * or user history dictionaries
+ */
+public class DistracterFilterCheckingExactMatches implements DistracterFilter {
+ private static final String TAG = DistracterFilterCheckingExactMatches.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120;
+ private static final int MAX_DISTRACTERS_CACHE_SIZE = 512;
+
+ private final Context mContext;
+ private final DictionaryFacilitator mDictionaryFacilitator;
+ private final LruCache<String, Boolean> mDistractersCache;
+ private final Object mLock = new Object();
+
+ /**
+ * Create a DistracterFilter instance.
+ *
+ * @param context the context.
+ */
+ public DistracterFilterCheckingExactMatches(final Context context) {
+ mContext = context;
+ mDictionaryFacilitator = new DictionaryFacilitator();
+ mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
+ }
+
+ @Override
+ public void close() {
+ mDictionaryFacilitator.closeDictionaries();
+ }
+
+ @Override
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
+ }
+
+ private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException {
+ mDictionaryFacilitator.resetDictionaries(mContext, newlocale,
+ false /* useContactsDict */, false /* usePersonalizedDicts */,
+ false /* forceReloadMainDictionary */, null /* listener */);
+ mDictionaryFacilitator.waitForLoadingMainDictionary(
+ TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Determine whether a word is a distracter to words in dictionaries.
+ *
+ * @param prevWordsInfo the information of previous words. Not used for now.
+ * @param testedWord the word that will be tested to see whether it is a distracter to words
+ * in dictionaries.
+ * @param locale the locale of word.
+ * @return true if testedWord is a distracter, otherwise false.
+ */
+ @Override
+ public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
+ final String testedWord, final Locale locale) {
+ if (locale == null) {
+ return false;
+ }
+ if (!locale.equals(mDictionaryFacilitator.getLocale())) {
+ synchronized (mLock) {
+ // Reset dictionaries for the locale.
+ try {
+ mDistractersCache.evictAll();
+ loadDictionariesForLocale(locale);
+ } catch (final InterruptedException e) {
+ Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter",
+ e);
+ return false;
+ }
+ }
+ }
+
+ final Boolean isCachedDistracter = mDistractersCache.get(testedWord);
+ if (isCachedDistracter != null && isCachedDistracter) {
+ if (DEBUG) {
+ Log.d(TAG, "testedWord: " + testedWord);
+ Log.d(TAG, "isDistracter: true (cache hit)");
+ }
+ return true;
+ }
+ // The tested word is a distracter when there is a word that is exact matched to the tested
+ // word and its probability is higher than the tested word's probability.
+ final int perfectMatchFreq = mDictionaryFacilitator.getFrequency(testedWord);
+ final int exactMatchFreq = mDictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
+ final boolean isDistracter = perfectMatchFreq < exactMatchFreq;
+ if (DEBUG) {
+ Log.d(TAG, "testedWord: " + testedWord);
+ Log.d(TAG, "perfectMatchFreq: " + perfectMatchFreq);
+ Log.d(TAG, "exactMatchFreq: " + exactMatchFreq);
+ Log.d(TAG, "isDistracter: " + isDistracter);
+ }
+ if (isDistracter) {
+ // Add the word to the cache.
+ mDistractersCache.put(testedWord, Boolean.TRUE);
+ }
+ return isDistracter;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
new file mode 100644
index 000000000..4ad4ba784
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.util.List;
+import java.util.Locale;
+
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
+
+public class DistracterFilterCheckingIsInDictionary implements DistracterFilter {
+ private final DistracterFilter mDistracterFilter;
+ private final Dictionary mDictionary;
+
+ public DistracterFilterCheckingIsInDictionary(final DistracterFilter distracterFilter,
+ final Dictionary dictionary) {
+ mDistracterFilter = distracterFilter;
+ mDictionary = dictionary;
+ }
+
+ @Override
+ public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
+ String testedWord, Locale locale) {
+ if (mDictionary.isInDictionary(testedWord)) {
+ // This filter treats entries that are already in the dictionary as non-distracters
+ // because they have passed the filtering in the past.
+ return false;
+ } else {
+ return mDistracterFilter.isDistracterToWordsInDictionaries(
+ prevWordsInfo, testedWord, locale);
+ }
+ }
+
+ @Override
+ public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
+ // Do nothing.
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
index ed502ed3d..61da1b789 100644
--- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
@@ -19,22 +19,42 @@ package com.android.inputmethod.latin.utils;
import com.android.inputmethod.annotations.UsedForTesting;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
/**
* Utilities to manage executors.
*/
public class ExecutorUtils {
- private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
- sExecutorMap = CollectionUtils.newConcurrentHashMap();
+ private static final ConcurrentHashMap<String, ExecutorService> sExecutorMap =
+ new ConcurrentHashMap<>();
+
+ private static class ThreadFactoryWithId implements ThreadFactory {
+ private final String mId;
+
+ public ThreadFactoryWithId(final String id) {
+ mId = id;
+ }
+
+ @Override
+ public Thread newThread(final Runnable r) {
+ return new Thread(r, "Executor - " + mId);
+ }
+ }
+
/**
- * Gets the executor for the given dictionary name.
+ * Gets the executor for the given id.
*/
- public static PrioritizedSerialExecutor getExecutor(final String dictName) {
- PrioritizedSerialExecutor executor = sExecutorMap.get(dictName);
+ public static ExecutorService getExecutor(final String id) {
+ ExecutorService executor = sExecutorMap.get(id);
if (executor == null) {
synchronized(sExecutorMap) {
- executor = new PrioritizedSerialExecutor();
- sExecutorMap.put(dictName, executor);
+ executor = sExecutorMap.get(id);
+ if (executor == null) {
+ executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithId(id));
+ sExecutorMap.put(id, executor);
+ }
}
}
return executor;
@@ -46,7 +66,7 @@ public class ExecutorUtils {
@UsedForTesting
public static void shutdownAllExecutors() {
synchronized(sExecutorMap) {
- for (final PrioritizedSerialExecutor executor : sExecutorMap.values()) {
+ for (final ExecutorService executor : sExecutorMap.values()) {
executor.execute(new Runnable() {
@Override
public void run() {
diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
index ee2b97b2a..e300bd1d3 100644
--- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
@@ -26,12 +26,11 @@ import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragmen
import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker;
import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
-import com.android.inputmethod.research.FeedbackFragment;
import java.util.HashSet;
public class FragmentUtils {
- private static final HashSet<String> sLatinImeFragments = new HashSet<String>();
+ private static final HashSet<String> sLatinImeFragments = new HashSet<>();
static {
sLatinImeFragments.add(DictionarySettingsFragment.class.getName());
sLatinImeFragments.add(AboutPreferences.class.getName());
@@ -43,7 +42,6 @@ public class FragmentUtils {
sLatinImeFragments.add(UserDictionaryList.class.getName());
sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName());
sLatinImeFragments.add(UserDictionarySettings.class.getName());
- sLatinImeFragments.add(FeedbackFragment.class.getName());
}
public static boolean isValidFragment(String fragmentName) {
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
index 7d937a9d2..8b7077879 100644
--- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -23,7 +23,6 @@ import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
import android.util.Log;
-import com.android.inputmethod.latin.InputAttributes;
import com.android.inputmethod.latin.R;
public final class ImportantNoticeUtils {
@@ -78,14 +77,7 @@ public final class ImportantNoticeUtils {
return getCurrentImportantNoticeVersion(context) > lastVersion;
}
- public static boolean shouldShowImportantNotice(final Context context,
- final InputAttributes inputAttributes) {
- if (inputAttributes == null || inputAttributes.mIsPasswordField) {
- return false;
- }
- if (isInSystemSetupWizard(context)) {
- return false;
- }
+ public static boolean shouldShowImportantNotice(final Context context) {
if (!hasNewImportantNotice(context)) {
return false;
}
@@ -93,6 +85,9 @@ public final class ImportantNoticeUtils {
if (TextUtils.isEmpty(importantNoticeTitle)) {
return false;
}
+ if (isInSystemSetupWizard(context)) {
+ return false;
+ }
return true;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
index 764ef72ce..6dd8d9711 100644
--- a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
@@ -37,7 +37,7 @@ public final class JsonUtils {
private static final String EMPTY_STRING = "";
public static List<Object> jsonStrToList(final String s) {
- final ArrayList<Object> list = CollectionUtils.newArrayList();
+ final ArrayList<Object> list = new ArrayList<>();
final JsonReader reader = new JsonReader(new StringReader(s));
try {
reader.beginArray();
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
index 5ce977d5e..4248bebf6 100644
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -19,10 +19,12 @@ package com.android.inputmethod.latin.utils;
import android.util.Log;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest;
+import com.android.inputmethod.latin.DictionaryFacilitator;
+import com.android.inputmethod.latin.PrevWordsInfo;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
// Note: this class is used as a parameter type of a native method. You should be careful when you
@@ -78,13 +80,13 @@ public final class LanguageModelParam {
// Process a list of words and return a list of {@link LanguageModelParam} objects.
public static ArrayList<LanguageModelParam> createLanguageModelParamsFrom(
- final ArrayList<String> tokens, final int timestamp,
- final DictionaryFacilitatorForSuggest dictionaryFacilitator,
- final SpacingAndPunctuations spacingAndPunctuations) {
- final ArrayList<LanguageModelParam> languageModelParams =
- CollectionUtils.newArrayList();
+ final List<String> tokens, final int timestamp,
+ final DictionaryFacilitator dictionaryFacilitator,
+ final SpacingAndPunctuations spacingAndPunctuations,
+ final DistracterFilter distracterFilter) {
+ final ArrayList<LanguageModelParam> languageModelParams = new ArrayList<>();
final int N = tokens.size();
- String prevWord = null;
+ PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
for (int i = 0; i < N; ++i) {
final String tempWord = tokens.get(i);
if (StringUtils.isEmptyStringOrWhiteSpaces(tempWord)) {
@@ -101,7 +103,7 @@ public final class LanguageModelParam {
+ tempWord + "\"");
}
// Sentence terminator found. Split.
- prevWord = null;
+ prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
continue;
}
if (DEBUG_TOKEN) {
@@ -109,56 +111,63 @@ public final class LanguageModelParam {
}
final LanguageModelParam languageModelParam =
detectWhetherVaildWordOrNotAndGetLanguageModelParam(
- prevWord, tempWord, timestamp, dictionaryFacilitator);
+ prevWordsInfo, tempWord, timestamp, dictionaryFacilitator,
+ distracterFilter);
if (languageModelParam == null) {
continue;
}
languageModelParams.add(languageModelParam);
- prevWord = languageModelParam.mTargetWord;
+ prevWordsInfo = new PrevWordsInfo(languageModelParam.mTargetWord);
}
return languageModelParams;
}
private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
- final String prevWord, final String targetWord, final int timestamp,
- final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
+ final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
+ final DictionaryFacilitator dictionaryFacilitator,
+ final DistracterFilter distracterFilter) {
final Locale locale = dictionaryFacilitator.getLocale();
if (locale == null) {
return null;
}
- if (!dictionaryFacilitator.isValidWord(targetWord, true /* ignoreCase */)) {
- // OOV word.
- return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
- false /* isValidWord */, locale);
- }
if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
- return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
- true /* isValidWord */, locale);
+ return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
+ true /* isValidWord */, locale, distracterFilter);
}
+
final String lowerCaseTargetWord = targetWord.toLowerCase(locale);
if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
// Add the lower-cased word.
- return createAndGetLanguageModelParamOfWord(prevWord, lowerCaseTargetWord,
- timestamp, true /* isValidWord */, locale);
+ return createAndGetLanguageModelParamOfWord(prevWordsInfo, lowerCaseTargetWord,
+ timestamp, true /* isValidWord */, locale, distracterFilter);
}
+
// Treat the word as an OOV word.
- return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
- false /* isValidWord */, locale);
+ return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
+ false /* isValidWord */, locale, distracterFilter);
}
private static LanguageModelParam createAndGetLanguageModelParamOfWord(
- final String prevWord, final String targetWord, final int timestamp,
- final boolean isValidWord, final Locale locale) {
+ final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp,
+ final boolean isValidWord, final Locale locale,
+ final DistracterFilter distracterFilter) {
final String word;
if (StringUtils.getCapitalizationType(targetWord) == StringUtils.CAPITALIZE_FIRST
- && prevWord == null && !isValidWord) {
+ && prevWordsInfo.mPrevWord == null && !isValidWord) {
word = targetWord.toLowerCase(locale);
} else {
word = targetWord;
}
+ // Check whether the word is a distracter to words in the dictionaries.
+ if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, word, locale)) {
+ if (DEBUG) {
+ Log.d(TAG, "The word (" + word + ") is a distracter. Skip this word.");
+ }
+ return null;
+ }
final int unigramProbability = isValidWord ?
UNIGRAM_PROBABILITY_FOR_VALID_WORD : UNIGRAM_PROBABILITY_FOR_OOV_WORD;
- if (prevWord == null) {
+ if (prevWordsInfo.mPrevWord == null) {
if (DEBUG) {
Log.d(TAG, "--- add unigram: current("
+ (isValidWord ? "Valid" : "OOV") + ") = " + word);
@@ -166,12 +175,12 @@ public final class LanguageModelParam {
return new LanguageModelParam(word, unigramProbability, timestamp);
}
if (DEBUG) {
- Log.d(TAG, "--- add bigram: prev = " + prevWord + ", current("
+ Log.d(TAG, "--- add bigram: prev = " + prevWordsInfo.mPrevWord + ", current("
+ (isValidWord ? "Valid" : "OOV") + ") = " + word);
}
final int bigramProbability = isValidWord ?
BIGRAM_PROBABILITY_FOR_VALID_WORD : BIGRAM_PROBABILITY_FOR_OOV_WORD;
- return new LanguageModelParam(prevWord, word, unigramProbability,
+ return new LanguageModelParam(prevWordsInfo.mPrevWord, word, unigramProbability,
bigramProbability, timestamp);
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
deleted file mode 100644
index d14ba508b..000000000
--- a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.text.TextUtils;
-
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.WordComposer;
-
-public final class LatinImeLoggerUtils {
- private LatinImeLoggerUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static void onNonSeparator(final char code, final int x, final int y) {
- UserLogRingCharBuffer.getInstance().push(code, x, y);
- LatinImeLogger.logOnInputChar();
- }
-
- public static void onSeparator(final int code, final int x, final int y) {
- // Helper method to log a single code point separator
- // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
- onSeparator(StringUtils.newSingleCodePointString(code), x, y);
- }
-
- public static void onSeparator(final String separator, final int x, final int y) {
- final int length = separator.length();
- for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
- int codePoint = Character.codePointAt(separator, i);
- // TODO: accept code points
- UserLogRingCharBuffer.getInstance().push((char)codePoint, x, y);
- }
- LatinImeLogger.logOnInputSeparator();
- }
-
- public static void onAutoCorrection(final String typedWord, final String correctedWord,
- final String separatorString, final WordComposer wordComposer) {
- final boolean isBatchMode = wordComposer.isBatchMode();
- if (!isBatchMode && TextUtils.isEmpty(typedWord)) {
- return;
- }
- // TODO: this fails when the separator is more than 1 code point long, but
- // the backend can't handle it yet. The only case when this happens is with
- // smileys and other multi-character keys.
- final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
- : separatorString.codePointAt(0);
- if (!isBatchMode) {
- LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
- } else {
- if (!TextUtils.isEmpty(correctedWord)) {
- // We must make sure that InputPointer contains only the relative timestamps,
- // not actual timestamps.
- LatinImeLogger.logOnAutoCorrectionForGeometric(
- "", correctedWord, codePoint, wordComposer.getInputPointers());
- }
- }
- }
-
- public static void onAutoCorrectionCancellation() {
- LatinImeLogger.logOnAutoCorrectionCancelled();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
index 8469c87b0..dd6fac671 100644
--- a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
@@ -33,7 +33,7 @@ public class LeakGuardHandlerWrapper<T> extends Handler {
if (ownerInstance == null) {
throw new NullPointerException("ownerInstance is null");
}
- mOwnerInstanceRef = new WeakReference<T>(ownerInstance);
+ mOwnerInstanceRef = new WeakReference<>(ownerInstance);
}
public T getOwnerInstance() {
diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
index 0c55484b4..c519a0de6 100644
--- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
@@ -159,7 +159,7 @@ public final class LocaleUtils {
return LOCALE_MATCH <= level;
}
- private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
+ private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
/**
* Creates a locale from a string specification.
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
deleted file mode 100644
index bf38abc95..000000000
--- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-
-import java.util.Queue;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * An object that executes submitted tasks using a thread.
- */
-public class PrioritizedSerialExecutor {
- public static final String TAG = PrioritizedSerialExecutor.class.getSimpleName();
-
- private final Object mLock = new Object();
-
- private final Queue<Runnable> mTasks;
- private final Queue<Runnable> mPrioritizedTasks;
- private boolean mIsShutdown;
- private final ThreadPoolExecutor mThreadPoolExecutor;
-
- // The task which is running now.
- private Runnable mActive;
-
- public PrioritizedSerialExecutor() {
- mTasks = new ConcurrentLinkedQueue<Runnable>();
- mPrioritizedTasks = new ConcurrentLinkedQueue<Runnable>();
- mIsShutdown = false;
- mThreadPoolExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
- 0 /* keepAliveTime */, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1));
- }
-
- /**
- * Enqueues the given task into the task queue.
- * @param r the enqueued task
- */
- public void execute(final Runnable r) {
- synchronized(mLock) {
- if (!mIsShutdown) {
- mTasks.offer(new Runnable() {
- @Override
- public void run() {
- try {
- r.run();
- } finally {
- scheduleNext();
- }
- }
- });
- if (mActive == null) {
- scheduleNext();
- }
- }
- }
- }
-
- /**
- * Enqueues the given task into the prioritized task queue.
- * @param r the enqueued task
- */
- @UsedForTesting
- public void executePrioritized(final Runnable r) {
- synchronized(mLock) {
- if (!mIsShutdown) {
- mPrioritizedTasks.offer(new Runnable() {
- @Override
- public void run() {
- try {
- r.run();
- } finally {
- scheduleNext();
- }
- }
- });
- if (mActive == null) {
- scheduleNext();
- }
- }
- }
- }
-
- private boolean fetchNextTasksLocked() {
- mActive = mPrioritizedTasks.poll();
- if (mActive == null) {
- mActive = mTasks.poll();
- }
- return mActive != null;
- }
-
- private void scheduleNext() {
- synchronized(mLock) {
- if (fetchNextTasksLocked()) {
- mThreadPoolExecutor.execute(mActive);
- }
- }
- }
-
- public void shutdown() {
- synchronized(mLock) {
- mIsShutdown = true;
- mThreadPoolExecutor.shutdown();
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index 4521ec531..e3cac97f0 100644
--- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
@@ -62,18 +62,22 @@ public class RecapitalizeStatus {
private Locale mLocale;
private int[] mSortedSeparators;
private String mStringAfter;
- private boolean mIsActive;
+ private boolean mIsStarted;
+ private boolean mIsEnabled = true;
private static final int[] EMPTY_STORTED_SEPARATORS = {};
public RecapitalizeStatus() {
// By default, initialize with dummy values that won't match any real recapitalize.
- initialize(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS);
- deactivate();
+ start(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS);
+ stop();
}
- public void initialize(final int cursorStart, final int cursorEnd, final String string,
+ public void start(final int cursorStart, final int cursorEnd, final String string,
final Locale locale, final int[] sortedSeparators) {
+ if (!mIsEnabled) {
+ return;
+ }
mCursorStartBefore = cursorStart;
mStringBefore = string;
mCursorStartAfter = cursorStart;
@@ -96,15 +100,27 @@ public class RecapitalizeStatus {
mRotationStyleCurrentIndex = currentMode;
mSkipOriginalMixedCaseMode = true;
}
- mIsActive = true;
+ mIsStarted = true;
+ }
+
+ public void stop() {
+ mIsStarted = false;
+ }
+
+ public boolean isStarted() {
+ return mIsStarted;
+ }
+
+ public void enable() {
+ mIsEnabled = true;
}
- public void deactivate() {
- mIsActive = false;
+ public void disable() {
+ mIsEnabled = false;
}
- public boolean isActive() {
- return mIsActive;
+ public boolean mIsEnabled() {
+ return mIsEnabled;
}
public boolean isSetAt(final int cursorStart, final int cursorEnd) {
diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
index 49f4929b4..093c5a6c1 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -41,8 +41,7 @@ public final class ResourceUtils {
// This utility class is not publicly instantiable.
}
- private static final HashMap<String, String> sDeviceOverrideValueMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, String> sDeviceOverrideValueMap = new HashMap<>();
private static final String[] BUILD_KEYS_AND_VALUES = {
"HARDWARE", Build.HARDWARE,
@@ -54,8 +53,8 @@ public final class ResourceUtils {
private static final String sBuildKeyValuesDebugString;
static {
- sBuildKeyValues = CollectionUtils.newHashMap();
- final ArrayList<String> keyValuePairs = CollectionUtils.newArrayList();
+ sBuildKeyValues = new HashMap<>();
+ final ArrayList<String> keyValuePairs = new ArrayList<>();
final int keyCount = BUILD_KEYS_AND_VALUES.length / 2;
for (int i = 0; i < keyCount; i++) {
final int index = i * 2;
diff --git a/java/src/com/android/inputmethod/latin/utils/StatsUtils.java b/java/src/com/android/inputmethod/latin/utils/StatsUtils.java
index a059f877b..79c19d077 100644
--- a/java/src/com/android/inputmethod/latin/utils/StatsUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StatsUtils.java
@@ -17,37 +17,18 @@
package com.android.inputmethod.latin.utils;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.util.Log;
-
-import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.settings.SettingsValues;
public final class StatsUtils {
- private static final String TAG = StatsUtils.class.getSimpleName();
- private static final StatsUtils sInstance = new StatsUtils();
-
- public static void onCreateCompleted(final Context context) {
- sInstance.onCreateCompletedInternal(context);
+ public static void init(final Context context) {
}
- private void onCreateCompletedInternal(final Context context) {
- mContext = context;
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
- final Boolean usePersonalizedDict =
- prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true);
- Log.d(TAG, "onCreateCompleted. context: " + context.toString() + "usePersonalizedDict: "
- + usePersonalizedDict);
+ public static void onCreate(final SettingsValues settingsValues) {
}
- public static void onDestroy() {
- sInstance.onDestroyInternal();
+ public static void onLoadSettings(final SettingsValues settingsValues) {
}
- private void onDestroyInternal() {
- Log.d(TAG, "onDestroy. context: " + mContext.toString());
- mContext = null;
+ public static void onDestroy() {
}
-
- private Context mContext;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index 374badc19..e4237a7f2 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -28,7 +28,6 @@ import java.util.Arrays;
import java.util.Locale;
public final class StringUtils {
- private static final String TAG = StringUtils.class.getSimpleName();
public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
public static final int CAPITALIZE_FIRST = 1; // First only
public static final int CAPITALIZE_ALL = 2; // All caps
@@ -110,7 +109,7 @@ public final class StringUtils {
if (!containsInArray(text, elements)) {
return extraValues;
}
- final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
+ final ArrayList<String> result = new ArrayList<>(elements.length - 1);
for (final String element : elements) {
if (!text.equals(element)) {
result.add(element);
@@ -316,24 +315,6 @@ public final class StringUtils {
return true;
}
- /**
- * Returns true if all code points in text are whitespace, false otherwise. Empty is true.
- */
- // Interestingly enough, U+00A0 NO-BREAK SPACE and U+200B ZERO-WIDTH SPACE are not considered
- // whitespace, while EN SPACE, EM SPACE and IDEOGRAPHIC SPACES are.
- public static boolean containsOnlyWhitespace(final String text) {
- final int length = text.length();
- int i = 0;
- while (i < length) {
- final int codePoint = text.codePointAt(i);
- if (!Character.isWhitespace(codePoint)) {
- return false;
- }
- i += Character.charCount(codePoint);
- }
- return true;
- }
-
public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
final int[] sortedSeparators) {
boolean needsCapsNext = true;
@@ -538,6 +519,15 @@ public final class StringUtils {
? casedText.codePointAt(0) : CODE_UNSPECIFIED;
}
+ public static int getTrailingSingleQuotesCount(final CharSequence charSequence) {
+ final int lastIndex = charSequence.length() - 1;
+ int i = lastIndex;
+ while (i >= 0 && charSequence.charAt(i) == Constants.CODE_SINGLE_QUOTE) {
+ --i;
+ }
+ return lastIndex - i;
+ }
+
@UsedForTesting
public static class Stringizer<E> {
public String stringize(final E element) {
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index b37779bdc..351d01400 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -49,17 +49,14 @@ public final class SubtypeLocaleUtils {
private static Resources sResources;
private static String[] sPredefinedKeyboardLayoutSet;
// Keyboard layout to its display name map.
- private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>();
// Keyboard layout to subtype name resource id map.
- private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>();
// Exceptional locale to subtype name resource id map.
- private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>();
// Exceptional locale to subtype name with layout resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
- CollectionUtils.newHashMap();
+ new HashMap<>();
private static final String SUBTYPE_NAME_RESOURCE_PREFIX =
"string/subtype_";
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
@@ -71,7 +68,7 @@ public final class SubtypeLocaleUtils {
// Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
// This is for compatibility to keep the same subtype ids as pre-JellyBean.
private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
- CollectionUtils.newHashMap();
+ new HashMap<>();
private SubtypeLocaleUtils() {
// Intentional empty constructor for utility class.
@@ -324,4 +321,8 @@ public final class SubtypeLocaleUtils {
public static boolean isRtlLanguage(final InputMethodSubtype subtype) {
return isRtlLanguage(getSubtypeLocale(subtype));
}
+
+ public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) {
+ return subtype.getExtraValueOf(Constants.Subtype.ExtraValue.COMBINING_RULES);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
index 42ea3c959..ab2b00e36 100644
--- a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
+++ b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
@@ -27,8 +27,7 @@ import com.android.inputmethod.compat.AppWorkaroundsUtils;
public final class TargetPackageInfoGetterTask extends
AsyncTask<String, Void, PackageInfo> {
private static final int MAX_CACHE_ENTRIES = 64; // arbitrary
- private static final LruCache<String, PackageInfo> sCache =
- new LruCache<String, PackageInfo>(MAX_CACHE_ENTRIES);
+ private static final LruCache<String, PackageInfo> sCache = new LruCache<>(MAX_CACHE_ENTRIES);
public static PackageInfo getCachedPackageInfo(final String packageName) {
if (null == packageName) return null;
diff --git a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index 087a7f255..fafba79c2 100644
--- a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -30,7 +30,7 @@ public final class TypefaceUtils {
}
// This sparse array caches key label text height in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
+ private static final SparseArray<Float> sTextHeightCache = new SparseArray<>();
// Working variable for the following method.
private static final Rect sTextHeightBounds = new Rect();
@@ -50,7 +50,7 @@ public final class TypefaceUtils {
}
// This sparse array caches key label text width in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
+ private static final SparseArray<Float> sTextWidthCache = new SparseArray<>();
// Working variable for the following method.
private static final Rect sTextWidthBounds = new Rect();
diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
deleted file mode 100644
index 06826dac0..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2016 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.utils;
-
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.inputmethodservice.InputMethodService;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.inputmethod.latin.LatinImeLogger;
-
-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.Date;
-import java.util.Locale;
-
-public final class UsabilityStudyLogUtils {
- // TODO: remove code duplication with ResearchLog class
- private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
- private static final String FILENAME = "log.txt";
- private final Handler mLoggingHandler;
- private File mFile;
- private File mDirectory;
- private InputMethodService mIms;
- private PrintWriter mWriter;
- private final Date mDate;
- private final SimpleDateFormat mDateFormat;
-
- private UsabilityStudyLogUtils() {
- mDate = new Date();
- mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
-
- HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
- Process.THREAD_PRIORITY_BACKGROUND);
- handlerThread.start();
- mLoggingHandler = new Handler(handlerThread.getLooper());
- }
-
- // Initialization-on-demand holder
- private static final class OnDemandInitializationHolder {
- public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
- }
-
- public static UsabilityStudyLogUtils getInstance() {
- return OnDemandInitializationHolder.sInstance;
- }
-
- public void init(final InputMethodService ims) {
- mIms = ims;
- mDirectory = ims.getFilesDir();
- }
-
- private void createLogFileIfNotExist() {
- if ((mFile == null || !mFile.exists())
- && (mDirectory != null && mDirectory.exists())) {
- try {
- mWriter = getPrintWriter(mDirectory, FILENAME, false);
- } catch (final IOException e) {
- Log.e(USABILITY_TAG, "Can't create log file.");
- }
- }
- }
-
- public static void writeBackSpace(final int x, final int y) {
- UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
- }
-
- public static void writeChar(final char c, final int x, final int y) {
- String inputChar = String.valueOf(c);
- switch (c) {
- case '\n':
- inputChar = "<enter>";
- break;
- case '\t':
- inputChar = "<tab>";
- break;
- case ' ':
- inputChar = "<space>";
- break;
- }
- UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
- LatinImeLogger.onPrintAllUsabilityStudyLogs();
- }
-
- public static void writeMotionEvent(final MotionEvent me) {
- final int action = me.getActionMasked();
- final long eventTime = me.getEventTime();
- final int pointerCount = me.getPointerCount();
- for (int index = 0; index < pointerCount; index++) {
- final int id = me.getPointerId(index);
- final int x = (int)me.getX(index);
- final int y = (int)me.getY(index);
- final float size = me.getSize(index);
- final float pressure = me.getPressure(index);
-
- final String eventTag;
- switch (action) {
- case MotionEvent.ACTION_UP:
- eventTag = "[Up]";
- break;
- case MotionEvent.ACTION_DOWN:
- eventTag = "[Down]";
- break;
- case MotionEvent.ACTION_POINTER_UP:
- eventTag = "[PointerUp]";
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- eventTag = "[PointerDown]";
- break;
- case MotionEvent.ACTION_MOVE:
- eventTag = "[Move]";
- break;
- default:
- eventTag = "[Action" + action + "]";
- break;
- }
- getInstance().write(eventTag + eventTime + "," + id + "," + x + "," + y + "," + size
- + "," + pressure);
- }
- }
-
- public void write(final String log) {
- mLoggingHandler.post(new Runnable() {
- @Override
- public void run() {
- createLogFileIfNotExist();
- final long currentTime = System.currentTimeMillis();
- mDate.setTime(currentTime);
-
- final String printString = String.format(Locale.US, "%s\t%d\t%s\n",
- mDateFormat.format(mDate), currentTime, log);
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "Write: " + log);
- }
- mWriter.print(printString);
- }
- });
- }
-
- private synchronized String getBufferedLogs() {
- mWriter.flush();
- final StringBuilder sb = new StringBuilder();
- final BufferedReader br = getBufferedReader();
- String line;
- try {
- while ((line = br.readLine()) != null) {
- sb.append('\n');
- sb.append(line);
- }
- } catch (final 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 (final 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", Locale.US).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();
- final String destPath = Environment.getExternalStorageDirectory()
- + "/research-" + currentDateTimeString + ".log";
- final File destFile = new File(destPath);
- try {
- final FileInputStream srcStream = new FileInputStream(mFile);
- final FileOutputStream destStream = new FileOutputStream(destFile);
- final FileChannel src = srcStream.getChannel();
- final FileChannel dest = destStream.getChannel();
- src.transferTo(0, src.size(), dest);
- src.close();
- srcStream.close();
- dest.close();
- destStream.close();
- } catch (final FileNotFoundException e1) {
- Log.w(USABILITY_TAG, e1);
- return;
- } catch (final IOException e2) {
- Log.w(USABILITY_TAG, e2);
- return;
- }
- if (!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);
- }
- });
- }
-
- public void clearAll() {
- mLoggingHandler.post(new Runnable() {
- @Override
- public void run() {
- if (mFile != null && mFile.exists()) {
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "Delete log file.");
- }
- mFile.delete();
- mWriter.close();
- }
- }
- });
- }
-
- private BufferedReader getBufferedReader() {
- createLogFileIfNotExist();
- try {
- return new BufferedReader(new FileReader(mFile));
- } catch (final FileNotFoundException e) {
- return null;
- }
- }
-
- private PrintWriter getPrintWriter(final File dir, final String filename,
- final boolean renew) throws IOException {
- mFile = new File(dir, filename);
- if (mFile.exists()) {
- if (renew) {
- mFile.delete();
- }
- }
- return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
deleted file mode 100644
index a75d353c9..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.inputmethodservice.InputMethodService;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.settings.Settings;
-
-public final class UserLogRingCharBuffer {
- public /* for test */ static final int BUFSIZE = 20;
- public /* for test */ int mLength = 0;
-
- private static UserLogRingCharBuffer sUserLogRingCharBuffer = new UserLogRingCharBuffer();
- private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
- private static final int INVALID_COORDINATE = -2;
- private boolean mEnabled = false;
- private int mEnd = 0;
- private char[] mCharBuf = new char[BUFSIZE];
- private int[] mXBuf = new int[BUFSIZE];
- private int[] mYBuf = new int[BUFSIZE];
-
- private UserLogRingCharBuffer() {
- // Intentional empty constructor for singleton.
- }
-
- @UsedForTesting
- public static UserLogRingCharBuffer getInstance() {
- return sUserLogRingCharBuffer;
- }
-
- public static UserLogRingCharBuffer init(final InputMethodService context,
- final boolean enabled, final boolean usabilityStudy) {
- if (!(enabled || usabilityStudy)) {
- return null;
- }
- sUserLogRingCharBuffer.mEnabled = true;
- UsabilityStudyLogUtils.getInstance().init(context);
- return sUserLogRingCharBuffer;
- }
-
- private static int normalize(final int in) {
- int ret = in % BUFSIZE;
- return ret < 0 ? ret + BUFSIZE : ret;
- }
-
- // TODO: accept code points
- @UsedForTesting
- public void push(final char c, final int x, final int y) {
- if (!mEnabled) {
- return;
- }
- if (LatinImeLogger.sUsabilityStudy) {
- UsabilityStudyLogUtils.getInstance().writeChar(c, x, y);
- }
- mCharBuf[mEnd] = c;
- mXBuf[mEnd] = x;
- mYBuf[mEnd] = y;
- mEnd = normalize(mEnd + 1);
- if (mLength < BUFSIZE) {
- ++mLength;
- }
- }
-
- public char pop() {
- if (mLength < 1) {
- return PLACEHOLDER_DELIMITER_CHAR;
- }
- mEnd = normalize(mEnd - 1);
- --mLength;
- return mCharBuf[mEnd];
- }
-
- public char getBackwardNthChar(final int n) {
- if (mLength <= n || n < 0) {
- return PLACEHOLDER_DELIMITER_CHAR;
- }
- return mCharBuf[normalize(mEnd - n - 1)];
- }
-
- public int getPreviousX(final char c, final int back) {
- final int index = normalize(mEnd - 2 - back);
- if (mLength <= back
- || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
- return INVALID_COORDINATE;
- }
- return mXBuf[index];
- }
-
- public int getPreviousY(final char c, final int back) {
- int index = normalize(mEnd - 2 - back);
- if (mLength <= back
- || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
- return INVALID_COORDINATE;
- }
- return mYBuf[index];
- }
-
- public String getLastWord(final int ignoreCharCount) {
- final StringBuilder sb = new StringBuilder();
- int i = ignoreCharCount;
- for (; i < mLength; ++i) {
- final char c = mCharBuf[normalize(mEnd - 1 - i)];
- if (!Settings.getInstance().isWordSeparator(c)) {
- break;
- }
- }
- for (; i < mLength; ++i) {
- char c = mCharBuf[normalize(mEnd - 1 - i)];
- if (!Settings.getInstance().isWordSeparator(c)) {
- sb.append(c);
- } else {
- break;
- }
- }
- return sb.reverse().toString();
- }
-
- public void reset() {
- mLength = 0;
- }
-}