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.java35
-rw-r--r--java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java10
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java51
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CollectionUtils.java36
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java36
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java35
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java25
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java51
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java222
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java15
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FragmentUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java187
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java11
-rw-r--r--java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java (renamed from java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java)33
-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/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.java631
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java77
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SuggestionResults.java15
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java118
29 files changed, 661 insertions, 1293 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..120cffbde 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -24,7 +24,6 @@ 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.
@@ -52,41 +51,9 @@ public final class AutoCorrectionUtils {
if (DBG) {
Log.d(TAG, "Auto corrected by S-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..23ffde2a2 100644
--- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
@@ -18,6 +18,7 @@ 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;
@@ -43,7 +44,6 @@ public final class BinaryDictionaryUtils {
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.
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
index 61292fc36..01f5e1079 100644
--- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
@@ -17,21 +17,32 @@
package com.android.inputmethod.latin.utils;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Map;
-import java.util.TreeMap;
+import java.util.Collection;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Utility methods for working with collections.
+ */
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();
- }
+ /**
+ * Converts a sub-range of the given array to an ArrayList of the appropriate type.
+ * @param array Array to be converted.
+ * @param start First index inclusive to be converted.
+ * @param end Last index exclusive to be converted.
+ * @throws IllegalArgumentException if start or end are out of range or start &gt; end.
+ */
+ @Nonnull
+ public static <E> ArrayList<E> arrayAsList(@Nonnull final E[] array, final int start,
+ final int end) {
if (start < 0 || start > end || end > array.length) {
- throw new IllegalArgumentException();
+ throw new IllegalArgumentException("Invalid start: " + start + " end: " + end
+ + " with array.length: " + array.length);
}
final ArrayList<E> list = new ArrayList<>(end - start);
@@ -40,4 +51,13 @@ public final class CollectionUtils {
}
return list;
}
+
+ /**
+ * Tests whether c contains no elements, true if c is null or c is empty.
+ * @param c Collection to test.
+ * @return Whether c contains no elements.
+ */
+ public static boolean isNullOrEmpty(@Nullable final Collection<?> c) {
+ return c == null || c.isEmpty();
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
index 34f59e8bc..8699f2ce7 100644
--- a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
@@ -17,6 +17,7 @@
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;
@@ -26,6 +27,8 @@ 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 NGRAM_TAG = "ngram";
+ public static final String NGRAM_PREV_WORD_TAG = "prev_word";
public static final String SHORTCUT_TAG = "shortcut";
public static final String PROBABILITY_TAG = "f";
public static final String HISTORICAL_INFO_TAG = "historicalInfo";
@@ -33,7 +36,8 @@ public class CombinedFormatUtils {
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,16 +62,16 @@ 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) {
+ if (wordProperty.mHasShortcuts) {
for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
builder.append(" " + SHORTCUT_TAG + "=" + shortcutTarget.mWord);
builder.append(",");
@@ -75,12 +79,20 @@ public class CombinedFormatUtils {
builder.append("\n");
}
}
- if (wordProperty.mBigrams != null) {
- for (final WeightedString bigram : wordProperty.mBigrams) {
- builder.append(" " + BIGRAM_TAG + "=" + bigram.mWord);
+ if (wordProperty.mHasNgrams) {
+ for (final NgramProperty ngramProperty : wordProperty.mNgrams) {
+ builder.append(" " + NGRAM_TAG + "=" + ngramProperty.mTargetWord.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.isNthPrevWordBeginningOfSontence(i + 1)) {
+ builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=true");
+ }
+ builder.append("\n");
+ }
}
}
return builder.toString();
@@ -100,4 +112,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
index 87df013a6..3a9705904 100644
--- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
@@ -16,7 +16,7 @@
package com.android.inputmethod.latin.utils;
-import java.util.Arrays;
+import javax.annotation.Nonnull;
public final class CoordinateUtils {
private static final int INDEX_X = 0;
@@ -27,32 +27,35 @@ public final class CoordinateUtils {
// This utility class is not publicly instantiable.
}
+ @Nonnull
public static int[] newInstance() {
return new int[ELEMENT_SIZE];
}
- public static int x(final int[] coords) {
+ public static int x(@Nonnull final int[] coords) {
return coords[INDEX_X];
}
- public static int y(final int[] coords) {
+ public static int y(@Nonnull final int[] coords) {
return coords[INDEX_Y];
}
- public static void set(final int[] coords, final int x, final int y) {
+ public static void set(@Nonnull 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) {
+ public static void copy(@Nonnull final int[] destination, @Nonnull final int[] source) {
destination[INDEX_X] = source[INDEX_X];
destination[INDEX_Y] = source[INDEX_Y];
}
+ @Nonnull
public static int[] newCoordinateArray(final int arraySize) {
return new int[ELEMENT_SIZE * arraySize];
}
+ @Nonnull
public static int[] newCoordinateArray(final int arraySize,
final int defaultX, final int defaultY) {
final int[] result = new int[ELEMENT_SIZE * arraySize];
@@ -62,30 +65,30 @@ public final class CoordinateUtils {
return result;
}
- public static int xFromArray(final int[] coordsArray, final int index) {
+ public static int xFromArray(@Nonnull final int[] coordsArray, final int index) {
return coordsArray[ELEMENT_SIZE * index + INDEX_X];
}
- public static int yFromArray(final int[] coordsArray, final int index) {
+ public static int yFromArray(@Nonnull 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);
+ @Nonnull
+ public static int[] coordinateFromArray(@Nonnull final int[] coordsArray, final int index) {
+ final int[] coords = newInstance();
+ set(coords, xFromArray(coordsArray, index), yFromArray(coordsArray, index));
+ return coords;
}
- public static void setXYInArray(final int[] coordsArray, final int index,
+ public static void setXYInArray(@Nonnull 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];
+ public static void setCoordinateInArray(@Nonnull final int[] coordsArray, final int index,
+ @Nonnull final int[] coords) {
+ setXYInArray(coordsArray, index, x(coords), y(coords));
}
}
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..24025b272 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -23,10 +23,11 @@ import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;
+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.common.Constants;
import com.android.inputmethod.latin.makedict.DictionaryHeader;
import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
@@ -229,6 +230,7 @@ public class DictionaryInfoUtils {
/**
* 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
*/
@@ -257,6 +259,7 @@ public class DictionaryInfoUtils {
/**
* Returns a main dictionary resource id
+ * @param res resources for the app
* @param locale dictionary locale
* @return main dictionary resource id
*/
@@ -282,6 +285,25 @@ public class DictionaryInfoUtils {
BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.getLanguage().toString();
}
+ /**
+ * Returns whether a main dictionary is readily available for this locale.
+ *
+ * This does not query the content provider.
+ *
+ * @param context context to open files upon
+ * @param locale dictionary locale
+ * @return true if a dictionary is available right away, false otherwise
+ */
+ public static boolean hasReadilyAvailableMainDictionaryForLocale(final Context context,
+ final Locale locale) {
+ final Resources res = context.getResources();
+ if (0 != getMainDictionaryResourceIdIfAvailableForLocale(res, locale)) {
+ return true;
+ }
+ final String fileName = getCacheFileName(getMainDictId(locale), locale.toString(), context);
+ return new File(fileName).exists();
+ }
+
public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file) {
return getDictionaryFileHeaderOrNull(file, 0, file.length());
}
@@ -382,6 +404,7 @@ public class DictionaryInfoUtils {
return dictList;
}
+ @UsedForTesting
public static boolean looksValidForDictionaryInsertion(final CharSequence text,
final SpacingAndPunctuations spacingAndPunctuations) {
if (TextUtils.isEmpty(text)) return false;
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index 787e4a59d..525212c96 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -16,38 +16,77 @@
package com.android.inputmethod.latin.utils;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.NgramContext;
+
import java.util.List;
import java.util.Locale;
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.latin.PrevWordsInfo;
+import javax.annotation.Nonnull;
public interface DistracterFilter {
/**
* Determine whether a word is a distracter to words in dictionaries.
*
- * @param prevWordsInfo the information of previous words.
+ * @param ngramContext the n-gram context
* @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,
+ public boolean isDistracterToWordsInDictionaries(final NgramContext ngramContext,
final String testedWord, final Locale locale);
+ @UsedForTesting
+ public int getWordHandlingType(final NgramContext ngramContext, final String testedWord,
+ final Locale locale);
+
public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
public void close();
+ public static final class HandlingType {
+ private final static int REQUIRE_NO_SPECIAL_HANDLINGS = 0x0;
+ private final static int SHOULD_BE_LOWER_CASED = 0x1;
+ private final static int SHOULD_BE_HANDLED_AS_OOV = 0x2;
+
+ public static int getHandlingType(final boolean shouldBeLowerCased, final boolean isOov) {
+ int wordHandlingType = HandlingType.REQUIRE_NO_SPECIAL_HANDLINGS;
+ if (shouldBeLowerCased) {
+ wordHandlingType |= HandlingType.SHOULD_BE_LOWER_CASED;
+ }
+ if (isOov) {
+ wordHandlingType |= HandlingType.SHOULD_BE_HANDLED_AS_OOV;
+ }
+ return wordHandlingType;
+ }
+
+ public static boolean shouldBeLowerCased(final int handlingType) {
+ return (handlingType & SHOULD_BE_LOWER_CASED) != 0;
+ }
+
+ public static boolean shouldBeHandledAsOov(final int handlingType) {
+ return (handlingType & SHOULD_BE_HANDLED_AS_OOV) != 0;
+ }
+ }
+
+ @Nonnull
public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() {
@Override
- public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
+ public boolean isDistracterToWordsInDictionaries(NgramContext ngramContext,
String testedWord, Locale locale) {
return false;
}
@Override
+ public int getWordHandlingType(final NgramContext ngramContext,
+ final String testedWord, final Locale locale) {
+ return HandlingType.REQUIRE_NO_SPECIAL_HANDLINGS;
+ }
+
+ @Override
public void close() {
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
index 27973287d..9c6a94810 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java
@@ -20,13 +20,14 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ConcurrentHashMap;
import android.content.Context;
import android.content.res.Resources;
import android.text.InputType;
import android.util.Log;
import android.util.LruCache;
+import android.util.Pair;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -34,9 +35,12 @@ 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.DictionaryFacilitatorLruCache;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.common.StringUtils;
import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
/**
@@ -48,21 +52,22 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
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 static final int MAX_DICTIONARY_FACILITATOR_CACHE_SIZE = 3;
+ private static final int MAX_DISTRACTERS_CACHE_SIZE = 1024;
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 ConcurrentHashMap<Locale, InputMethodSubtype> mLocaleToSubtypeCache;
+ private final ConcurrentHashMap<Locale, Keyboard> mLocaleToKeyboardCache;
+ private final DictionaryFacilitatorLruCache mDictionaryFacilitatorLruCache;
+ // The key is a pair of a locale and a word. The value indicates the word is a distracter to
+ // words of the locale.
+ private final LruCache<Pair<Locale, String>, Boolean> mDistractersCache;
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
+ // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter 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
+ // become a distracter, 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;
@@ -73,16 +78,19 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
*/
public DistracterFilterCheckingExactMatchesAndSuggestions(final Context context) {
mContext = context;
- mLocaleToSubtypeMap = new HashMap<>();
- mLocaleToKeyboardMap = new HashMap<>();
- mDictionaryFacilitator = new DictionaryFacilitator();
+ mLocaleToSubtypeCache = new ConcurrentHashMap<>();
+ mLocaleToKeyboardCache = new ConcurrentHashMap<>();
+ mDictionaryFacilitatorLruCache = new DictionaryFacilitatorLruCache(context,
+ MAX_DICTIONARY_FACILITATOR_CACHE_SIZE, "" /* dictionaryNamePrefix */);
mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
- mKeyboard = null;
}
@Override
public void close() {
- mDictionaryFacilitator.closeDictionaries();
+ mLocaleToSubtypeCache.clear();
+ mLocaleToKeyboardCache.clear();
+ mDictionaryFacilitatorLruCache.evictAll();
+ // Don't clear mDistractersCache.
}
@Override
@@ -99,29 +107,36 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
newLocaleToSubtypeMap.put(locale, subtype);
}
}
- if (mLocaleToSubtypeMap.equals(newLocaleToSubtypeMap)) {
+ if (mLocaleToSubtypeCache.equals(newLocaleToSubtypeMap)) {
// Enabled subtypes have not been changed.
return;
}
- synchronized (mLock) {
- mLocaleToSubtypeMap.clear();
- mLocaleToSubtypeMap.putAll(newLocaleToSubtypeMap);
- mLocaleToKeyboardMap.clear();
+ // Update subtype and keyboard map for locales that are in the current mapping.
+ for (final Locale locale: mLocaleToSubtypeCache.keySet()) {
+ if (newLocaleToSubtypeMap.containsKey(locale)) {
+ final InputMethodSubtype newSubtype = newLocaleToSubtypeMap.remove(locale);
+ if (newSubtype.equals(newLocaleToSubtypeMap.get(locale))) {
+ // Mapping has not been changed.
+ continue;
+ }
+ mLocaleToSubtypeCache.replace(locale, newSubtype);
+ } else {
+ mLocaleToSubtypeCache.remove(locale);
+ }
+ mLocaleToKeyboardCache.remove(locale);
}
+ // Add locales that are not in the current mapping.
+ mLocaleToSubtypeCache.putAll(newLocaleToSubtypeMap);
}
- private void loadKeyboardForLocale(final Locale newLocale) {
- final Keyboard cachedKeyboard = mLocaleToKeyboardMap.get(newLocale);
+ private Keyboard getKeyboardForLocale(final Locale locale) {
+ final Keyboard cachedKeyboard = mLocaleToKeyboardCache.get(locale);
if (cachedKeyboard != null) {
- mKeyboard = cachedKeyboard;
- return;
- }
- final InputMethodSubtype subtype;
- synchronized (mLock) {
- subtype = mLocaleToSubtypeMap.get(newLocale);
+ return cachedKeyboard;
}
+ final InputMethodSubtype subtype = mLocaleToSubtypeCache.get(locale);
if (subtype == null) {
- return;
+ return null;
}
final EditorInfo editorInfo = new EditorInfo();
editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
@@ -131,59 +146,41 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
- builder.setSubtype(subtype);
+ builder.setSubtype(new RichInputMethodSubtype(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);
+ final Keyboard newKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
+ mLocaleToKeyboardCache.put(locale, newKeyboard);
+ return newKeyboard;
}
/**
* Determine whether a word is a distracter to words in dictionaries.
*
- * @param prevWordsInfo the information of previous words. Not used for now.
+ * @param ngramContext the n-gram context. 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,
+ public boolean isDistracterToWordsInDictionaries(final NgramContext ngramContext,
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 (!mLocaleToSubtypeCache.containsKey(locale)) {
+ Log.e(TAG, "Locale " + locale + " is not enabled.");
+ // TODO: Investigate what we should do for disabled locales.
+ return false;
}
-
+ final DictionaryFacilitator dictionaryFacilitator =
+ mDictionaryFacilitatorLruCache.get(locale);
if (DEBUG) {
Log.d(TAG, "testedWord: " + testedWord);
}
- final Boolean isCachedDistracter = mDistractersCache.get(testedWord);
+ final Pair<Locale, String> cacheKey = new Pair<>(locale, testedWord);
+ final Boolean isCachedDistracter = mDistractersCache.get(cacheKey);
if (isCachedDistracter != null && isCachedDistracter) {
if (DEBUG) {
Log.d(TAG, "isDistracter: true (cache hit)");
@@ -192,37 +189,38 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
}
final boolean isDistracterCheckedByGetMaxFreqencyOfExactMatches =
- checkDistracterUsingMaxFreqencyOfExactMatches(testedWord);
+ checkDistracterUsingMaxFreqencyOfExactMatches(dictionaryFacilitator, testedWord);
if (isDistracterCheckedByGetMaxFreqencyOfExactMatches) {
- // Add the word to the cache.
- mDistractersCache.put(testedWord, Boolean.TRUE);
+ // Add the pair of locale and word to the cache.
+ mDistractersCache.put(cacheKey, Boolean.TRUE);
return true;
}
- final boolean isValidWord = mDictionaryFacilitator.isValidWord(testedWord,
- false /* ignoreCase */);
- if (isValidWord) {
- // Valid word is not a distractor.
+ final boolean Word = dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */);
+ if (Word) {
+ // Valid word is not a distracter.
if (DEBUG) {
Log.d(TAG, "isDistracter: false (valid word)");
}
return false;
}
+ final Keyboard keyboard = getKeyboardForLocale(locale);
final boolean isDistracterCheckedByGetSuggestion =
- checkDistracterUsingGetSuggestions(testedWord);
+ checkDistracterUsingGetSuggestions(dictionaryFacilitator, keyboard, testedWord);
if (isDistracterCheckedByGetSuggestion) {
- // Add the word to the cache.
- mDistractersCache.put(testedWord, Boolean.TRUE);
+ // Add the pair of locale and word to the cache.
+ mDistractersCache.put(cacheKey, Boolean.TRUE);
return true;
}
return false;
}
- private boolean checkDistracterUsingMaxFreqencyOfExactMatches(final String testedWord) {
+ private static boolean checkDistracterUsingMaxFreqencyOfExactMatches(
+ final DictionaryFacilitator dictionaryFacilitator, 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 int perfectMatchFreq = dictionaryFacilitator.getFrequency(testedWord);
+ final int exactMatchFreq = dictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
final boolean isDistracter = perfectMatchFreq < exactMatchFreq;
if (DEBUG) {
Log.d(TAG, "perfectMatchFreq: " + perfectMatchFreq);
@@ -232,8 +230,10 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
return isDistracter;
}
- private boolean checkDistracterUsingGetSuggestions(final String testedWord) {
- if (mKeyboard == null) {
+ private boolean checkDistracterUsingGetSuggestions(
+ final DictionaryFacilitator dictionaryFacilitator, final Keyboard keyboard,
+ final String testedWord) {
+ if (keyboard == null) {
return false;
}
final SettingsValuesForSuggestion settingsValuesForSuggestion =
@@ -246,24 +246,25 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
testedWord;
final WordComposer composer = new WordComposer();
final int[] codePoints = StringUtils.toCodePointArray(testedWord);
-
+ final int[] coordinates = keyboard.getCoordinates(codePoints);
+ composer.setComposingWord(codePoints, coordinates);
+ final SuggestionResults suggestionResults;
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(),
+ suggestionResults = dictionaryFacilitator.getSuggestionResults(composer,
+ NgramContext.EMPTY_PREV_WORDS_INFO,
+ keyboard.getProximityInfo().getNativeProximityInfo(),
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;
}
+ if (suggestionResults.isEmpty()) {
+ return false;
+ }
+ final SuggestedWordInfo firstSuggestion = suggestionResults.first();
+ final boolean isDistracter = suggestionExceedsDistracterThreshold(
+ firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
+ if (DEBUG) {
+ Log.d(TAG, "isDistracter: " + isDistracter);
+ }
+ return isDistracter;
}
private static boolean suggestionExceedsDistracterThreshold(final SuggestedWordInfo suggestion,
@@ -283,4 +284,41 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr
}
return false;
}
+
+ private boolean shouldBeLowerCased(final NgramContext ngramContext, final String testedWord,
+ final Locale locale) {
+ final DictionaryFacilitator dictionaryFacilitator =
+ mDictionaryFacilitatorLruCache.get(locale);
+ if (dictionaryFacilitator.isValidWord(testedWord, false /* ignoreCase */)) {
+ return false;
+ }
+ final String lowerCaseTargetWord = testedWord.toLowerCase(locale);
+ if (testedWord.equals(lowerCaseTargetWord)) {
+ return false;
+ }
+ if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
+ return true;
+ }
+ if (StringUtils.getCapitalizationType(testedWord) == StringUtils.CAPITALIZE_FIRST
+ && !ngramContext.isValid()) {
+ // TODO: Check beginning-of-sentence.
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int getWordHandlingType(final NgramContext ngramContext, final String testedWord,
+ final Locale locale) {
+ // TODO: Use this method for user history dictionary.
+ if (testedWord == null|| locale == null) {
+ return HandlingType.getHandlingType(false /* shouldBeLowerCased */, false /* isOov */);
+ }
+ final boolean shouldBeLowerCased = shouldBeLowerCased(ngramContext, testedWord, locale);
+ final String caseModifiedWord =
+ shouldBeLowerCased ? testedWord.toLowerCase(locale) : testedWord;
+ final boolean isOov = !mDictionaryFacilitatorLruCache.get(locale).isValidWord(
+ caseModifiedWord, false /* ignoreCase */);
+ return HandlingType.getHandlingType(shouldBeLowerCased, isOov);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
index 4ad4ba784..4c99fed9f 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
@@ -22,7 +22,7 @@ import java.util.Locale;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.PrevWordsInfo;
+import com.android.inputmethod.latin.NgramContext;
public class DistracterFilterCheckingIsInDictionary implements DistracterFilter {
private final DistracterFilter mDistracterFilter;
@@ -35,16 +35,21 @@ public class DistracterFilterCheckingIsInDictionary implements DistracterFilter
}
@Override
- public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
+ public boolean isDistracterToWordsInDictionaries(NgramContext ngramContext,
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);
}
+ return mDistracterFilter.isDistracterToWordsInDictionaries(
+ ngramContext, testedWord, locale);
+ }
+
+ @Override
+ public int getWordHandlingType(final NgramContext ngramContext, final String testedWord,
+ final Locale locale) {
+ return mDistracterFilter.getWordHandlingType(ngramContext, testedWord, locale);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
index 61da1b789..e77f6fd40 100644
--- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
@@ -27,7 +27,7 @@ import java.util.concurrent.ThreadFactory;
* Utilities to manage executors.
*/
public class ExecutorUtils {
- private static final ConcurrentHashMap<String, ExecutorService> sExecutorMap =
+ static final ConcurrentHashMap<String, ExecutorService> sExecutorMap =
new ConcurrentHashMap<>();
private static class ThreadFactoryWithId implements ThreadFactory {
@@ -49,7 +49,7 @@ public class ExecutorUtils {
public static ExecutorService getExecutor(final String id) {
ExecutorService executor = sExecutorMap.get(id);
if (executor == null) {
- synchronized(sExecutorMap) {
+ synchronized (sExecutorMap) {
executor = sExecutorMap.get(id);
if (executor == null) {
executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithId(id));
@@ -65,7 +65,7 @@ public class ExecutorUtils {
*/
@UsedForTesting
public static void shutdownAllExecutors() {
- synchronized(sExecutorMap) {
+ synchronized (sExecutorMap) {
for (final ExecutorService executor : sExecutorMap.values()) {
executor.execute(new Runnable() {
@Override
diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
index c2167a76b..ae2de44c7 100644
--- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
@@ -18,6 +18,7 @@ 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;
@@ -42,6 +43,7 @@ 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());
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
index ea406fa75..142548b25 100644
--- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -53,7 +53,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 +85,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;
}
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/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/PrevWordsInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java
index 3cd63612c..7d2ddd268 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java
@@ -16,15 +16,18 @@
package com.android.inputmethod.latin.utils;
+import com.android.inputmethod.latin.NgramContext;
+import com.android.inputmethod.latin.NgramContext.WordInfo;
+import com.android.inputmethod.latin.common.Constants;
+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.
}
@@ -43,7 +46,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 +54,13 @@ 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;
+ if (prev == null) return NgramContext.EMPTY_PREV_WORDS_INFO;
final String[] w = SPACE_REGEX.split(prev);
final WordInfo[] prevWordsInfo = new WordInfo[Constants.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 +71,36 @@ 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.
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.
final char lastChar = focusedWord.charAt(length - 1);
if (spacingAndPunctuations.isSentenceSeparator(lastChar)) {
- prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE;
+ 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/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 1781924ac..000000000
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ /dev/null
@@ -1,631 +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 s.toUpperCase(locale);
- }
- // Please refer to the comment below in
- // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings
- final int cutoff = s.offsetByCodePoints(0, 1);
- return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff);
- }
-
- public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) {
- if (s.length() <= 1) {
- return s.toUpperCase(locale);
- }
- // TODO: fix the bugs below
- // - This does not work for Greek, because it returns upper case instead of title case.
- // - 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);
- return s.substring(0, cutoff).toUpperCase(locale) + 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;
- }
-
- public static String toUpperCaseOfStringForLocale(final String text,
- final boolean needsToUpperCase, final Locale locale) {
- if (text == null || !needsToUpperCase) return text;
- return text.toUpperCase(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..55c1dc9e5 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,23 @@ 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.RichInputMethodSubtype;
+import com.android.inputmethod.latin.common.StringUtils;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
+/**
+ * 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";
@@ -52,6 +58,9 @@ public final class SubtypeLocaleUtils {
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 +74,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 =
@@ -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++) {
@@ -153,10 +173,13 @@ public final class SubtypeLocaleUtils {
return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId;
}
- private static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
+ public static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
if (NO_LANGUAGE.equals(localeString)) {
return sResources.getConfiguration().locale;
}
+ if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
+ return Locale.ROOT;
+ }
return LocaleUtils.constructLocaleFromString(localeString);
}
@@ -171,9 +194,15 @@ public final class SubtypeLocaleUtils {
}
public static String getSubtypeLanguageDisplayName(final String localeString) {
- final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
- return getSubtypeLocaleDisplayNameInternal(locale.getLanguage(), displayLocale);
+ final String languageString;
+ if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
+ languageString = localeString;
+ } else {
+ final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ languageString = locale.getLanguage();
+ }
+ return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale);
}
private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
@@ -182,7 +211,16 @@ public final class SubtypeLocaleUtils {
// 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>() {
@@ -222,9 +260,8 @@ public final class SubtypeLocaleUtils {
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) {
@@ -242,6 +279,7 @@ public final class SubtypeLocaleUtils {
private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
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,11 +302,6 @@ 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) {
final String localeString = subtype.getLocale();
return LocaleUtils.constructLocaleFromString(localeString);
@@ -283,6 +316,10 @@ public final class SubtypeLocaleUtils {
return sKeyboardLayoutToDisplayNameMap.get(layoutName);
}
+ public static String getKeyboardLayoutSetName(final RichInputMethodSubtype subtype) {
+ return getKeyboardLayoutSetName(subtype.getRawSubtype());
+ }
+
public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) {
String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET);
if (keyboardLayoutSet == null) {
@@ -318,11 +355,7 @@ public final class SubtypeLocaleUtils {
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..b319aeb8a 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,19 @@ 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;
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) {
+ this(sSuggestedWordInfoComparator, capacity, isBeginningOfSentence);
}
- private SuggestionResults(final Locale locale, final Comparator<SuggestedWordInfo> comparator,
+ private SuggestionResults(final Comparator<SuggestedWordInfo> comparator,
final int capacity, final boolean isBeginningOfSentence) {
super(comparator);
- mLocale = locale;
mCapacity = capacity;
if (ProductionFlags.INCLUDE_RAW_SUGGESTIONS) {
mRawSuggestions = new ArrayList<>();
@@ -70,8 +66,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..86a5b19ec
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java
@@ -0,0 +1,118 @@
+/*
+ * 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.Constants;
+import com.android.inputmethod.latin.common.StringUtils;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+import com.android.inputmethod.latin.utils.DistracterFilter.HandlingType;
+
+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[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
+ public final boolean[] mIsPrevWordBeginningOfSentenceArray =
+ new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ public final boolean mIsValid;
+ // Time stamp in seconds.
+ public final int mTimestamp;
+
+ @UsedForTesting
+ public WordInputEventForPersonalization(final CharSequence targetWord,
+ final NgramContext ngramContext, final boolean isValid, final int timestamp) {
+ mTargetWord = StringUtils.toCodePointArray(targetWord);
+ mPrevWordsCount = ngramContext.getPrevWordCount();
+ ngramContext.outputToArray(mPrevWordArray, mIsPrevWordBeginningOfSentenceArray);
+ mIsValid = isValid;
+ 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 DistracterFilter distracterFilter) {
+ 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, distracterFilter);
+ 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, final DistracterFilter distracterFilter) {
+ if (locale == null) {
+ return null;
+ }
+ final int wordHandlingType = distracterFilter.getWordHandlingType(ngramContext,
+ targetWord, locale);
+ final String word = HandlingType.shouldBeLowerCased(wordHandlingType) ?
+ targetWord.toLowerCase(locale) : targetWord;
+ if (distracterFilter.isDistracterToWordsInDictionaries(ngramContext, targetWord, locale)) {
+ // The word is a distracter.
+ return null;
+ }
+ return new WordInputEventForPersonalization(word, ngramContext,
+ !HandlingType.shouldBeHandledAsOov(wordHandlingType), timestamp);
+ }
+}