aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/utils
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin/utils')
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java13
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java50
-rw-r--r--java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java16
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java51
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CollectionUtils.java43
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java44
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java91
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java157
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java58
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java286
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java59
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java142
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FileUtils.java54
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FragmentUtils.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java14
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java187
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java92
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java11
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LocaleUtils.java190
-rw-r--r--java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java (renamed from java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java)50
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java13
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java155
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResourceUtils.java12
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ScriptUtils.java67
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java58
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java57
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java646
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java145
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SuggestionResults.java22
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java106
33 files changed, 801 insertions, 2138 deletions
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index db7f2a56c..2aac7c57a 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -16,12 +16,12 @@
package com.android.inputmethod.latin.utils;
-import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE;
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.ASCII_CAPABLE;
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE;
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
+import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.ASCII_CAPABLE;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
import android.os.Build;
import android.text.TextUtils;
@@ -31,6 +31,7 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
index d12aad639..952ac2a62 100644
--- a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
+++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
@@ -59,11 +59,7 @@ public class AsyncResultHolder<E> {
*/
public E get(final E defaultValue, final long timeOut) {
try {
- if (mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
- return mResult;
- } else {
- return defaultValue;
- }
+ return mLatch.await(timeOut, TimeUnit.MILLISECONDS) ? mResult : defaultValue;
} catch (InterruptedException e) {
return defaultValue;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index 156fcf57c..c9ecade91 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -24,20 +24,22 @@ import com.android.inputmethod.latin.define.DebugFlags;
public final class AutoCorrectionUtils {
private static final boolean DBG = DebugFlags.DEBUG_ENABLED;
private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
- private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
private AutoCorrectionUtils() {
// Purely static class: can't instantiate.
}
- public static boolean suggestionExceedsAutoCorrectionThreshold(
- final SuggestedWordInfo suggestion, final String consideredWord,
- final float autoCorrectionThreshold) {
+ public static boolean suggestionExceedsThreshold(final SuggestedWordInfo suggestion,
+ final String consideredWord, final float threshold) {
if (null != suggestion) {
// Shortlist a whitelisted word
if (suggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
return true;
}
+ // TODO: return suggestion.isAprapreateForAutoCorrection();
+ if (!suggestion.isAprapreateForAutoCorrection()) {
+ return false;
+ }
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.
@@ -46,47 +48,15 @@ public final class AutoCorrectionUtils {
if (DBG) {
Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + ","
+ autoCorrectionSuggestionScore + ", " + normalizedScore
- + "(" + autoCorrectionThreshold + ")");
+ + "(" + threshold + ")");
}
- if (normalizedScore >= autoCorrectionThreshold) {
+ if (normalizedScore >= threshold) {
if (DBG) {
- Log.d(TAG, "Auto corrected by S-threshold.");
+ Log.d(TAG, "Exceeds threshold.");
}
- return !shouldBlockAutoCorrectionBySafetyNet(consideredWord, suggestion.mWord);
+ return true;
}
}
return false;
}
-
- // TODO: Resolve the inconsistencies between the native auto correction algorithms and
- // this safety net
- public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord,
- final String suggestion) {
- // Safety net for auto correction.
- // Actually if we hit this safety net, it's a bug.
- // If user selected aggressive auto correction mode, there is no need to use the safety
- // net.
- // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
- // we should not use net because relatively edit distance can be big.
- final int typedWordLength = typedWord.length();
- if (typedWordLength < MINIMUM_SAFETY_NET_CHAR_LENGTH) {
- return false;
- }
- final int maxEditDistanceOfNativeDictionary = (typedWordLength / 2) + 1;
- final int distance = BinaryDictionaryUtils.editDistance(typedWord, suggestion);
- if (DBG) {
- Log.d(TAG, "Autocorrected edit distance = " + distance
- + ", " + maxEditDistanceOfNativeDictionary);
- }
- if (distance > maxEditDistanceOfNativeDictionary) {
- if (DBG) {
- Log.e(TAG, "Safety net: before = " + typedWord + ", after = " + suggestion);
- Log.e(TAG, "(Error) The edit distance of this correction exceeds limit. "
- + "Turning off auto-correction.");
- }
- return true;
- } else {
- return false;
- }
- }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
index 5d7deba15..3bf9c6200 100644
--- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
@@ -18,9 +18,9 @@ package com.android.inputmethod.latin.utils;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.personalization.PersonalizationHelper;
import java.io.File;
import java.io.IOException;
@@ -40,10 +40,10 @@ public final class BinaryDictionaryUtils {
JniUtils.loadNativeLibrary();
}
+ @UsedForTesting
private static native boolean createEmptyDictFileNative(String filePath, long dictVersion,
String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray);
private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
- private static native int editDistanceNative(int[] before, int[] after);
private static native int setCurrentTimeForTestNative(int currentTime);
public static DictionaryHeader getHeader(final File dictFile)
@@ -112,14 +112,6 @@ public final class BinaryDictionaryUtils {
StringUtils.toCodePointArray(after), score);
}
- public static int editDistance(final String before, final String after) {
- if (before == null || after == null) {
- throw new IllegalArgumentException();
- }
- return editDistanceNative(StringUtils.toCodePointArray(before),
- StringUtils.toCodePointArray(after));
- }
-
/**
* Control the current time to be used in the native code. If currentTime >= 0, this method sets
* the current time and gets into test mode.
@@ -131,8 +123,6 @@ public final class BinaryDictionaryUtils {
*/
@UsedForTesting
public static int setCurrentTimeForTest(final int currentTime) {
- final int currentNativeTimestamp = setCurrentTimeForTestNative(currentTime);
- PersonalizationHelper.currentTimeChangedForTesting(currentNativeTimestamp);
- return currentNativeTimestamp;
+ return setCurrentTimeForTestNative(currentTime);
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 936219332..0dbc7c858 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -19,10 +19,12 @@ package com.android.inputmethod.latin.utils;
import android.text.InputType;
import android.text.TextUtils;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import java.util.ArrayList;
import java.util.Locale;
public final class CapsModeUtils {
@@ -213,12 +215,22 @@ public final class CapsModeUtils {
char c = cs.charAt(--j);
// We found the next interesting chunk of text ; next we need to determine if it's the
- // end of a sentence. If we have a question mark or an exclamation mark, it's the end of
- // a sentence. If it's neither, the only remaining case is the period so we get the opposite
- // case out of the way.
- if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
- return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
+ // end of a sentence. If we have a sentence terminator (typically a question mark or an
+ // exclamation mark), then it's the end of a sentence; however, we treat the abbreviation
+ // marker specially because usually is the same char as the sentence separator (the
+ // period in most languages) and in this case we need to apply a heuristic to determine
+ // in which of these senses it's used.
+ if (spacingAndPunctuations.isSentenceTerminator(c)
+ && !spacingAndPunctuations.isAbbreviationMarker(c)) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES) & reqModes;
}
+ // If we reach here, we know we have whitespace before the cursor and before that there
+ // is something that either does not terminate the sentence, or a symbol preceded by the
+ // start of the text, or it's the sentence separator AND it happens to be the same code
+ // point as the abbreviation marker.
+ // If it's a symbol or something that does not terminate the sentence, then we need to
+ // return caps for MODE_CHARACTERS and MODE_WORDS, but not for MODE_SENTENCES.
if (!spacingAndPunctuations.isSentenceSeparator(c) || j <= 0) {
return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
}
@@ -315,4 +327,31 @@ public final class CapsModeUtils {
// Here we arrived at the start of the line. This should behave exactly like whitespace.
return (START == state || LETTER == state) ? noCaps : caps;
}
+
+ /**
+ * Convert capitalize mode flags into human readable text.
+ *
+ * @param capsFlags The modes flags to be converted. It may be any combination of
+ * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
+ * {@link TextUtils#CAP_MODE_SENTENCES}.
+ * @return the text that describe the <code>capsMode</code>.
+ */
+ public static String flagsToString(final int capsFlags) {
+ final int capsFlagsMask = TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
+ | TextUtils.CAP_MODE_SENTENCES;
+ if ((capsFlags & ~capsFlagsMask) != 0) {
+ return "unknown<0x" + Integer.toHexString(capsFlags) + ">";
+ }
+ final ArrayList<String> builder = new ArrayList<>();
+ if ((capsFlags & android.text.TextUtils.CAP_MODE_CHARACTERS) != 0) {
+ builder.add("characters");
+ }
+ if ((capsFlags & android.text.TextUtils.CAP_MODE_WORDS) != 0) {
+ builder.add("words");
+ }
+ if ((capsFlags & android.text.TextUtils.CAP_MODE_SENTENCES) != 0) {
+ builder.add("sentences");
+ }
+ return builder.isEmpty() ? "none" : TextUtils.join("|", builder);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
deleted file mode 100644
index 61292fc36..000000000
--- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
-
-public final class CollectionUtils {
- private CollectionUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static <E> ArrayList<E> arrayAsList(final E[] array, final int start, final int end) {
- if (array == null) {
- throw new NullPointerException();
- }
- if (start < 0 || start > end || end > array.length) {
- throw new IllegalArgumentException();
- }
-
- final ArrayList<E> list = new ArrayList<>(end - start);
- for (int i = start; i < end; i++) {
- list.add(array[i]);
- }
- return list;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
index 34f59e8bc..5c0c4328f 100644
--- a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
@@ -17,8 +17,8 @@
package com.android.inputmethod.latin.utils;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.NgramProperty;
import com.android.inputmethod.latin.makedict.ProbabilityInfo;
-import com.android.inputmethod.latin.makedict.WeightedString;
import com.android.inputmethod.latin.makedict.WordProperty;
import java.util.HashMap;
@@ -26,14 +26,16 @@ import java.util.HashMap;
public class CombinedFormatUtils {
public static final String DICTIONARY_TAG = "dictionary";
public static final String BIGRAM_TAG = "bigram";
- public static final String SHORTCUT_TAG = "shortcut";
+ public static final String NGRAM_TAG = "ngram";
+ public static final String NGRAM_PREV_WORD_TAG = "prev_word";
public static final String PROBABILITY_TAG = "f";
public static final String HISTORICAL_INFO_TAG = "historicalInfo";
public static final String HISTORICAL_INFO_SEPARATOR = ":";
public static final String WORD_TAG = "word";
public static final String BEGINNING_OF_SENTENCE_TAG = "beginning_of_sentence";
public static final String NOT_A_WORD_TAG = "not_a_word";
- public static final String BLACKLISTED_TAG = "blacklisted";
+ public static final String POSSIBLY_OFFENSIVE_TAG = "possibly_offensive";
+ public static final String TRUE_VALUE = "true";
public static String formatAttributeMap(final HashMap<String, String> attributeMap) {
final StringBuilder builder = new StringBuilder();
@@ -58,29 +60,29 @@ public class CombinedFormatUtils {
builder.append(",");
builder.append(formatProbabilityInfo(wordProperty.mProbabilityInfo));
if (wordProperty.mIsBeginningOfSentence) {
- builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=true");
+ builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=" + TRUE_VALUE);
}
if (wordProperty.mIsNotAWord) {
- builder.append("," + NOT_A_WORD_TAG + "=true");
+ builder.append("," + NOT_A_WORD_TAG + "=" + TRUE_VALUE);
}
- if (wordProperty.mIsBlacklistEntry) {
- builder.append("," + BLACKLISTED_TAG + "=true");
+ if (wordProperty.mIsPossiblyOffensive) {
+ builder.append("," + POSSIBLY_OFFENSIVE_TAG + "=" + TRUE_VALUE);
}
builder.append("\n");
- if (wordProperty.mShortcutTargets != null) {
- for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
- builder.append(" " + SHORTCUT_TAG + "=" + shortcutTarget.mWord);
+ if (wordProperty.mHasNgrams) {
+ for (final NgramProperty ngramProperty : wordProperty.mNgrams) {
+ builder.append(" " + NGRAM_TAG + "=" + ngramProperty.mTargetWord.mWord);
builder.append(",");
- builder.append(formatProbabilityInfo(shortcutTarget.mProbabilityInfo));
- builder.append("\n");
- }
- }
- if (wordProperty.mBigrams != null) {
- for (final WeightedString bigram : wordProperty.mBigrams) {
- builder.append(" " + BIGRAM_TAG + "=" + bigram.mWord);
- builder.append(",");
- builder.append(formatProbabilityInfo(bigram.mProbabilityInfo));
+ builder.append(formatProbabilityInfo(ngramProperty.mTargetWord.mProbabilityInfo));
builder.append("\n");
+ for (int i = 0; i < ngramProperty.mNgramContext.getPrevWordCount(); i++) {
+ builder.append(" " + NGRAM_PREV_WORD_TAG + "[" + i + "]="
+ + ngramProperty.mNgramContext.getNthPrevWord(i + 1));
+ if (ngramProperty.mNgramContext.isNthPrevWordBeginningOfSentence(i + 1)) {
+ builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=true");
+ }
+ builder.append("\n");
+ }
}
}
return builder.toString();
@@ -100,4 +102,8 @@ public class CombinedFormatUtils {
}
return builder.toString();
}
+
+ public static boolean isLiteralTrue(final String value) {
+ return TRUE_VALUE.equalsIgnoreCase(value);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
deleted file mode 100644
index 87df013a6..000000000
--- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import java.util.Arrays;
-
-public final class CoordinateUtils {
- private static final int INDEX_X = 0;
- private static final int INDEX_Y = 1;
- private static final int ELEMENT_SIZE = INDEX_Y + 1;
-
- private CoordinateUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static int[] newInstance() {
- return new int[ELEMENT_SIZE];
- }
-
- public static int x(final int[] coords) {
- return coords[INDEX_X];
- }
-
- public static int y(final int[] coords) {
- return coords[INDEX_Y];
- }
-
- public static void set(final int[] coords, final int x, final int y) {
- coords[INDEX_X] = x;
- coords[INDEX_Y] = y;
- }
-
- public static void copy(final int[] destination, final int[] source) {
- destination[INDEX_X] = source[INDEX_X];
- destination[INDEX_Y] = source[INDEX_Y];
- }
-
- public static int[] newCoordinateArray(final int arraySize) {
- return new int[ELEMENT_SIZE * arraySize];
- }
-
- public static int[] newCoordinateArray(final int arraySize,
- final int defaultX, final int defaultY) {
- final int[] result = new int[ELEMENT_SIZE * arraySize];
- for (int i = 0; i < arraySize; ++i) {
- setXYInArray(result, i, defaultX, defaultY);
- }
- return result;
- }
-
- public static int xFromArray(final int[] coordsArray, final int index) {
- return coordsArray[ELEMENT_SIZE * index + INDEX_X];
- }
-
- public static int yFromArray(final int[] coordsArray, final int index) {
- return coordsArray[ELEMENT_SIZE * index + INDEX_Y];
- }
-
- public static int[] coordinateFromArray(final int[] coordsArray, final int index) {
- final int baseIndex = ELEMENT_SIZE * index;
- return Arrays.copyOfRange(coordsArray, baseIndex, baseIndex + ELEMENT_SIZE);
- }
-
- public static void setXYInArray(final int[] coordsArray, final int index,
- final int x, final int y) {
- final int baseIndex = ELEMENT_SIZE * index;
- coordsArray[baseIndex + INDEX_X] = x;
- coordsArray[baseIndex + INDEX_Y] = y;
- }
-
- public static void setCoordinateInArray(final int[] coordsArray, final int index,
- final int[] coords) {
- final int baseIndex = ELEMENT_SIZE * index;
- coordsArray[baseIndex + INDEX_X] = coords[INDEX_X];
- coordsArray[baseIndex + INDEX_Y] = coords[INDEX_Y];
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
index 9dc0524a2..c90d30c42 100644
--- a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java
@@ -16,17 +16,26 @@
package com.android.inputmethod.latin.utils;
+import android.annotation.TargetApi;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.inputmethodservice.ExtractEditText;
import android.inputmethodservice.InputMethodService;
+import android.os.Build;
import android.text.Layout;
import android.text.Spannable;
+import android.text.Spanned;
import android.view.View;
import android.view.ViewParent;
import android.view.inputmethod.CursorAnchorInfo;
import android.widget.TextView;
+import com.android.inputmethod.compat.BuildCompatUtils;
+import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* This class allows input methods to extract {@link CursorAnchorInfo} directly from the given
* {@link TextView}. This is useful and even necessary to support full-screen mode where the default
@@ -77,13 +86,32 @@ public final class CursorAnchorInfoUtils {
}
/**
+ * Extracts {@link CursorAnchorInfoCompatWrapper} from the given {@link TextView}.
+ * @param textView the target text view from which {@link CursorAnchorInfoCompatWrapper} is to
+ * be extracted.
+ * @return the {@link CursorAnchorInfoCompatWrapper} object based on the current layout.
+ * {@code null} if {@code Build.VERSION.SDK_INT} is 20 or prior or {@link TextView} is not
+ * ready to provide layout information.
+ */
+ @Nullable
+ public static CursorAnchorInfoCompatWrapper extractFromTextView(
+ @Nonnull final TextView textView) {
+ if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ return null;
+ }
+ return CursorAnchorInfoCompatWrapper.wrap(extractFromTextViewInternal(textView));
+ }
+
+ /**
* Returns {@link CursorAnchorInfo} from the given {@link TextView}.
* @param textView the target text view from which {@link CursorAnchorInfo} is to be extracted.
* @return the {@link CursorAnchorInfo} object based on the current layout. {@code null} if it
* is not feasible.
*/
- public static CursorAnchorInfo getCursorAnchorInfo(final TextView textView) {
- Layout layout = textView.getLayout();
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ @Nullable
+ private static CursorAnchorInfo extractFromTextViewInternal(@Nonnull final TextView textView) {
+ final Layout layout = textView.getLayout();
if (layout == null) {
return null;
}
@@ -122,7 +150,7 @@ public final class CursorAnchorInfoUtils {
final Object[] spans = spannable.getSpans(0, text.length(), Object.class);
for (Object span : spans) {
final int spanFlag = spannable.getSpanFlags(span);
- if ((spanFlag & Spannable.SPAN_COMPOSING) != 0) {
+ if ((spanFlag & Spanned.SPAN_COMPOSING) != 0) {
composingTextStart = Math.min(composingTextStart,
spannable.getSpanStart(span));
composingTextEnd = Math.max(composingTextEnd, spannable.getSpanEnd(span));
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 197908032..25fa723cc 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -22,11 +22,15 @@ import android.content.res.AssetManager;
import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;
+import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.AssetFileAddress;
import com.android.inputmethod.latin.BinaryDictionaryGetter;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
@@ -35,9 +39,13 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
/**
* This class encapsulates the logic for the Latin-IME side of dictionary information management.
*/
@@ -46,6 +54,7 @@ public class DictionaryInfoUtils {
private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
private static final String DEFAULT_MAIN_DICT = "main";
private static final String MAIN_DICT_PREFIX = "main_";
+ private static final String DECODER_DICT_SUFFIX = DecoderSpecificConstants.DECODER_DICT_SUFFIX;
// 6 digits - unicode is limited to 21 bits
private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
@@ -57,28 +66,36 @@ public class DictionaryInfoUtils {
private static final String DATE_COLUMN = "date";
private static final String FILESIZE_COLUMN = "filesize";
private static final String VERSION_COLUMN = "version";
+ @Nonnull
public final String mId;
+ @Nonnull
public final Locale mLocale;
+ @Nullable
public final String mDescription;
public final AssetFileAddress mFileAddress;
public final int mVersion;
- public DictionaryInfo(final String id, final Locale locale, final String description,
- final AssetFileAddress fileAddress, final int version) {
+
+ public DictionaryInfo(@Nonnull final String id, @Nonnull final Locale locale,
+ @Nullable final String description, @Nullable final AssetFileAddress fileAddress,
+ final int version) {
mId = id;
mLocale = locale;
mDescription = description;
mFileAddress = fileAddress;
mVersion = version;
}
+
public ContentValues toContentValues() {
final ContentValues values = new ContentValues();
values.put(WORDLISTID_COLUMN, mId);
values.put(LOCALE_COLUMN, mLocale.toString());
values.put(DESCRIPTION_COLUMN, mDescription);
- values.put(LOCAL_FILENAME_COLUMN, mFileAddress.mFilename);
+ values.put(LOCAL_FILENAME_COLUMN,
+ mFileAddress != null ? mFileAddress.mFilename : "");
values.put(DATE_COLUMN, TimeUnit.MILLISECONDS.toSeconds(
- new File(mFileAddress.mFilename).lastModified()));
- values.put(FILESIZE_COLUMN, mFileAddress.mLength);
+ mFileAddress != null ? new File(mFileAddress.mFilename).lastModified() : 0));
+ values.put(FILESIZE_COLUMN,
+ mFileAddress != null ? mFileAddress.mLength : 0);
values.put(VERSION_COLUMN, mVersion);
return values;
}
@@ -140,9 +157,10 @@ public class DictionaryInfoUtils {
}
/**
- * Reverse escaping done by replaceFileNameDangerousCharacters.
+ * Reverse escaping done by {@link #replaceFileNameDangerousCharacters(String)}.
*/
- public static String getWordListIdFromFileName(final String fname) {
+ @Nonnull
+ public static String getWordListIdFromFileName(@Nonnull final String fname) {
final StringBuilder sb = new StringBuilder();
final int fnameLength = fname.length();
for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) {
@@ -174,12 +192,15 @@ public class DictionaryInfoUtils {
* {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}.
* @return The category as a string or null if it can't be found in the file name.
*/
- public static String getCategoryFromFileName(final String fileName) {
+ @Nullable
+ public static String getCategoryFromFileName(@Nonnull final String fileName) {
final String id = getWordListIdFromFileName(fileName);
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
// An id is supposed to be in format category:locale, so splitting on the separator
// should yield a 2-elements array
- if (2 != idArray.length) return null;
+ if (2 != idArray.length) {
+ return null;
+ }
return idArray[0];
}
@@ -223,12 +244,26 @@ public class DictionaryInfoUtils {
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
// An id is supposed to be in format category:locale, so splitting on the separator
// should yield a 2-elements array
- if (2 != idArray.length) return false;
+ if (2 != idArray.length) {
+ return false;
+ }
return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
}
/**
+ * Find out whether a dictionary is available for this locale.
+ * @param context the context on which to check resources.
+ * @param locale the locale to check for.
+ * @return whether a (non-placeholder) dictionary is available or not.
+ */
+ public static boolean isDictionaryAvailable(final Context context, final Locale locale) {
+ final Resources res = context.getResources();
+ return 0 != getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
+ }
+
+ /**
* Helper method to return a dictionary res id for a locale, or 0 if none.
+ * @param res resources for the app
* @param locale dictionary locale
* @return main dictionary resource id
*/
@@ -237,8 +272,8 @@ public class DictionaryInfoUtils {
int resId;
// Try to find main_language_country dictionary.
if (!locale.getCountry().isEmpty()) {
- final String dictLanguageCountry =
- MAIN_DICT_PREFIX + locale.toString().toLowerCase(Locale.ROOT);
+ final String dictLanguageCountry = MAIN_DICT_PREFIX
+ + locale.toString().toLowerCase(Locale.ROOT) + DECODER_DICT_SUFFIX;
if ((resId = res.getIdentifier(
dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
return resId;
@@ -246,7 +281,7 @@ public class DictionaryInfoUtils {
}
// Try to find main_language dictionary.
- final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage();
+ final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage() + DECODER_DICT_SUFFIX;
if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
return resId;
}
@@ -257,13 +292,17 @@ public class DictionaryInfoUtils {
/**
* Returns a main dictionary resource id
+ * @param res resources for the app
* @param locale dictionary locale
* @return main dictionary resource id
*/
public static int getMainDictionaryResourceId(final Resources res, final Locale locale) {
int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
- if (0 != resourceId) return resourceId;
- return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", RESOURCE_PACKAGE_NAME);
+ if (0 != resourceId) {
+ return resourceId;
+ }
+ return res.getIdentifier(DEFAULT_MAIN_DICT + DecoderSpecificConstants.DECODER_DICT_SUFFIX,
+ "raw", RESOURCE_PACKAGE_NAME);
}
/**
@@ -274,19 +313,15 @@ public class DictionaryInfoUtils {
* unique ID to them. This ID is just the name of the language (locale-wise) they
* are for, and this method returns this ID.
*/
- public static String getMainDictId(final Locale locale) {
+ public static String getMainDictId(@Nonnull final Locale locale) {
// This works because we don't include by default different dictionaries for
// different countries. This actually needs to return the id that we would
// like to use for word lists included in resources, and the following is okay.
return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY +
- BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.getLanguage().toString();
+ BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.toString().toLowerCase();
}
- public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file) {
- return getDictionaryFileHeaderOrNull(file, 0, file.length());
- }
-
- private static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
+ public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
final long offset, final long length) {
try {
final DictionaryHeader header =
@@ -303,20 +338,27 @@ public class DictionaryInfoUtils {
* Returns information of the dictionary.
*
* @param fileAddress the asset dictionary file address.
+ * @param locale Locale for this file.
* @return information of the specified dictionary.
*/
private static DictionaryInfo createDictionaryInfoFromFileAddress(
- final AssetFileAddress fileAddress) {
- final DictionaryHeader header = getDictionaryFileHeaderOrNull(
- new File(fileAddress.mFilename), fileAddress.mOffset, fileAddress.mLength);
- if (header == null) {
- return null;
- }
- final String id = header.getId();
- final Locale locale = LocaleUtils.constructLocaleFromString(header.getLocaleString());
- final String description = header.getDescription();
- final String version = header.getVersion();
- return new DictionaryInfo(id, locale, description, fileAddress, Integer.parseInt(version));
+ final AssetFileAddress fileAddress, Locale locale) {
+ final String id = getMainDictId(locale);
+ final int version = DictionaryHeaderUtils.getContentVersion(fileAddress);
+ final String description = SubtypeLocaleUtils
+ .getSubtypeLocaleDisplayName(locale.toString());
+ return new DictionaryInfo(id, locale, description, fileAddress, version);
+ }
+
+ /**
+ * Returns dictionary information for the given locale.
+ */
+ private static DictionaryInfo createDictionaryInfoFromLocale(Locale locale) {
+ final String id = getMainDictId(locale);
+ final int version = -1;
+ final String description = SubtypeLocaleUtils
+ .getSubtypeLocaleDisplayName(locale.toString());
+ return new DictionaryInfo(id, locale, description, null, version);
}
private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList,
@@ -343,18 +385,23 @@ public class DictionaryInfoUtils {
if (null != directoryList) {
for (final File directory : directoryList) {
final String localeString = getWordListIdFromFileName(directory.getName());
- File[] dicts = BinaryDictionaryGetter.getCachedWordLists(localeString, context);
+ final File[] dicts = BinaryDictionaryGetter.getCachedWordLists(
+ localeString, context);
for (final File dict : dicts) {
final String wordListId = getWordListIdFromFileName(dict.getName());
- if (!DictionaryInfoUtils.isMainWordListId(wordListId)) continue;
+ if (!DictionaryInfoUtils.isMainWordListId(wordListId)) {
+ continue;
+ }
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict);
final DictionaryInfo dictionaryInfo =
- createDictionaryInfoFromFileAddress(fileAddress);
+ createDictionaryInfoFromFileAddress(fileAddress, locale);
// Protect against cases of a less-specific dictionary being found, like an
// en dictionary being used for an en_US locale. In this case, the en dictionary
// should be used for en_US but discounted for listing purposes.
- if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) continue;
+ if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) {
+ continue;
+ }
addOrUpdateDictInfo(dictList, dictionaryInfo);
}
}
@@ -368,25 +415,45 @@ public class DictionaryInfoUtils {
final int resourceId =
DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
context.getResources(), locale);
- if (0 == resourceId) continue;
+ if (0 == resourceId) {
+ continue;
+ }
final AssetFileAddress fileAddress =
BinaryDictionaryGetter.loadFallbackResource(context, resourceId);
- final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress);
+ final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress,
+ locale);
// Protect against cases of a less-specific dictionary being found, like an
// en dictionary being used for an en_US locale. In this case, the en dictionary
// should be used for en_US but discounted for listing purposes.
- if (!dictionaryInfo.mLocale.equals(locale)) continue;
+ // TODO: Remove dictionaryInfo == null when the static LMs have the headers.
+ if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) {
+ continue;
+ }
+ addOrUpdateDictInfo(dictList, dictionaryInfo);
+ }
+
+ // Generate the dictionary information from the enabled subtypes. This will not
+ // overwrite the real records.
+ RichInputMethodManager.init(context);
+ List<InputMethodSubtype> enabledSubtypes = RichInputMethodManager
+ .getInstance().getMyEnabledInputMethodSubtypeList(true);
+ for (InputMethodSubtype subtype : enabledSubtypes) {
+ Locale locale = LocaleUtils.constructLocaleFromString(subtype.getLocale());
+ DictionaryInfo dictionaryInfo = createDictionaryInfoFromLocale(locale);
addOrUpdateDictInfo(dictList, dictionaryInfo);
}
return dictList;
}
+ @UsedForTesting
public static boolean looksValidForDictionaryInsertion(final CharSequence text,
final SpacingAndPunctuations spacingAndPunctuations) {
- if (TextUtils.isEmpty(text)) return false;
+ if (TextUtils.isEmpty(text)) {
+ return false;
+ }
final int length = text.length();
- if (length > Constants.DICTIONARY_MAX_WORD_LENGTH) {
+ if (length > DecoderSpecificConstants.DICTIONARY_MAX_WORD_LENGTH) {
return false;
}
int i = 0;
@@ -400,7 +467,9 @@ public class DictionaryInfoUtils {
digitCount += charCount;
continue;
}
- if (!spacingAndPunctuations.isWordCodePoint(codePoint)) return false;
+ if (!spacingAndPunctuations.isWordCodePoint(codePoint)) {
+ return false;
+ }
}
// We reject strings entirely comprised of digits to avoid using PIN codes or credit
// card numbers. It would come in handy for word prediction though; a good example is
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
deleted file mode 100644
index 787e4a59d..000000000
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.PrevWordsInfo;
-
-public interface DistracterFilter {
- /**
- * Determine whether a word is a distracter to words in dictionaries.
- *
- * @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 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/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
deleted file mode 100644
index 27973287d..000000000
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * 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.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.text.InputType;
-import android.util.Log;
-import android.util.LruCache;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.keyboard.KeyboardLayoutSet;
-import com.android.inputmethod.latin.DictionaryFacilitator;
-import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-
-/**
- * This class is used to prevent distracters being added to personalization
- * or user history dictionaries
- */
-public class DistracterFilterCheckingExactMatchesAndSuggestions implements DistracterFilter {
- private static final String TAG =
- DistracterFilterCheckingExactMatchesAndSuggestions.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 Map<Locale, InputMethodSubtype> mLocaleToSubtypeMap;
- private final Map<Locale, Keyboard> mLocaleToKeyboardMap;
- private final DictionaryFacilitator mDictionaryFacilitator;
- private final LruCache<String, Boolean> mDistractersCache;
- private Keyboard mKeyboard;
- private final Object mLock = new Object();
-
- // If the score of the top suggestion exceeds this value, the tested word (e.g.,
- // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distractor to
- // words in dictionary. The greater the threshold is, the less likely the tested word would
- // become a distractor, which means the tested word will be more likely to be added to
- // the dictionary.
- private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 0.4f;
-
- /**
- * Create a DistracterFilter instance.
- *
- * @param context the context.
- */
- public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context) {
- mContext = context;
- mLocaleToSubtypeMap = new HashMap<>();
- mLocaleToKeyboardMap = new HashMap<>();
- mDictionaryFacilitator = new DictionaryFacilitator();
- mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
- mKeyboard = null;
- }
-
- @Override
- public void close() {
- mDictionaryFacilitator.closeDictionaries();
- }
-
- @Override
- public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
- final Map<Locale, InputMethodSubtype> newLocaleToSubtypeMap = new HashMap<>();
- if (enabledSubtypes != null) {
- for (final InputMethodSubtype subtype : enabledSubtypes) {
- final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
- if (newLocaleToSubtypeMap.containsKey(locale)) {
- // Multiple subtypes are enabled for one locale.
- // TODO: Investigate what we should do for this case.
- continue;
- }
- newLocaleToSubtypeMap.put(locale, subtype);
- }
- }
- if (mLocaleToSubtypeMap.equals(newLocaleToSubtypeMap)) {
- // Enabled subtypes have not been changed.
- return;
- }
- synchronized (mLock) {
- mLocaleToSubtypeMap.clear();
- mLocaleToSubtypeMap.putAll(newLocaleToSubtypeMap);
- mLocaleToKeyboardMap.clear();
- }
- }
-
- private void loadKeyboardForLocale(final Locale newLocale) {
- final Keyboard cachedKeyboard = mLocaleToKeyboardMap.get(newLocale);
- if (cachedKeyboard != null) {
- mKeyboard = cachedKeyboard;
- return;
- }
- final InputMethodSubtype subtype;
- synchronized (mLock) {
- subtype = mLocaleToSubtypeMap.get(newLocale);
- }
- if (subtype == null) {
- return;
- }
- final EditorInfo editorInfo = new EditorInfo();
- editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
- final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
- mContext, editorInfo);
- final Resources res = mContext.getResources();
- final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
- final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
- builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
- builder.setSubtype(subtype);
- builder.setIsSpellChecker(false /* isSpellChecker */);
- final KeyboardLayoutSet layoutSet = builder.build();
- mKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
- }
-
- 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) {
- if (!mLocaleToSubtypeMap.containsKey(locale)) {
- Log.e(TAG, "Locale " + locale + " is not enabled.");
- // TODO: Investigate what we should do for disabled locales.
- return false;
- }
- loadKeyboardForLocale(locale);
- // 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;
- }
- }
- }
-
- if (DEBUG) {
- Log.d(TAG, "testedWord: " + testedWord);
- }
- final Boolean isCachedDistracter = mDistractersCache.get(testedWord);
- if (isCachedDistracter != null && isCachedDistracter) {
- if (DEBUG) {
- Log.d(TAG, "isDistracter: true (cache hit)");
- }
- return true;
- }
-
- final boolean isDistracterCheckedByGetMaxFreqencyOfExactMatches =
- checkDistracterUsingMaxFreqencyOfExactMatches(testedWord);
- if (isDistracterCheckedByGetMaxFreqencyOfExactMatches) {
- // Add the word to the cache.
- mDistractersCache.put(testedWord, Boolean.TRUE);
- return true;
- }
- final boolean isValidWord = mDictionaryFacilitator.isValidWord(testedWord,
- false /* ignoreCase */);
- if (isValidWord) {
- // Valid word is not a distractor.
- if (DEBUG) {
- Log.d(TAG, "isDistracter: false (valid word)");
- }
- return false;
- }
-
- final boolean isDistracterCheckedByGetSuggestion =
- checkDistracterUsingGetSuggestions(testedWord);
- if (isDistracterCheckedByGetSuggestion) {
- // Add the word to the cache.
- mDistractersCache.put(testedWord, Boolean.TRUE);
- return true;
- }
- return false;
- }
-
- private boolean checkDistracterUsingMaxFreqencyOfExactMatches(final String testedWord) {
- // 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, "perfectMatchFreq: " + perfectMatchFreq);
- Log.d(TAG, "exactMatchFreq: " + exactMatchFreq);
- Log.d(TAG, "isDistracter: " + isDistracter);
- }
- return isDistracter;
- }
-
- private boolean checkDistracterUsingGetSuggestions(final String testedWord) {
- if (mKeyboard == null) {
- return false;
- }
- final SettingsValuesForSuggestion settingsValuesForSuggestion =
- new SettingsValuesForSuggestion(false /* blockPotentiallyOffensive */,
- false /* spaceAwareGestureEnabled */,
- null /* additionalFeaturesSettingValues */);
- final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord);
- final String consideredWord = trailingSingleQuotesCount > 0 ?
- testedWord.substring(0, testedWord.length() - trailingSingleQuotesCount) :
- testedWord;
- final WordComposer composer = new WordComposer();
- final int[] codePoints = StringUtils.toCodePointArray(testedWord);
-
- synchronized (mLock) {
- final int[] coordinates = mKeyboard.getCoordinates(codePoints);
- composer.setComposingWord(codePoints, coordinates);
- final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults(
- composer, PrevWordsInfo.EMPTY_PREV_WORDS_INFO, mKeyboard.getProximityInfo(),
- settingsValuesForSuggestion, 0 /* sessionId */);
- if (suggestionResults.isEmpty()) {
- return false;
- }
- final SuggestedWordInfo firstSuggestion = suggestionResults.first();
- final boolean isDistractor = suggestionExceedsDistracterThreshold(
- firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
- if (DEBUG) {
- Log.d(TAG, "isDistracter: " + isDistractor);
- }
- return isDistractor;
- }
- }
-
- private static boolean suggestionExceedsDistracterThreshold(final SuggestedWordInfo suggestion,
- final String consideredWord, final float distracterThreshold) {
- if (suggestion == null) {
- return false;
- }
- final int suggestionScore = suggestion.mScore;
- final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
- consideredWord, suggestion.mWord, suggestionScore);
- if (DEBUG) {
- Log.d(TAG, "normalizedScore: " + normalizedScore);
- Log.d(TAG, "distracterThreshold: " + distracterThreshold);
- }
- if (normalizedScore > distracterThreshold) {
- return true;
- }
- return false;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
deleted file mode 100644
index 4ad4ba784..000000000
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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 61da1b789..8ce6eff92 100644
--- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
@@ -16,64 +16,136 @@
package com.android.inputmethod.latin.utils;
+import android.util.Log;
+
import com.android.inputmethod.annotations.UsedForTesting;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
+import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
/**
* Utilities to manage executors.
*/
public class ExecutorUtils {
- private static final ConcurrentHashMap<String, ExecutorService> sExecutorMap =
- new ConcurrentHashMap<>();
- private static class ThreadFactoryWithId implements ThreadFactory {
- private final String mId;
+ private static final String TAG = "ExecutorUtils";
+
+ public static final String KEYBOARD = "Keyboard";
+ public static final String SPELLING = "Spelling";
+
+ private static ScheduledExecutorService sKeyboardExecutorService = newExecutorService(KEYBOARD);
+ private static ScheduledExecutorService sSpellingExecutorService = newExecutorService(SPELLING);
+
+ private static ScheduledExecutorService newExecutorService(final String name) {
+ return Executors.newSingleThreadScheduledExecutor(new ExecutorFactory(name));
+ }
- public ThreadFactoryWithId(final String id) {
- mId = id;
+ private static class ExecutorFactory implements ThreadFactory {
+ private final String mName;
+
+ private ExecutorFactory(final String name) {
+ mName = name;
}
@Override
- public Thread newThread(final Runnable r) {
- return new Thread(r, "Executor - " + mId);
+ public Thread newThread(final Runnable runnable) {
+ Thread thread = new Thread(runnable, TAG);
+ thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread thread, Throwable ex) {
+ Log.w(mName + "-" + runnable.getClass().getSimpleName(), ex);
+ }
+ });
+ return thread;
}
}
+ @UsedForTesting
+ private static ScheduledExecutorService sExecutorServiceForTests;
+
+ @UsedForTesting
+ public static void setExecutorServiceForTests(
+ final ScheduledExecutorService executorServiceForTests) {
+ sExecutorServiceForTests = executorServiceForTests;
+ }
+
+ //
+ // Public methods used to schedule a runnable for execution.
+ //
+
/**
- * Gets the executor for the given id.
+ * @param name Executor's name.
+ * @return scheduled executor service used to run background tasks
*/
- public static ExecutorService getExecutor(final String id) {
- ExecutorService executor = sExecutorMap.get(id);
- if (executor == null) {
- synchronized(sExecutorMap) {
- executor = sExecutorMap.get(id);
- if (executor == null) {
- executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithId(id));
- sExecutorMap.put(id, executor);
- }
- }
+ public static ScheduledExecutorService getBackgroundExecutor(final String name) {
+ if (sExecutorServiceForTests != null) {
+ return sExecutorServiceForTests;
+ }
+ switch (name) {
+ case KEYBOARD:
+ return sKeyboardExecutorService;
+ case SPELLING:
+ return sSpellingExecutorService;
+ default:
+ throw new IllegalArgumentException("Invalid executor: " + name);
}
- return executor;
}
- /**
- * Shutdowns all executors and removes all executors from the executor map for testing.
- */
+ public static void killTasks(final String name) {
+ final ScheduledExecutorService executorService = getBackgroundExecutor(name);
+ executorService.shutdownNow();
+ try {
+ executorService.awaitTermination(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Log.wtf(TAG, "Failed to shut down: " + name);
+ }
+ if (executorService == sExecutorServiceForTests) {
+ // Don't do anything to the test service.
+ return;
+ }
+ switch (name) {
+ case KEYBOARD:
+ sKeyboardExecutorService = newExecutorService(KEYBOARD);
+ break;
+ case SPELLING:
+ sSpellingExecutorService = newExecutorService(SPELLING);
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid executor: " + name);
+ }
+ }
+
+ @UsedForTesting
+ public static Runnable chain(final Runnable... runnables) {
+ return new RunnableChain(runnables);
+ }
+
@UsedForTesting
- public static void shutdownAllExecutors() {
- synchronized(sExecutorMap) {
- for (final ExecutorService executor : sExecutorMap.values()) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- executor.shutdown();
- sExecutorMap.remove(executor);
- }
- });
+ public static class RunnableChain implements Runnable {
+ private final Runnable[] mRunnables;
+
+ private RunnableChain(final Runnable... runnables) {
+ if (runnables == null || runnables.length == 0) {
+ throw new IllegalArgumentException("Attempting to construct an empty chain");
+ }
+ mRunnables = runnables;
+ }
+
+ @UsedForTesting
+ public Runnable[] getRunnables() {
+ return mRunnables;
+ }
+
+ @Override
+ public void run() {
+ for (Runnable runnable : mRunnables) {
+ if (Thread.interrupted()) {
+ return;
+ }
+ runnable.run();
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/FileUtils.java b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
deleted file mode 100644
index f1106a6c6..000000000
--- a/java/src/com/android/inputmethod/latin/utils/FileUtils.java
+++ /dev/null
@@ -1,54 +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 java.io.File;
-import java.io.FilenameFilter;
-
-/**
- * A simple class to help with removing directories recursively.
- */
-public class FileUtils {
- public static boolean deleteRecursively(final File path) {
- if (path.isDirectory()) {
- final File[] files = path.listFiles();
- if (files != null) {
- for (final File child : files) {
- deleteRecursively(child);
- }
- }
- }
- return path.delete();
- }
-
- public static boolean deleteFilteredFiles(final File dir, final FilenameFilter fileNameFilter) {
- if (!dir.isDirectory()) {
- return false;
- }
- final File[] files = dir.listFiles(fileNameFilter);
- if (files == null) {
- return false;
- }
- boolean hasDeletedAllFiles = true;
- for (final File file : files) {
- if (!deleteRecursively(file)) {
- hasDeletedAllFiles = false;
- }
- }
- return hasDeletedAllFiles;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
index c2167a76b..c87a7c05c 100644
--- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
@@ -18,18 +18,17 @@ package com.android.inputmethod.latin.utils;
import com.android.inputmethod.dictionarypack.DictionarySettingsFragment;
import com.android.inputmethod.latin.about.AboutPreferences;
+import com.android.inputmethod.latin.settings.AccountsSettingsFragment;
import com.android.inputmethod.latin.settings.AdvancedSettingsFragment;
import com.android.inputmethod.latin.settings.AppearanceSettingsFragment;
import com.android.inputmethod.latin.settings.CorrectionSettingsFragment;
import com.android.inputmethod.latin.settings.CustomInputStyleSettingsFragment;
import com.android.inputmethod.latin.settings.DebugSettingsFragment;
import com.android.inputmethod.latin.settings.GestureSettingsFragment;
-import com.android.inputmethod.latin.settings.MultiLingualSettingsFragment;
import com.android.inputmethod.latin.settings.PreferencesSettingsFragment;
import com.android.inputmethod.latin.settings.SettingsFragment;
import com.android.inputmethod.latin.settings.ThemeSettingsFragment;
import com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsFragment;
-import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragment;
import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker;
import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
@@ -42,9 +41,9 @@ public class FragmentUtils {
sLatinImeFragments.add(DictionarySettingsFragment.class.getName());
sLatinImeFragments.add(AboutPreferences.class.getName());
sLatinImeFragments.add(PreferencesSettingsFragment.class.getName());
+ sLatinImeFragments.add(AccountsSettingsFragment.class.getName());
sLatinImeFragments.add(AppearanceSettingsFragment.class.getName());
sLatinImeFragments.add(ThemeSettingsFragment.class.getName());
- sLatinImeFragments.add(MultiLingualSettingsFragment.class.getName());
sLatinImeFragments.add(CustomInputStyleSettingsFragment.class.getName());
sLatinImeFragments.add(GestureSettingsFragment.class.getName());
sLatinImeFragments.add(CorrectionSettingsFragment.class.getName());
@@ -52,7 +51,6 @@ public class FragmentUtils {
sLatinImeFragments.add(DebugSettingsFragment.class.getName());
sLatinImeFragments.add(SettingsFragment.class.getName());
sLatinImeFragments.add(SpellCheckerSettingsFragment.class.getName());
- sLatinImeFragments.add(UserDictionaryAddWordFragment.class.getName());
sLatinImeFragments.add(UserDictionaryList.class.getName());
sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName());
sLatinImeFragments.add(UserDictionarySettings.class.getName());
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
index ea406fa75..df0cd8437 100644
--- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -25,6 +25,7 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.settings.SettingsValues;
import java.util.concurrent.TimeUnit;
@@ -53,7 +54,8 @@ public final class ImportantNoticeUtils {
// This utility class is not publicly instantiable.
}
- private static boolean isInSystemSetupWizard(final Context context) {
+ @UsedForTesting
+ static boolean isInSystemSetupWizard(final Context context) {
try {
final int userSetupComplete = Settings.Secure.getInt(
context.getContentResolver(), Settings_Secure_USER_SETUP_COMPLETE);
@@ -84,7 +86,8 @@ public final class ImportantNoticeUtils {
return getLastImportantNoticeVersion(context) + 1;
}
- private static boolean hasNewImportantNotice(final Context context) {
+ @UsedForTesting
+ static boolean hasNewImportantNotice(final Context context) {
final int lastVersion = getLastImportantNoticeVersion(context);
return getCurrentImportantNoticeVersion(context) > lastVersion;
}
@@ -103,7 +106,12 @@ public final class ImportantNoticeUtils {
return elapsedTime >= TIMEOUT_OF_IMPORTANT_NOTICE;
}
- public static boolean shouldShowImportantNotice(final Context context) {
+ public static boolean shouldShowImportantNotice(final Context context,
+ final SettingsValues settingsValues) {
+ // Check to see whether personalization is enabled by the user.
+ if (!settingsValues.isPersonalizationEnabled()) {
+ return false;
+ }
if (!hasNewImportantNotice(context)) {
return false;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
deleted file mode 100644
index fbce3f2fd..000000000
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * 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 android.util.Log;
-
-import com.android.inputmethod.latin.Dictionary;
-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
-// rename this class or field name. See BinaryDictionary#addMultipleDictionaryEntriesNative().
-public final class LanguageModelParam {
- private static final String TAG = LanguageModelParam.class.getSimpleName();
- private static final boolean DEBUG = false;
- private static final boolean DEBUG_TOKEN = false;
-
- // For now, these probability values are being referred to only when we add new entries to
- // decaying dynamic binary dictionaries. When these are referred to, what matters is 0 or
- // non-0. Thus, it's not meaningful to compare 10, 100, and so on.
- // TODO: Revise the logic in ForgettingCurveUtils in native code.
- private static final int UNIGRAM_PROBABILITY_FOR_VALID_WORD = 100;
- private static final int UNIGRAM_PROBABILITY_FOR_OOV_WORD = Dictionary.NOT_A_PROBABILITY;
- private static final int BIGRAM_PROBABILITY_FOR_VALID_WORD = 10;
- private static final int BIGRAM_PROBABILITY_FOR_OOV_WORD = Dictionary.NOT_A_PROBABILITY;
-
- public final CharSequence mTargetWord;
- public final int[] mWord0;
- public final int[] mWord1;
- // TODO: this needs to be a list of shortcuts
- public final int[] mShortcutTarget;
- public final int mUnigramProbability;
- public final int mBigramProbability;
- public final int mShortcutProbability;
- public final boolean mIsNotAWord;
- public final boolean mIsBlacklisted;
- // Time stamp in seconds.
- public final int mTimestamp;
-
- // Constructor for unigram. TODO: support shortcuts
- public LanguageModelParam(final CharSequence word, final int unigramProbability,
- final int timestamp) {
- this(null /* word0 */, word, unigramProbability, Dictionary.NOT_A_PROBABILITY, timestamp);
- }
-
- // Constructor for unigram and bigram.
- public LanguageModelParam(final CharSequence word0, final CharSequence word1,
- final int unigramProbability, final int bigramProbability,
- final int timestamp) {
- mTargetWord = word1;
- mWord0 = (word0 == null) ? null : StringUtils.toCodePointArray(word0);
- mWord1 = StringUtils.toCodePointArray(word1);
- mShortcutTarget = null;
- mUnigramProbability = unigramProbability;
- mBigramProbability = bigramProbability;
- mShortcutProbability = Dictionary.NOT_A_PROBABILITY;
- mIsNotAWord = false;
- mIsBlacklisted = false;
- mTimestamp = timestamp;
- }
-
- // Process a list of words and return a list of {@link LanguageModelParam} objects.
- public static ArrayList<LanguageModelParam> createLanguageModelParamsFrom(
- 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();
- PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
- for (int i = 0; i < N; ++i) {
- final String tempWord = tokens.get(i);
- if (StringUtils.isEmptyStringOrWhiteSpaces(tempWord)) {
- // just skip this token
- if (DEBUG_TOKEN) {
- Log.d(TAG, "--- isEmptyStringOrWhiteSpaces: \"" + tempWord + "\"");
- }
- continue;
- }
- if (!DictionaryInfoUtils.looksValidForDictionaryInsertion(
- tempWord, spacingAndPunctuations)) {
- if (DEBUG_TOKEN) {
- Log.d(TAG, "--- not looksValidForDictionaryInsertion: \""
- + tempWord + "\"");
- }
- // Sentence terminator found. Split.
- prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
- continue;
- }
- if (DEBUG_TOKEN) {
- Log.d(TAG, "--- word: \"" + tempWord + "\"");
- }
- final LanguageModelParam languageModelParam =
- detectWhetherVaildWordOrNotAndGetLanguageModelParam(
- prevWordsInfo, tempWord, timestamp, dictionaryFacilitator,
- distracterFilter);
- if (languageModelParam == null) {
- continue;
- }
- languageModelParams.add(languageModelParam);
- prevWordsInfo = prevWordsInfo.getNextPrevWordsInfo(
- new PrevWordsInfo.WordInfo(tempWord));
- }
- return languageModelParams;
- }
-
- private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
- 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, false /* ignoreCase */)) {
- 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(prevWordsInfo, lowerCaseTargetWord,
- timestamp, true /* isValidWord */, locale, distracterFilter);
- }
-
- // Treat the word as an OOV word.
- return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp,
- false /* isValidWord */, locale, distracterFilter);
- }
-
- private static LanguageModelParam createAndGetLanguageModelParamOfWord(
- 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
- && !prevWordsInfo.isValid() && !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 (!prevWordsInfo.isValid()) {
- if (DEBUG) {
- Log.d(TAG, "--- add unigram: current("
- + (isValidWord ? "Valid" : "OOV") + ") = " + word);
- }
- return new LanguageModelParam(word, unigramProbability, timestamp);
- }
- if (DEBUG) {
- Log.d(TAG, "--- add bigram: prev = " + prevWordsInfo + ", current("
- + (isValidWord ? "Valid" : "OOV") + ") = " + word);
- }
- final int bigramProbability = isValidWord ?
- BIGRAM_PROBABILITY_FOR_VALID_WORD : BIGRAM_PROBABILITY_FOR_OOV_WORD;
- return new LanguageModelParam(prevWordsInfo.mPrevWordsInfo[0].mWord, word,
- unigramProbability, bigramProbability, timestamp);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java b/java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java
new file mode 100644
index 000000000..a5a1ea921
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageOnSpacebarUtils.java
@@ -0,0 +1,92 @@
+/*
+ * 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 android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.RichInputMethodSubtype;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import javax.annotation.Nonnull;
+
+/**
+ * This class determines that the language name on the spacebar should be displayed in what format.
+ */
+public final class LanguageOnSpacebarUtils {
+ public static final int FORMAT_TYPE_NONE = 0;
+ public static final int FORMAT_TYPE_LANGUAGE_ONLY = 1;
+ public static final int FORMAT_TYPE_FULL_LOCALE = 2;
+
+ private static List<InputMethodSubtype> sEnabledSubtypes = Collections.emptyList();
+ private static boolean sIsSystemLanguageSameAsInputLanguage;
+
+ private LanguageOnSpacebarUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static int getLanguageOnSpacebarFormatType(
+ @Nonnull final RichInputMethodSubtype subtype) {
+ if (subtype.isNoLanguage()) {
+ return FORMAT_TYPE_FULL_LOCALE;
+ }
+ // Only this subtype is enabled and equals to the system locale.
+ if (sEnabledSubtypes.size() < 2 && sIsSystemLanguageSameAsInputLanguage) {
+ return FORMAT_TYPE_NONE;
+ }
+ final Locale locale = subtype.getLocale();
+ if (locale == null) {
+ return FORMAT_TYPE_NONE;
+ }
+ final String keyboardLanguage = locale.getLanguage();
+ final String keyboardLayout = subtype.getKeyboardLayoutSetName();
+ int sameLanguageAndLayoutCount = 0;
+ for (final InputMethodSubtype ims : sEnabledSubtypes) {
+ final String language = SubtypeLocaleUtils.getSubtypeLocale(ims).getLanguage();
+ if (keyboardLanguage.equals(language) && keyboardLayout.equals(
+ SubtypeLocaleUtils.getKeyboardLayoutSetName(ims))) {
+ sameLanguageAndLayoutCount++;
+ }
+ }
+ // Display full locale name only when there are multiple subtypes that have the same
+ // locale and keyboard layout. Otherwise displaying language name is enough.
+ return sameLanguageAndLayoutCount > 1 ? FORMAT_TYPE_FULL_LOCALE
+ : FORMAT_TYPE_LANGUAGE_ONLY;
+ }
+
+ public static void setEnabledSubtypes(@Nonnull final List<InputMethodSubtype> enabledSubtypes) {
+ sEnabledSubtypes = enabledSubtypes;
+ }
+
+ public static void onSubtypeChanged(@Nonnull final RichInputMethodSubtype subtype,
+ final boolean implicitlyEnabledSubtype, @Nonnull final Locale systemLocale) {
+ final Locale newLocale = subtype.getLocale();
+ if (systemLocale.equals(newLocale)) {
+ sIsSystemLanguageSameAsInputLanguage = true;
+ return;
+ }
+ if (!systemLocale.getLanguage().equals(newLocale.getLanguage())) {
+ sIsSystemLanguageSameAsInputLanguage = false;
+ return;
+ }
+ // If the subtype is enabled explicitly, the language name should be displayed even when
+ // the keyboard language and the system language are equal.
+ sIsSystemLanguageSameAsInputLanguage = implicitlyEnabledSubtype;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
index dd6fac671..9a5be99b3 100644
--- a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
@@ -21,21 +21,22 @@ import android.os.Looper;
import java.lang.ref.WeakReference;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
public class LeakGuardHandlerWrapper<T> extends Handler {
private final WeakReference<T> mOwnerInstanceRef;
- public LeakGuardHandlerWrapper(final T ownerInstance) {
+ public LeakGuardHandlerWrapper(@Nonnull final T ownerInstance) {
this(ownerInstance, Looper.myLooper());
}
- public LeakGuardHandlerWrapper(final T ownerInstance, final Looper looper) {
+ public LeakGuardHandlerWrapper(@Nonnull final T ownerInstance, final Looper looper) {
super(looper);
- if (ownerInstance == null) {
- throw new NullPointerException("ownerInstance is null");
- }
mOwnerInstanceRef = new WeakReference<>(ownerInstance);
}
+ @Nullable
public T getOwnerInstance() {
return mOwnerInstanceRef.get();
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
deleted file mode 100644
index c519a0de6..000000000
--- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.text.TextUtils;
-
-import java.util.HashMap;
-import java.util.Locale;
-
-/**
- * A class to help with handling Locales in string form.
- *
- * This file has the same meaning and features (and shares all of its code) with
- * the one in the dictionary pack. They need to be kept synchronized; for any
- * update/bugfix to this file, consider also updating/fixing the version in the
- * dictionary pack.
- */
-public final class LocaleUtils {
- private LocaleUtils() {
- // Intentional empty constructor for utility class.
- }
-
- // Locale match level constants.
- // A higher level of match is guaranteed to have a higher numerical value.
- // Some room is left within constants to add match cases that may arise necessary
- // in the future, for example differentiating between the case where the countries
- // are both present and different, and the case where one of the locales does not
- // specify the countries. This difference is not needed now.
-
- // Nothing matches.
- public static final int LOCALE_NO_MATCH = 0;
- // The languages matches, but the country are different. Or, the reference locale requires a
- // country and the tested locale does not have one.
- public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3;
- // The languages and country match, but the variants are different. Or, the reference locale
- // requires a variant and the tested locale does not have one.
- public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6;
- // The required locale is null or empty so it will accept anything, and the tested locale
- // is non-null and non-empty.
- public static final int LOCALE_ANY_MATCH = 10;
- // The language matches, and the tested locale specifies a country but the reference locale
- // does not require one.
- public static final int LOCALE_LANGUAGE_MATCH = 15;
- // The language and the country match, and the tested locale specifies a variant but the
- // reference locale does not require one.
- public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20;
- // The compared locales are fully identical. This is the best match level.
- public static final int LOCALE_FULL_MATCH = 30;
-
- // The level at which a match is "normally" considered a locale match with standard algorithms.
- // Don't use this directly, use #isMatch to test.
- private static final int LOCALE_MATCH = LOCALE_ANY_MATCH;
-
- // Make this match the maximum match level. If this evolves to have more than 2 digits
- // when written in base 10, also adjust the getMatchLevelSortedString method.
- private static final int MATCH_LEVEL_MAX = 30;
-
- /**
- * Return how well a tested locale matches a reference locale.
- *
- * This will check the tested locale against the reference locale and return a measure of how
- * a well it matches the reference. The general idea is that the tested locale has to match
- * every specified part of the required locale. A full match occur when they are equal, a
- * partial match when the tested locale agrees with the reference locale but is more specific,
- * and a difference when the tested locale does not comply with all requirements from the
- * reference locale.
- * In more detail, if the reference locale specifies at least a language and the testedLocale
- * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the
- * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH
- * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and
- * tested locale agree on the language, but not on the country,
- * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country,
- * and LOCALE_LANGUAGE_MATCH otherwise.
- * If they agree on both the language and the country, but not on the variant,
- * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale
- * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches,
- * LOCALE_FULL_MATCH is returned.
- * Examples:
- * en <=> en_US => LOCALE_LANGUAGE_MATCH
- * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER
- * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER
- * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH
- * sp_US <=> en_US => LOCALE_NO_MATCH
- * de <=> de => LOCALE_FULL_MATCH
- * en_US <=> en_US => LOCALE_FULL_MATCH
- * "" <=> en_US => LOCALE_ANY_MATCH
- *
- * @param referenceLocale the reference locale to test against.
- * @param testedLocale the locale to test.
- * @return a constant that measures how well the tested locale matches the reference locale.
- */
- public static int getMatchLevel(String referenceLocale, String testedLocale) {
- if (TextUtils.isEmpty(referenceLocale)) {
- return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH;
- }
- if (null == testedLocale) return LOCALE_NO_MATCH;
- String[] referenceParams = referenceLocale.split("_", 3);
- String[] testedParams = testedLocale.split("_", 3);
- // By spec of String#split, [0] cannot be null and length cannot be 0.
- if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH;
- switch (referenceParams.length) {
- case 1:
- return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH;
- case 2:
- if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (!referenceParams[1].equals(testedParams[1]))
- return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH;
- return LOCALE_FULL_MATCH;
- case 3:
- if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (!referenceParams[1].equals(testedParams[1]))
- return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
- if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
- if (!referenceParams[2].equals(testedParams[2]))
- return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
- return LOCALE_FULL_MATCH;
- }
- // It should be impossible to come here
- return LOCALE_NO_MATCH;
- }
-
- /**
- * Return a string that represents this match level, with better matches first.
- *
- * The strings are sorted in lexicographic order: a better match will always be less than
- * a worse match when compared together.
- */
- public static String getMatchLevelSortedString(int matchLevel) {
- // This works because the match levels are 0~99 (actually 0~30)
- // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel
- return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
- }
-
- /**
- * Find out whether a match level should be considered a match.
- *
- * This method takes a match level as returned by the #getMatchLevel method, and returns whether
- * it should be considered a match in the usual sense with standard Locale functions.
- *
- * @param level the match level, as returned by getMatchLevel.
- * @return whether this is a match or not.
- */
- public static boolean isMatch(int level) {
- return LOCALE_MATCH <= level;
- }
-
- private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
-
- /**
- * Creates a locale from a string specification.
- */
- public static Locale constructLocaleFromString(final String localeStr) {
- if (localeStr == null) {
- return null;
- }
- synchronized (sLocaleCache) {
- Locale retval = sLocaleCache.get(localeStr);
- if (retval != null) {
- return retval;
- }
- String[] localeParams = localeStr.split("_", 3);
- if (localeParams.length == 1) {
- retval = new Locale(localeParams[0]);
- } else if (localeParams.length == 2) {
- retval = new Locale(localeParams[0], localeParams[1]);
- } else if (localeParams.length == 3) {
- retval = new Locale(localeParams[0], localeParams[1], localeParams[2]);
- }
- if (retval != null) {
- sLocaleCache.put(localeStr, retval);
- }
- return retval;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java
index 3cd63612c..c05ffd693 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java
@@ -16,18 +16,22 @@
package com.android.inputmethod.latin.utils;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.NgramContext.WordInfo;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+
+import java.util.Arrays;
import java.util.regex.Pattern;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.PrevWordsInfo;
-import com.android.inputmethod.latin.PrevWordsInfo.WordInfo;
-import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import javax.annotation.Nonnull;
-public final class PrevWordsInfoUtils {
- private PrevWordsInfoUtils() {
+public final class NgramContextUtils {
+ private NgramContextUtils() {
// Intentional empty constructor for utility class.
}
+ private static final Pattern NEWLINE_REGEX = Pattern.compile("[\\r\\n]+");
private static final Pattern SPACE_REGEX = Pattern.compile("\\s+");
// Get context information from nth word before the cursor. n = 1 retrieves the words
// immediately before the cursor, n = 2 retrieves the words before that, and so on. This splits
@@ -43,7 +47,7 @@ public final class PrevWordsInfoUtils {
// (n = 2) "abc def|" -> beginning-of-sentence, abc
// (n = 2) "abc def |" -> beginning-of-sentence, 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.
+ // represent this situation using NgramContext. See TODO in the method.
// TODO: The next example's result should be "abc, def". This have to be fixed before we
// retrieve the prior context of Beginning-of-Sentence.
// (n = 2) "abc def. |" -> beginning-of-sentence, abc
@@ -51,11 +55,18 @@ public final class PrevWordsInfoUtils {
// (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,
+ @Nonnull
+ public static NgramContext getNgramContextFromNthPreviousWord(final CharSequence prev,
final SpacingAndPunctuations spacingAndPunctuations, final int n) {
- if (prev == null) return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
- final String[] w = SPACE_REGEX.split(prev);
- final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ if (prev == null) return NgramContext.EMPTY_PREV_WORDS_INFO;
+ final String[] lines = NEWLINE_REGEX.split(prev);
+ if (lines.length == 0) {
+ return new NgramContext(WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO);
+ }
+ final String[] w = SPACE_REGEX.split(lines[lines.length - 1]);
+ final WordInfo[] prevWordsInfo =
+ new WordInfo[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ Arrays.fill(prevWordsInfo, WordInfo.EMPTY_WORD_INFO);
for (int i = 0; i < prevWordsInfo.length; i++) {
final int focusedWordIndex = w.length - n - i;
// Referring to the word after the focused word.
@@ -66,38 +77,37 @@ public final class PrevWordsInfoUtils {
if (spacingAndPunctuations.isWordConnector(firstChar)) {
// The word following the focused word is starting with a word connector.
// TODO: Return meaningful context for this case.
- prevWordsInfo[i] = WordInfo.EMPTY_WORD_INFO;
break;
}
}
}
// If we can't find (n + i) words, the context is beginning-of-sentence.
if (focusedWordIndex < 0) {
- prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE;
+ prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO;
break;
}
+
final String focusedWord = w[focusedWordIndex];
- // If the word is, the context is beginning-of-sentence.
+ // If the word is empty, the context is beginning-of-sentence.
final int length = focusedWord.length();
if (length <= 0) {
- prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE;
+ prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO;
break;
}
- // If ends in a sentence separator, the context is beginning-of-sentence.
+ // If the word ends in a sentence terminator, the context is beginning-of-sentence.
final char lastChar = focusedWord.charAt(length - 1);
- if (spacingAndPunctuations.isSentenceSeparator(lastChar)) {
- prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE;
+ if (spacingAndPunctuations.isSentenceTerminator(lastChar)) {
+ prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO;
break;
}
// 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)) {
- prevWordsInfo[i] = WordInfo.EMPTY_WORD_INFO;
break;
}
prevWordsInfo[i] = new WordInfo(focusedWord);
}
- return new PrevWordsInfo(prevWordsInfo);
+ return new NgramContext(prevWordsInfo);
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index e3cac97f0..a381649a4 100644
--- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin.utils;
+import com.android.inputmethod.latin.common.StringUtils;
+
import java.util.Locale;
/**
@@ -49,6 +51,17 @@ public class RecapitalizeStatus {
}
}
+ public static String modeToString(final int recapitalizeMode) {
+ switch (recapitalizeMode) {
+ case NOT_A_RECAPITALIZE_MODE: return "undefined";
+ case CAPS_MODE_ORIGINAL_MIXED_CASE: return "mixedCase";
+ case CAPS_MODE_ALL_LOWER: return "allLower";
+ case CAPS_MODE_FIRST_WORD_UPPER: return "firstWordUpper";
+ case CAPS_MODE_ALL_UPPER: return "allUpper";
+ default: return "unknown<" + recapitalizeMode + ">";
+ }
+ }
+
/**
* We store the location of the cursor and the string that was there before the recapitalize
* action was done, and the location of the cursor and the string that was there after.
diff --git a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
deleted file mode 100644
index 64c9e2cff..000000000
--- a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import java.util.Arrays;
-
-// TODO: This class is not thread-safe.
-public final class ResizableIntArray {
- private int[] mArray;
- private int mLength;
-
- public ResizableIntArray(final int capacity) {
- reset(capacity);
- }
-
- public int get(final int index) {
- if (index < mLength) {
- return mArray[index];
- }
- throw new ArrayIndexOutOfBoundsException("length=" + mLength + "; index=" + index);
- }
-
- public void addAt(final int index, final int val) {
- if (index < mLength) {
- mArray[index] = val;
- } else {
- mLength = index;
- add(val);
- }
- }
-
- public void add(final int val) {
- final int currentLength = mLength;
- ensureCapacity(currentLength + 1);
- mArray[currentLength] = val;
- mLength = currentLength + 1;
- }
-
- /**
- * Calculate the new capacity of {@code mArray}.
- * @param minimumCapacity the minimum capacity that the {@code mArray} should have.
- * @return the new capacity that the {@code mArray} should have. Returns zero when there is no
- * need to expand {@code mArray}.
- */
- private int calculateCapacity(final int minimumCapacity) {
- final int currentCapcity = mArray.length;
- if (currentCapcity < minimumCapacity) {
- final int nextCapacity = currentCapcity * 2;
- // The following is the same as return Math.max(minimumCapacity, nextCapacity);
- return minimumCapacity > nextCapacity ? minimumCapacity : nextCapacity;
- }
- return 0;
- }
-
- private void ensureCapacity(final int minimumCapacity) {
- final int newCapacity = calculateCapacity(minimumCapacity);
- if (newCapacity > 0) {
- // TODO: Implement primitive array pool.
- mArray = Arrays.copyOf(mArray, newCapacity);
- }
- }
-
- public int getLength() {
- return mLength;
- }
-
- public void setLength(final int newLength) {
- ensureCapacity(newLength);
- mLength = newLength;
- }
-
- public void reset(final int capacity) {
- // TODO: Implement primitive array pool.
- mArray = new int[capacity];
- mLength = 0;
- }
-
- public int[] getPrimitiveArray() {
- return mArray;
- }
-
- public void set(final ResizableIntArray ip) {
- // TODO: Implement primitive array pool.
- mArray = ip.mArray;
- mLength = ip.mLength;
- }
-
- public void copy(final ResizableIntArray ip) {
- final int newCapacity = calculateCapacity(ip.mLength);
- if (newCapacity > 0) {
- // TODO: Implement primitive array pool.
- mArray = new int[newCapacity];
- }
- System.arraycopy(ip.mArray, 0, mArray, 0, ip.mLength);
- mLength = ip.mLength;
- }
-
- public void append(final ResizableIntArray src, final int startPos, final int length) {
- if (length == 0) {
- return;
- }
- final int currentLength = mLength;
- final int newLength = currentLength + length;
- ensureCapacity(newLength);
- System.arraycopy(src.mArray, startPos, mArray, currentLength, length);
- mLength = newLength;
- }
-
- public void fill(final int value, final int startPos, final int length) {
- if (startPos < 0 || length < 0) {
- throw new IllegalArgumentException("startPos=" + startPos + "; length=" + length);
- }
- final int endPos = startPos + length;
- ensureCapacity(endPos);
- Arrays.fill(mArray, startPos, endPos, value);
- if (mLength < endPos) {
- mLength = endPos;
- }
- }
-
- /**
- * Shift to the left by elementCount, discarding elementCount pointers at the start.
- * @param elementCount how many elements to shift.
- */
- public void shift(final int elementCount) {
- System.arraycopy(mArray, elementCount, mArray, 0, mLength - elementCount);
- mLength -= elementCount;
- }
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder();
- for (int i = 0; i < mLength; i++) {
- if (i != 0) {
- sb.append(",");
- }
- sb.append(mArray[i]);
- }
- return "[" + sb + "]";
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
index 093c5a6c1..cc0d470df 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -26,6 +26,7 @@ import android.util.TypedValue;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.settings.SettingsValues;
import java.util.ArrayList;
import java.util.HashMap;
@@ -110,7 +111,6 @@ public final class ResourceUtils {
* are true for the specified key value pairs.
*
* For example, "condition,constant" has the following format.
- * (See {@link ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()})
* - HARDWARE=mako,constantForNexus4
* - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4
* - ,defaultConstant
@@ -119,6 +119,7 @@ public final class ResourceUtils {
* @param conditionConstantArray an array of "condition,constant" elements to be searched.
* @return the constant part of the matched "condition,constant" element. Returns null if no
* condition matches.
+ * @see com.android.inputmethod.latin.utils.ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()
*/
@UsedForTesting
static String findConstantForKeyValuePairs(final HashMap<String, String> keyValuePairs,
@@ -186,6 +187,15 @@ public final class ResourceUtils {
return dm.widthPixels;
}
+ public static int getKeyboardHeight(final Resources res, final SettingsValues settingsValues) {
+ final int defaultKeyboardHeight = getDefaultKeyboardHeight(res);
+ if (settingsValues.mHasKeyboardResize) {
+ // mKeyboardHeightScale Ranges from [.5,1.2], from xml/prefs_screen_debug.xml
+ return (int)(defaultKeyboardHeight * settingsValues.mKeyboardHeightScale);
+ }
+ return defaultKeyboardHeight;
+ }
+
public static int getDefaultKeyboardHeight(final Resources res) {
final DisplayMetrics dm = res.getDisplayMetrics();
final String keyboardHeightInDp = getDeviceOverrideValue(
diff --git a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
index 0e244666d..4679f7814 100644
--- a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
@@ -23,9 +23,10 @@ import java.util.TreeMap;
* A class to help with handling different writing scripts.
*/
public class ScriptUtils {
+
// Used for hardware keyboards
public static final int SCRIPT_UNKNOWN = -1;
- // TODO: should we use ISO 15924 identifiers instead?
+
public static final int SCRIPT_ARABIC = 0;
public static final int SCRIPT_ARMENIAN = 1;
public static final int SCRIPT_BENGALI = 2;
@@ -44,35 +45,31 @@ public class ScriptUtils {
public static final int SCRIPT_TAMIL = 15;
public static final int SCRIPT_TELUGU = 16;
public static final int SCRIPT_THAI = 17;
- public static final TreeMap<String, Integer> mSpellCheckerLanguageToScript;
+
+ private static final TreeMap<String, Integer> mLanguageCodeToScriptCode;
+
static {
- // List of the supported languages and their associated script. We won't check
- // words written in another script than the selected script, because we know we
- // don't have those in our dictionary so we will underline everything and we
- // will never have any suggestions, so it makes no sense checking them, and this
- // is done in {@link #shouldFilterOut}. Also, the script is used to choose which
- // 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.
- mSpellCheckerLanguageToScript = new TreeMap<>();
- mSpellCheckerLanguageToScript.put("cs", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("da", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("de", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("el", SCRIPT_GREEK);
- mSpellCheckerLanguageToScript.put("en", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("es", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("fi", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("fr", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("hr", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("it", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("lt", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("lv", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("nb", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("nl", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("pt", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("sl", SCRIPT_LATIN);
- mSpellCheckerLanguageToScript.put("ru", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode = new TreeMap<>();
+ mLanguageCodeToScriptCode.put("", SCRIPT_LATIN); // default
+ mLanguageCodeToScriptCode.put("ar", SCRIPT_ARABIC);
+ mLanguageCodeToScriptCode.put("hy", SCRIPT_ARMENIAN);
+ mLanguageCodeToScriptCode.put("bn", SCRIPT_BENGALI);
+ mLanguageCodeToScriptCode.put("bg", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode.put("sr", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode.put("ru", SCRIPT_CYRILLIC);
+ mLanguageCodeToScriptCode.put("ka", SCRIPT_GEORGIAN);
+ mLanguageCodeToScriptCode.put("el", SCRIPT_GREEK);
+ mLanguageCodeToScriptCode.put("he", SCRIPT_HEBREW);
+ mLanguageCodeToScriptCode.put("km", SCRIPT_KHMER);
+ mLanguageCodeToScriptCode.put("lo", SCRIPT_LAO);
+ mLanguageCodeToScriptCode.put("ml", SCRIPT_MALAYALAM);
+ mLanguageCodeToScriptCode.put("my", SCRIPT_MYANMAR);
+ mLanguageCodeToScriptCode.put("si", SCRIPT_SINHALA);
+ mLanguageCodeToScriptCode.put("ta", SCRIPT_TAMIL);
+ mLanguageCodeToScriptCode.put("te", SCRIPT_TELUGU);
+ mLanguageCodeToScriptCode.put("th", SCRIPT_THAI);
}
+
/*
* Returns whether the code point is a letter that makes sense for the specified
* locale for this spell checker.
@@ -181,11 +178,17 @@ public class ScriptUtils {
}
}
+ /**
+ * @param locale spell checker locale
+ * @return internal Latin IME script code that maps to a language code
+ * {@see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes}
+ */
public static int getScriptFromSpellCheckerLocale(final Locale locale) {
- final Integer script = mSpellCheckerLanguageToScript.get(locale.getLanguage());
- if (null == script) {
- throw new RuntimeException("We have been called with an unsupported language: \""
- + locale.getLanguage() + "\". Framework bug?");
+ String language = locale.getLanguage();
+ Integer script = mLanguageCodeToScriptCode.get(language);
+ if (script == null) {
+ // Default to Latin.
+ script = mLanguageCodeToScriptCode.get("");
}
return script;
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
deleted file mode 100644
index 1ca895fdb..000000000
--- a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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 android.view.inputmethod.InputMethodSubtype;
-
-public final class SpacebarLanguageUtils {
- private SpacebarLanguageUtils() {
- // Intentional empty constructor for utility class.
- }
-
- // InputMethodSubtype's display name for spacebar text in its locale.
- // isAdditionalSubtype (T=true, F=false)
- // locale layout | Middle Full
- // ------ ------- - --------- ----------------------
- // en_US qwerty F English English (US) exception
- // en_GB qwerty F English English (UK) exception
- // es_US spanish F Español Español (EE.UU.) exception
- // fr azerty F Français Français
- // fr_CA qwerty F Français Français (Canada)
- // fr_CH swiss F Français Français (Suisse)
- // de qwertz F Deutsch Deutsch
- // de_CH swiss T Deutsch Deutsch (Schweiz)
- // zz qwerty F QWERTY QWERTY
- // fr qwertz T Français Français
- // de qwerty T Deutsch Deutsch
- // en_US azerty T English English (US)
- // zz azerty T AZERTY AZERTY
- // Get InputMethodSubtype's full display name in its locale.
- public static String getFullDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
- return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
- }
- return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale());
- }
-
- // Get InputMethodSubtype's middle display name in its locale.
- public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
- if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
- return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
- }
- return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(subtype.getLocale());
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
index 38164cb36..c41817fe6 100644
--- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
@@ -24,6 +24,12 @@ import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.text.style.URLSpan;
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
public final class SpannableStringUtils {
/**
* Copies the spans from the region <code>start...end</code> in
@@ -51,7 +57,7 @@ public final class SpannableStringUtils {
// of a word. But the spans have been split into two by the getText{Before,After}Cursor
// methods, so after concatenation they may end in the middle of a word.
// Since we don't use them, we can just remove them and avoid crashing.
- fl &= ~Spannable.SPAN_PARAGRAPH;
+ fl &= ~Spanned.SPAN_PARAGRAPH;
int st = source.getSpanStart(spans[i]);
int en = source.getSpanEnd(spans[i]);
@@ -125,4 +131,53 @@ public final class SpannableStringUtils {
final URLSpan[] spans = spanned.getSpans(startIndex - 1, endIndex + 1, URLSpan.class);
return null != spans && spans.length > 0;
}
+
+ /**
+ * Splits the given {@code charSequence} with at occurrences of the given {@code regex}.
+ * <p>
+ * This is equivalent to
+ * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)}
+ * except that the spans are preserved in the result array.
+ * </p>
+ * @param charSequence the character sequence to be split.
+ * @param regex the regex pattern to be used as the separator.
+ * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty
+ * segments. Otherwise, trailing empty segments will be removed before being returned.
+ * @return the array which contains the result. All the spans in the <code>charSequence</code>
+ * is preserved.
+ */
+ @UsedForTesting
+ public static CharSequence[] split(final CharSequence charSequence, final String regex,
+ final boolean preserveTrailingEmptySegments) {
+ // A short-cut for non-spanned strings.
+ if (!(charSequence instanceof Spanned)) {
+ // -1 means that trailing empty segments will be preserved.
+ return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0);
+ }
+
+ // Hereafter, emulate String.split for CharSequence.
+ final ArrayList<CharSequence> sequences = new ArrayList<>();
+ final Matcher matcher = Pattern.compile(regex).matcher(charSequence);
+ int nextStart = 0;
+ boolean matched = false;
+ while (matcher.find()) {
+ sequences.add(charSequence.subSequence(nextStart, matcher.start()));
+ nextStart = matcher.end();
+ matched = true;
+ }
+ if (!matched) {
+ // never matched. preserveTrailingEmptySegments is ignored in this case.
+ return new CharSequence[] { charSequence };
+ }
+ sequences.add(charSequence.subSequence(nextStart, charSequence.length()));
+ if (!preserveTrailingEmptySegments) {
+ for (int i = sequences.size() - 1; i >= 0; --i) {
+ if (!TextUtils.isEmpty(sequences.get(i))) {
+ break;
+ }
+ sequences.remove(i);
+ }
+ }
+ return sequences.toArray(new CharSequence[sequences.size()]);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
deleted file mode 100644
index 79128dbd2..000000000
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ /dev/null
@@ -1,646 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
-
-import android.text.Spanned;
-import android.text.TextUtils;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public final class StringUtils {
- 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
-
- private static final String EMPTY_STRING = "";
-
- private static final char CHAR_LINE_FEED = 0X000A;
- private static final char CHAR_VERTICAL_TAB = 0X000B;
- private static final char CHAR_FORM_FEED = 0X000C;
- private static final char CHAR_CARRIAGE_RETURN = 0X000D;
- private static final char CHAR_NEXT_LINE = 0X0085;
- private static final char CHAR_LINE_SEPARATOR = 0X2028;
- private static final char CHAR_PARAGRAPH_SEPARATOR = 0X2029;
-
- private StringUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static int codePointCount(final String text) {
- if (TextUtils.isEmpty(text)) return 0;
- return text.codePointCount(0, text.length());
- }
-
- public static String newSingleCodePointString(int codePoint) {
- if (Character.charCount(codePoint) == 1) {
- // Optimization: avoid creating a temporary array for characters that are
- // represented by a single char value
- return String.valueOf((char) codePoint);
- }
- // For surrogate pair
- return new String(Character.toChars(codePoint));
- }
-
- public static boolean containsInArray(final String text, final String[] array) {
- for (final String element : array) {
- if (text.equals(element)) return true;
- }
- return false;
- }
-
- /**
- * Comma-Splittable Text is similar to Comma-Separated Values (CSV) but has much simpler syntax.
- * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain
- * a comma character in it.
- */
- private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ",";
-
- public static boolean containsInCommaSplittableText(final String text,
- final String extraValues) {
- if (TextUtils.isEmpty(extraValues)) {
- return false;
- }
- return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
- }
-
- public static String removeFromCommaSplittableTextIfExists(final String text,
- final String extraValues) {
- if (TextUtils.isEmpty(extraValues)) {
- return EMPTY_STRING;
- }
- final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
- if (!containsInArray(text, elements)) {
- return extraValues;
- }
- final ArrayList<String> result = new ArrayList<>(elements.length - 1);
- for (final String element : elements) {
- if (!text.equals(element)) {
- result.add(element);
- }
- }
- return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
- }
-
- /**
- * Remove duplicates from an array of strings.
- *
- * This method will always keep the first occurrence of all strings at their position
- * in the array, removing the subsequent ones.
- */
- public static void removeDupes(final ArrayList<String> suggestions) {
- if (suggestions.size() < 2) return;
- int i = 1;
- // Don't cache suggestions.size(), since we may be removing items
- while (i < suggestions.size()) {
- final String cur = suggestions.get(i);
- // Compare each suggestion with each previous suggestion
- for (int j = 0; j < i; j++) {
- final String previous = suggestions.get(j);
- if (TextUtils.equals(cur, previous)) {
- suggestions.remove(i);
- i--;
- break;
- }
- }
- i++;
- }
- }
-
- public static String capitalizeFirstCodePoint(final String s, final Locale locale) {
- if (s.length() <= 1) {
- return toUpperCaseOfStringForLocale(s, true /* needsToUpperCase */, locale);
- }
- // Please refer to the comment below in
- // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings
- final int cutoff = s.offsetByCodePoints(0, 1);
- return toUpperCaseOfStringForLocale(
- s.substring(0, cutoff), true /* needsToUpperCase */, locale) + s.substring(cutoff);
- }
-
- public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) {
- if (s.length() <= 1) {
- return toUpperCaseOfStringForLocale(s, true /* needsToUpperCase */, locale);
- }
- // TODO: fix the bugs below
- // - It does not work for Serbian, because it fails to account for the "lj" character,
- // which should be "Lj" in title case and "LJ" in upper case.
- // - It does not work for Dutch, because it fails to account for the "ij" digraph when it's
- // written as two separate code points. They are two different characters but both should
- // be capitalized as "IJ" as if they were a single letter in most words (not all). If the
- // unicode char for the ligature is used however, it works.
- final int cutoff = s.offsetByCodePoints(0, 1);
- final String titleCaseFirstLetter = toUpperCaseOfStringForLocale(
- s.substring(0, cutoff), true /* needsToUpperCase */, locale);
- return titleCaseFirstLetter + s.substring(cutoff).toLowerCase(locale);
- }
-
- private static final int[] EMPTY_CODEPOINTS = {};
-
- public static int[] toCodePointArray(final CharSequence charSequence) {
- return toCodePointArray(charSequence, 0, charSequence.length());
- }
-
- /**
- * Converts a range of a string to an array of code points.
- * @param charSequence the source string.
- * @param startIndex the start index inside the string in java chars, inclusive.
- * @param endIndex the end index inside the string in java chars, exclusive.
- * @return a new array of code points. At most endIndex - startIndex, but possibly less.
- */
- public static int[] toCodePointArray(final CharSequence charSequence,
- final int startIndex, final int endIndex) {
- final int length = charSequence.length();
- if (length <= 0) {
- return EMPTY_CODEPOINTS;
- }
- final int[] codePoints =
- new int[Character.codePointCount(charSequence, startIndex, endIndex)];
- copyCodePointsAndReturnCodePointCount(codePoints, charSequence, startIndex, endIndex,
- false /* downCase */);
- return codePoints;
- }
-
- /**
- * Copies the codepoints in a CharSequence to an int array.
- *
- * This method assumes there is enough space in the array to store the code points. The size
- * can be measured with Character#codePointCount(CharSequence, int, int) before passing to this
- * method. If the int array is too small, an ArrayIndexOutOfBoundsException will be thrown.
- * Also, this method makes no effort to be thread-safe. Do not modify the CharSequence while
- * this method is running, or the behavior is undefined.
- * This method can optionally downcase code points before copying them, but it pays no attention
- * to locale while doing so.
- *
- * @param destination the int array.
- * @param charSequence the CharSequence.
- * @param startIndex the start index inside the string in java chars, inclusive.
- * @param endIndex the end index inside the string in java chars, exclusive.
- * @param downCase if this is true, code points will be downcased before being copied.
- * @return the number of copied code points.
- */
- public static int copyCodePointsAndReturnCodePointCount(final int[] destination,
- final CharSequence charSequence, final int startIndex, final int endIndex,
- final boolean downCase) {
- int destIndex = 0;
- for (int index = startIndex; index < endIndex;
- index = Character.offsetByCodePoints(charSequence, index, 1)) {
- final int codePoint = Character.codePointAt(charSequence, index);
- // TODO: stop using this, as it's not aware of the locale and does not always do
- // the right thing.
- destination[destIndex] = downCase ? Character.toLowerCase(codePoint) : codePoint;
- destIndex++;
- }
- return destIndex;
- }
-
- public static int[] toSortedCodePointArray(final String string) {
- final int[] codePoints = toCodePointArray(string);
- Arrays.sort(codePoints);
- return codePoints;
- }
-
- /**
- * Construct a String from a code point array
- *
- * @param codePoints a code point array that is null terminated when its logical length is
- * shorter than the array length.
- * @return a string constructed from the code point array.
- */
- public static String getStringFromNullTerminatedCodePointArray(final int[] codePoints) {
- int stringLength = codePoints.length;
- for (int i = 0; i < codePoints.length; i++) {
- if (codePoints[i] == 0) {
- stringLength = i;
- break;
- }
- }
- return new String(codePoints, 0 /* offset */, stringLength);
- }
-
- // This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE.
- public static int getCapitalizationType(final String text) {
- // If the first char is not uppercase, then the word is either all lower case or
- // camel case, and in either case we return CAPITALIZE_NONE.
- final int len = text.length();
- int index = 0;
- for (; index < len; index = text.offsetByCodePoints(index, 1)) {
- if (Character.isLetter(text.codePointAt(index))) {
- break;
- }
- }
- if (index == len) return CAPITALIZE_NONE;
- if (!Character.isUpperCase(text.codePointAt(index))) {
- return CAPITALIZE_NONE;
- }
- int capsCount = 1;
- int letterCount = 1;
- for (index = text.offsetByCodePoints(index, 1); index < len;
- index = text.offsetByCodePoints(index, 1)) {
- if (1 != capsCount && letterCount != capsCount) break;
- final int codePoint = text.codePointAt(index);
- if (Character.isUpperCase(codePoint)) {
- ++capsCount;
- ++letterCount;
- } else if (Character.isLetter(codePoint)) {
- // We need to discount non-letters since they may not be upper-case, but may
- // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME")
- ++letterCount;
- }
- }
- // We know the first char is upper case. So we want to test if either every letter other
- // than the first is lower case, or if they are all upper case. If the string is exactly
- // one char long, then we will arrive here with letterCount 1, and this is correct, too.
- if (1 == capsCount) return CAPITALIZE_FIRST;
- return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
- }
-
- public static boolean isIdenticalAfterUpcase(final String text) {
- final int length = text.length();
- int i = 0;
- while (i < length) {
- final int codePoint = text.codePointAt(i);
- if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) {
- return false;
- }
- i += Character.charCount(codePoint);
- }
- return true;
- }
-
- public static boolean isIdenticalAfterDowncase(final String text) {
- final int length = text.length();
- int i = 0;
- while (i < length) {
- final int codePoint = text.codePointAt(i);
- if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) {
- return false;
- }
- i += Character.charCount(codePoint);
- }
- return true;
- }
-
- public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
- final int[] sortedSeparators) {
- boolean needsCapsNext = true;
- final int len = text.length();
- for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
- final int codePoint = text.codePointAt(i);
- if (Character.isLetter(codePoint)) {
- if ((needsCapsNext && !Character.isUpperCase(codePoint))
- || (!needsCapsNext && !Character.isLowerCase(codePoint))) {
- return false;
- }
- }
- // We need a capital letter next if this is a separator.
- needsCapsNext = (Arrays.binarySearch(sortedSeparators, codePoint) >= 0);
- }
- return true;
- }
-
- // TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph
- // which should be capitalized together in *some* cases.
- public static String capitalizeEachWord(final String text, final int[] sortedSeparators,
- final Locale locale) {
- final StringBuilder builder = new StringBuilder();
- boolean needsCapsNext = true;
- final int len = text.length();
- for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
- final String nextChar = text.substring(i, text.offsetByCodePoints(i, 1));
- if (needsCapsNext) {
- builder.append(nextChar.toUpperCase(locale));
- } else {
- builder.append(nextChar.toLowerCase(locale));
- }
- // We need a capital letter next if this is a separator.
- needsCapsNext = (Arrays.binarySearch(sortedSeparators, nextChar.codePointAt(0)) >= 0);
- }
- return builder.toString();
- }
-
- /**
- * Approximates whether the text before the cursor looks like a URL.
- *
- * This is not foolproof, but it should work well in the practice.
- * Essentially it walks backward from the cursor until it finds something that's not a letter,
- * digit, or common URL symbol like underscore. If it hasn't found a period yet, then it
- * does not look like a URL.
- * If the text:
- * - starts with www and contains a period
- * - starts with a slash preceded by either a slash, whitespace, or start-of-string
- * Then it looks like a URL and we return true. Otherwise, we return false.
- *
- * Note: this method is called quite often, and should be fast.
- *
- * TODO: This will return that "abc./def" and ".abc/def" look like URLs to keep down the
- * code complexity, but ideally it should not. It's acceptable for now.
- */
- public static boolean lastPartLooksLikeURL(final CharSequence text) {
- int i = text.length();
- if (0 == i) return false;
- int wCount = 0;
- int slashCount = 0;
- boolean hasSlash = false;
- boolean hasPeriod = false;
- int codePoint = 0;
- while (i > 0) {
- codePoint = Character.codePointBefore(text, i);
- if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') {
- // Handwavy heuristic to see if that's a URL character. Anything between period
- // and z. This includes all lower- and upper-case ascii letters, period,
- // underscore, arrobase, question mark, equal sign. It excludes spaces, exclamation
- // marks, double quotes...
- // Anything that's not a URL-like character causes us to break from here and
- // evaluate normally.
- break;
- }
- if (Constants.CODE_PERIOD == codePoint) {
- hasPeriod = true;
- }
- if (Constants.CODE_SLASH == codePoint) {
- hasSlash = true;
- if (2 == ++slashCount) {
- return true;
- }
- } else {
- slashCount = 0;
- }
- if ('w' == codePoint) {
- ++wCount;
- } else {
- wCount = 0;
- }
- i = Character.offsetByCodePoints(text, i, -1);
- }
- // End of the text run.
- // If it starts with www and includes a period, then it looks like a URL.
- if (wCount >= 3 && hasPeriod) return true;
- // If it starts with a slash, and the code point before is whitespace, it looks like an URL.
- if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) return true;
- // If it has both a period and a slash, it looks like an URL.
- if (hasPeriod && hasSlash) return true;
- // Otherwise, it doesn't look like an URL.
- return false;
- }
-
- /**
- * Examines the string and returns whether we're inside a double quote.
- *
- * This is used to decide whether we should put an automatic space before or after a double
- * quote character. If we're inside a quotation, then we want to close it, so we want a space
- * after and not before. Otherwise, we want to open the quotation, so we want a space before
- * and not after. Exception: after a digit, we never want a space because the "inch" or
- * "minutes" use cases is dominant after digits.
- * In the practice, we determine whether we are in a quotation or not by finding the previous
- * double quote character, and looking at whether it's followed by whitespace. If so, that
- * was a closing quotation mark, so we're not inside a double quote. If it's not followed
- * by whitespace, then it was an opening quotation mark, and we're inside a quotation.
- *
- * @param text the text to examine.
- * @return whether we're inside a double quote.
- */
- public static boolean isInsideDoubleQuoteOrAfterDigit(final CharSequence text) {
- int i = text.length();
- if (0 == i) return false;
- int codePoint = Character.codePointBefore(text, i);
- if (Character.isDigit(codePoint)) return true;
- int prevCodePoint = 0;
- while (i > 0) {
- codePoint = Character.codePointBefore(text, i);
- if (Constants.CODE_DOUBLE_QUOTE == codePoint) {
- // If we see a double quote followed by whitespace, then that
- // was a closing quote.
- if (Character.isWhitespace(prevCodePoint)) return false;
- }
- if (Character.isWhitespace(codePoint) && Constants.CODE_DOUBLE_QUOTE == prevCodePoint) {
- // If we see a double quote preceded by whitespace, then that
- // was an opening quote. No need to continue seeking.
- return true;
- }
- i -= Character.charCount(codePoint);
- prevCodePoint = codePoint;
- }
- // We reached the start of text. If the first char is a double quote, then we're inside
- // a double quote. Otherwise we're not.
- return Constants.CODE_DOUBLE_QUOTE == codePoint;
- }
-
- public static boolean isEmptyStringOrWhiteSpaces(final String s) {
- final int N = codePointCount(s);
- for (int i = 0; i < N; ++i) {
- if (!Character.isWhitespace(s.codePointAt(i))) {
- return false;
- }
- }
- return true;
- }
-
- @UsedForTesting
- public static String byteArrayToHexString(final byte[] bytes) {
- if (bytes == null || bytes.length == 0) {
- return EMPTY_STRING;
- }
- final StringBuilder sb = new StringBuilder();
- for (byte b : bytes) {
- sb.append(String.format("%02x", b & 0xff));
- }
- return sb.toString();
- }
-
- /**
- * Convert hex string to byte array. The string length must be an even number.
- */
- @UsedForTesting
- public static byte[] hexStringToByteArray(final String hexString) {
- if (TextUtils.isEmpty(hexString)) {
- return null;
- }
- final int N = hexString.length();
- if (N % 2 != 0) {
- throw new NumberFormatException("Input hex string length must be an even number."
- + " Length = " + N);
- }
- final byte[] bytes = new byte[N / 2];
- for (int i = 0; i < N; i += 2) {
- bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
- + Character.digit(hexString.charAt(i + 1), 16));
- }
- return bytes;
- }
-
- private static final String LANGUAGE_GREEK = "el";
-
- private static Locale getLocaleUsedForToTitleCase(final Locale locale) {
- // In Greek locale {@link String#toUpperCase(Locale)} eliminates accents from its result.
- // In order to get accented upper case letter, {@link Locale#ROOT} should be used.
- if (LANGUAGE_GREEK.equals(locale.getLanguage())) {
- return Locale.ROOT;
- }
- return locale;
- }
-
- public static String toUpperCaseOfStringForLocale(final String text,
- final boolean needsToUpperCase, final Locale locale) {
- if (text == null || !needsToUpperCase) {
- return text;
- }
- return text.toUpperCase(getLocaleUsedForToTitleCase(locale));
- }
-
- public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase,
- final Locale locale) {
- if (!Constants.isLetterCode(code) || !needsToUpperCase) return code;
- final String text = newSingleCodePointString(code);
- final String casedText = toUpperCaseOfStringForLocale(
- text, needsToUpperCase, locale);
- return codePointCount(casedText) == 1
- ? 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;
- }
-
- /**
- * Splits the given {@code charSequence} with at occurrences of the given {@code regex}.
- * <p>
- * This is equivalent to
- * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)}
- * except that the spans are preserved in the result array.
- * </p>
- * @param input the character sequence to be split.
- * @param regex the regex pattern to be used as the separator.
- * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty
- * segments. Otherwise, trailing empty segments will be removed before being returned.
- * @return the array which contains the result. All the spans in the {@param input} is
- * preserved.
- */
- @UsedForTesting
- public static CharSequence[] split(final CharSequence charSequence, final String regex,
- final boolean preserveTrailingEmptySegments) {
- // A short-cut for non-spanned strings.
- if (!(charSequence instanceof Spanned)) {
- // -1 means that trailing empty segments will be preserved.
- return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0);
- }
-
- // Hereafter, emulate String.split for CharSequence.
- final ArrayList<CharSequence> sequences = new ArrayList<>();
- final Matcher matcher = Pattern.compile(regex).matcher(charSequence);
- int nextStart = 0;
- boolean matched = false;
- while (matcher.find()) {
- sequences.add(charSequence.subSequence(nextStart, matcher.start()));
- nextStart = matcher.end();
- matched = true;
- }
- if (!matched) {
- // never matched. preserveTrailingEmptySegments is ignored in this case.
- return new CharSequence[] { charSequence };
- }
- sequences.add(charSequence.subSequence(nextStart, charSequence.length()));
- if (!preserveTrailingEmptySegments) {
- for (int i = sequences.size() - 1; i >= 0; --i) {
- if (!TextUtils.isEmpty(sequences.get(i))) {
- break;
- }
- sequences.remove(i);
- }
- }
- return sequences.toArray(new CharSequence[sequences.size()]);
- }
-
- @UsedForTesting
- public static class Stringizer<E> {
- public String stringize(final E element) {
- return element != null ? element.toString() : "null";
- }
-
- @UsedForTesting
- public final String join(final E[] array) {
- return joinStringArray(toStringArray(array), null /* delimiter */);
- }
-
- @UsedForTesting
- public final String join(final E[] array, final String delimiter) {
- return joinStringArray(toStringArray(array), delimiter);
- }
-
- protected String[] toStringArray(final E[] array) {
- final String[] stringArray = new String[array.length];
- for (int index = 0; index < array.length; index++) {
- stringArray[index] = stringize(array[index]);
- }
- return stringArray;
- }
-
- protected String joinStringArray(final String[] stringArray, final String delimiter) {
- if (stringArray == null) {
- return "null";
- }
- if (delimiter == null) {
- return Arrays.toString(stringArray);
- }
- final StringBuilder sb = new StringBuilder();
- for (int index = 0; index < stringArray.length; index++) {
- sb.append(index == 0 ? "[" : delimiter);
- sb.append(stringArray[index]);
- }
- return sb + "]";
- }
- }
-
- /**
- * Returns whether the last composed word contains line-breaking character (e.g. CR or LF).
- * @param text the text to be examined.
- * @return {@code true} if the last composed word contains line-breaking separator.
- */
- @UsedForTesting
- public static boolean hasLineBreakCharacter(final String text) {
- if (TextUtils.isEmpty(text)) {
- return false;
- }
- for (int i = text.length() - 1; i >= 0; --i) {
- final char c = text.charAt(i);
- switch (c) {
- case CHAR_LINE_FEED:
- case CHAR_VERTICAL_TAB:
- case CHAR_FORM_FEED:
- case CHAR_CARRIAGE_RETURN:
- case CHAR_NEXT_LINE:
- case CHAR_LINE_SEPARATOR:
- case CHAR_PARAGRAPH_SEPARATOR:
- return true;
- }
- }
- return false;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 351d01400..54a3fc39c 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -16,8 +16,9 @@
package com.android.inputmethod.latin.utils;
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
-import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.COMBINING_RULES;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
+import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
import android.content.Context;
import android.content.res.Resources;
@@ -25,18 +26,25 @@ import android.os.Build;
import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.common.LocaleUtils;
+import com.android.inputmethod.latin.common.StringUtils;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * A helper class to deal with subtype locales.
+ */
+// TODO: consolidate this into RichInputMethodSubtype
public final class SubtypeLocaleUtils {
- private static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
+ static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
- // This reference class {@link Constants} must be located in the same package as LatinIME.java.
- private static final String RESOURCE_PACKAGE_NAME = Constants.class.getPackage().getName();
+ // This reference class {@link R} must be located in the same package as LatinIME.java.
+ private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
// Special language code to represent "no language".
public static final String NO_LANGUAGE = "zz";
@@ -47,11 +55,13 @@ public final class SubtypeLocaleUtils {
private static volatile boolean sInitialized = false;
private static final Object sInitializeLock = new Object();
private static Resources sResources;
- private static String[] sPredefinedKeyboardLayoutSet;
// Keyboard layout to its display name map.
private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>();
// Keyboard layout to subtype name resource id map.
private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>();
+ // Exceptional locale whose name should be displayed in Locale.ROOT.
+ private static final HashMap<String, Integer> sExceptionalLocaleDisplayedInRootLocale =
+ new HashMap<>();
// Exceptional locale to subtype name resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>();
// Exceptional locale to subtype name with layout resource id map.
@@ -65,6 +75,8 @@ public final class SubtypeLocaleUtils {
"string/subtype_with_layout_";
private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX =
"string/subtype_no_language_";
+ private static final String SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX =
+ "string/subtype_in_root_locale_";
// 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 =
@@ -89,7 +101,6 @@ public final class SubtypeLocaleUtils {
sResources = res;
final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts);
- sPredefinedKeyboardLayoutSet = predefinedLayoutSet;
final String[] layoutDisplayNames = res.getStringArray(
R.array.predefined_layout_display_names);
for (int i = 0; i < predefinedLayoutSet.length; i++) {
@@ -106,6 +117,15 @@ public final class SubtypeLocaleUtils {
sKeyboardLayoutToNameIdsMap.put(key, noLanguageResId);
}
+ final String[] exceptionalLocaleInRootLocale = res.getStringArray(
+ R.array.subtype_locale_displayed_in_root_locale);
+ for (int i = 0; i < exceptionalLocaleInRootLocale.length; i++) {
+ final String localeString = exceptionalLocaleInRootLocale[i];
+ final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + localeString;
+ final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME);
+ sExceptionalLocaleDisplayedInRootLocale.put(localeString, resId);
+ }
+
final String[] exceptionalLocales = res.getStringArray(
R.array.subtype_locale_exception_keys);
for (int i = 0; i < exceptionalLocales.length; i++) {
@@ -129,10 +149,6 @@ public final class SubtypeLocaleUtils {
}
}
- public static String[] getPredefinedKeyboardLayoutSet() {
- return sPredefinedKeyboardLayoutSet;
- }
-
public static boolean isExceptionalLocale(final String localeString) {
return sExceptionalLocaleToNameIdsMap.containsKey(localeString);
}
@@ -153,36 +169,58 @@ public final class SubtypeLocaleUtils {
return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId;
}
- private static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
+ @Nonnull
+ public static Locale getDisplayLocaleOfSubtypeLocale(@Nonnull final String localeString) {
if (NO_LANGUAGE.equals(localeString)) {
return sResources.getConfiguration().locale;
}
+ if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
+ return Locale.ROOT;
+ }
return LocaleUtils.constructLocaleFromString(localeString);
}
- public static String getSubtypeLocaleDisplayNameInSystemLocale(final String localeString) {
+ public static String getSubtypeLocaleDisplayNameInSystemLocale(
+ @Nonnull final String localeString) {
final Locale displayLocale = sResources.getConfiguration().locale;
return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
}
- public static String getSubtypeLocaleDisplayName(final String localeString) {
+ @Nonnull
+ public static String getSubtypeLocaleDisplayName(@Nonnull final String localeString) {
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
}
- public static String getSubtypeLanguageDisplayName(final String localeString) {
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ @Nonnull
+ public static String getSubtypeLanguageDisplayName(@Nonnull final String localeString) {
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
- return getSubtypeLocaleDisplayNameInternal(locale.getLanguage(), displayLocale);
+ final String languageString;
+ if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
+ languageString = localeString;
+ } else {
+ languageString = LocaleUtils.constructLocaleFromString(localeString).getLanguage();
+ }
+ return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale);
}
- private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
- final Locale displayLocale) {
+ @Nonnull
+ private static String getSubtypeLocaleDisplayNameInternal(@Nonnull final String localeString,
+ @Nonnull final Locale displayLocale) {
if (NO_LANGUAGE.equals(localeString)) {
// No language subtype should be displayed in system locale.
return sResources.getString(R.string.subtype_no_language);
}
- final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
+ final Integer exceptionalNameResId;
+ if (displayLocale.equals(Locale.ROOT)
+ && sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
+ exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(localeString);
+ } else if (sExceptionalLocaleToNameIdsMap.containsKey(localeString)) {
+ exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
+ } else {
+ exceptionalNameResId = null;
+ }
+
final String displayName;
if (exceptionalNameResId != null) {
final RunInLocale<String> getExceptionalName = new RunInLocale<String>() {
@@ -193,8 +231,8 @@ public final class SubtypeLocaleUtils {
};
displayName = getExceptionalName.runInLocale(sResources, displayLocale);
} else {
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
- displayName = locale.getDisplayName(displayLocale);
+ displayName = LocaleUtils.constructLocaleFromString(localeString)
+ .getDisplayName(displayLocale);
}
return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale);
}
@@ -217,31 +255,36 @@ public final class SubtypeLocaleUtils {
// en_US azerty T English (US) (AZERTY) exception
// zz azerty T Alphabet (AZERTY) in system locale
- private static String getReplacementString(final InputMethodSubtype subtype,
- final Locale displayLocale) {
+ @Nonnull
+ private static String getReplacementString(@Nonnull final InputMethodSubtype subtype,
+ @Nonnull final Locale displayLocale) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
&& subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
- } else {
- return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale);
}
+ return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale);
}
- public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static String getSubtypeDisplayNameInSystemLocale(
+ @Nonnull final InputMethodSubtype subtype) {
final Locale displayLocale = sResources.getConfiguration().locale;
return getSubtypeDisplayNameInternal(subtype, displayLocale);
}
- public static String getSubtypeNameForLogging(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static String getSubtypeNameForLogging(@Nullable final InputMethodSubtype subtype) {
if (subtype == null) {
return "<null subtype>";
}
return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype);
}
- private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
- final Locale displayLocale) {
+ @Nonnull
+ private static String getSubtypeDisplayNameInternal(@Nonnull final InputMethodSubtype subtype,
+ @Nonnull final Locale displayLocale) {
final String replacementString = getReplacementString(subtype, displayLocale);
+ // TODO: rework this for multi-lingual subtypes
final int nameResId = subtype.getNameResId();
final RunInLocale<String> getSubtypeName = new RunInLocale<String>() {
@Override
@@ -264,25 +307,25 @@ public final class SubtypeLocaleUtils {
getSubtypeName.runInLocale(sResources, displayLocale), displayLocale);
}
- public static boolean isNoLanguage(final InputMethodSubtype subtype) {
- final String localeString = subtype.getLocale();
- return NO_LANGUAGE.equals(localeString);
- }
-
- public static Locale getSubtypeLocale(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static Locale getSubtypeLocale(@Nonnull final InputMethodSubtype subtype) {
final String localeString = subtype.getLocale();
return LocaleUtils.constructLocaleFromString(localeString);
}
- public static String getKeyboardLayoutSetDisplayName(final InputMethodSubtype subtype) {
+ @Nonnull
+ public static String getKeyboardLayoutSetDisplayName(
+ @Nonnull final InputMethodSubtype subtype) {
final String layoutName = getKeyboardLayoutSetName(subtype);
return getKeyboardLayoutSetDisplayName(layoutName);
}
- public static String getKeyboardLayoutSetDisplayName(final String layoutName) {
+ @Nonnull
+ public static String getKeyboardLayoutSetDisplayName(@Nonnull final String layoutName) {
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
}
+ @Nonnull
public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) {
String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET);
if (keyboardLayoutSet == null) {
@@ -302,27 +345,7 @@ public final class SubtypeLocaleUtils {
return keyboardLayoutSet;
}
- // TODO: Get this information from the framework instead of maintaining here by ourselves.
- // Sorted list of known Right-To-Left language codes.
- private static final String[] SORTED_RTL_LANGUAGES = {
- "ar", // Arabic
- "fa", // Persian
- "iw", // Hebrew
- };
- static {
- Arrays.sort(SORTED_RTL_LANGUAGES);
- }
-
- public static boolean isRtlLanguage(final Locale locale) {
- final String language = locale.getLanguage();
- return Arrays.binarySearch(SORTED_RTL_LANGUAGES, language) >= 0;
- }
-
- 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);
+ return subtype.getExtraValueOf(COMBINING_RULES);
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
index 8cd49509f..981355115 100644
--- a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
+++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
@@ -22,7 +22,6 @@ import com.android.inputmethod.latin.define.ProductionFlags;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
-import java.util.Locale;
import java.util.TreeSet;
/**
@@ -30,22 +29,23 @@ import java.util.TreeSet;
* than its limit
*/
public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
- public final Locale mLocale;
public final ArrayList<SuggestedWordInfo> mRawSuggestions;
// TODO: Instead of a boolean , we may want to include the context of this suggestion results,
- // such as {@link PrevWordsInfo}.
+ // such as {@link NgramContext}.
public final boolean mIsBeginningOfSentence;
+ public final boolean mFirstSuggestionExceedsConfidenceThreshold;
private final int mCapacity;
- public SuggestionResults(final Locale locale, final int capacity,
- final boolean isBeginningOfSentence) {
- this(locale, sSuggestedWordInfoComparator, capacity, isBeginningOfSentence);
+ public SuggestionResults(final int capacity, final boolean isBeginningOfSentence,
+ final boolean firstSuggestionExceedsConfidenceThreshold) {
+ this(sSuggestedWordInfoComparator, capacity, isBeginningOfSentence,
+ firstSuggestionExceedsConfidenceThreshold);
}
- private SuggestionResults(final Locale locale, final Comparator<SuggestedWordInfo> comparator,
- final int capacity, final boolean isBeginningOfSentence) {
+ private SuggestionResults(final Comparator<SuggestedWordInfo> comparator, final int capacity,
+ final boolean isBeginningOfSentence,
+ final boolean firstSuggestionExceedsConfidenceThreshold) {
super(comparator);
- mLocale = locale;
mCapacity = capacity;
if (ProductionFlags.INCLUDE_RAW_SUGGESTIONS) {
mRawSuggestions = new ArrayList<>();
@@ -53,6 +53,7 @@ public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
mRawSuggestions = null;
}
mIsBeginningOfSentence = isBeginningOfSentence;
+ mFirstSuggestionExceedsConfidenceThreshold = firstSuggestionExceedsConfidenceThreshold;
}
@Override
@@ -70,8 +71,7 @@ public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
return super.addAll(e);
}
- private static final class SuggestedWordInfoComparator
- implements Comparator<SuggestedWordInfo> {
+ static final class SuggestedWordInfoComparator implements Comparator<SuggestedWordInfo> {
// This comparator ranks the word info with the higher frequency first. That's because
// that's the order we want our elements in.
@Override
diff --git a/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
index dd122b634..0bcba2754 100644
--- a/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
@@ -57,7 +57,7 @@ public final class ViewLayoutUtils {
public static void updateLayoutHeightOf(final Window window, final int layoutHeight) {
final WindowManager.LayoutParams params = window.getAttributes();
- if (params.height != layoutHeight) {
+ if (params != null && params.height != layoutHeight) {
params.height = layoutHeight;
window.setAttributes(params);
}
@@ -65,7 +65,7 @@ public final class ViewLayoutUtils {
public static void updateLayoutHeightOf(final View view, final int layoutHeight) {
final ViewGroup.LayoutParams params = view.getLayoutParams();
- if (params.height != layoutHeight) {
+ if (params != null && params.height != layoutHeight) {
params.height = layoutHeight;
view.setLayoutParams(params);
}
diff --git a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
new file mode 100644
index 000000000..fc0a9cb6c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
@@ -0,0 +1,106 @@
+/*
+ * 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 android.util.Log;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.common.StringUtils;
+import com.android.inputmethod.latin.define.DecoderSpecificConstants;
+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
+// rename this class or field name. See BinaryDictionary#addMultipleDictionaryEntriesNative().
+public final class WordInputEventForPersonalization {
+ private static final String TAG = WordInputEventForPersonalization.class.getSimpleName();
+ private static final boolean DEBUG_TOKEN = false;
+
+ public final int[] mTargetWord;
+ public final int mPrevWordsCount;
+ public final int[][] mPrevWordArray =
+ new int[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
+ public final boolean[] mIsPrevWordBeginningOfSentenceArray =
+ new boolean[DecoderSpecificConstants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ // Time stamp in seconds.
+ public final int mTimestamp;
+
+ @UsedForTesting
+ public WordInputEventForPersonalization(final CharSequence targetWord,
+ final NgramContext ngramContext, final int timestamp) {
+ mTargetWord = StringUtils.toCodePointArray(targetWord);
+ mPrevWordsCount = ngramContext.getPrevWordCount();
+ ngramContext.outputToArray(mPrevWordArray, mIsPrevWordBeginningOfSentenceArray);
+ mTimestamp = timestamp;
+ }
+
+ // Process a list of words and return a list of {@link WordInputEventForPersonalization}
+ // objects.
+ public static ArrayList<WordInputEventForPersonalization> createInputEventFrom(
+ final List<String> tokens, final int timestamp,
+ final SpacingAndPunctuations spacingAndPunctuations, final Locale locale) {
+ final ArrayList<WordInputEventForPersonalization> inputEvents = new ArrayList<>();
+ final int N = tokens.size();
+ NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO;
+ for (int i = 0; i < N; ++i) {
+ final String tempWord = tokens.get(i);
+ if (StringUtils.isEmptyStringOrWhiteSpaces(tempWord)) {
+ // just skip this token
+ if (DEBUG_TOKEN) {
+ Log.d(TAG, "--- isEmptyStringOrWhiteSpaces: \"" + tempWord + "\"");
+ }
+ continue;
+ }
+ if (!DictionaryInfoUtils.looksValidForDictionaryInsertion(
+ tempWord, spacingAndPunctuations)) {
+ if (DEBUG_TOKEN) {
+ Log.d(TAG, "--- not looksValidForDictionaryInsertion: \""
+ + tempWord + "\"");
+ }
+ // Sentence terminator found. Split.
+ // TODO: Detect whether the context is beginning-of-sentence.
+ ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO;
+ continue;
+ }
+ if (DEBUG_TOKEN) {
+ Log.d(TAG, "--- word: \"" + tempWord + "\"");
+ }
+ final WordInputEventForPersonalization inputEvent =
+ detectWhetherVaildWordOrNotAndGetInputEvent(
+ ngramContext, tempWord, timestamp, locale);
+ if (inputEvent == null) {
+ continue;
+ }
+ inputEvents.add(inputEvent);
+ ngramContext = ngramContext.getNextNgramContext(new NgramContext.WordInfo(tempWord));
+ }
+ return inputEvents;
+ }
+
+ private static WordInputEventForPersonalization detectWhetherVaildWordOrNotAndGetInputEvent(
+ final NgramContext ngramContext, final String targetWord, final int timestamp,
+ final Locale locale) {
+ if (locale == null) {
+ return null;
+ }
+ return new WordInputEventForPersonalization(targetWord, ngramContext, timestamp);
+ }
+}