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.java182
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java12
-rw-r--r--java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java63
-rw-r--r--java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java138
-rw-r--r--java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java49
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java81
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java72
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CollectionUtils.java83
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java103
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java46
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CsvUtils.java9
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DialogUtils.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java68
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java58
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java129
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java59
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java80
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FileUtils.java54
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FragmentUtils.java4
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java120
-rw-r--r--java/src/com/android/inputmethod/latin/utils/JsonUtils.java103
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java187
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java77
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java (renamed from java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java)20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LocaleUtils.java49
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java103
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java151
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java50
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResourceUtils.java67
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RunInLocale.java20
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ScriptUtils.java141
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java58
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java22
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StatsUtils.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java320
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java113
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SuggestionResults.java84
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java17
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TextRange.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java40
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java84
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java293
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java182
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java233
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java137
47 files changed, 2298 insertions, 1759 deletions
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index d87f6f3c4..db7f2a56c 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -17,53 +17,73 @@
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 android.os.Build;
import android.text.TextUtils;
+import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
-import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import java.util.ArrayList;
+import java.util.Arrays;
public final class AdditionalSubtypeUtils {
+ private static final String TAG = AdditionalSubtypeUtils.class.getSimpleName();
+
private static final InputMethodSubtype[] EMPTY_SUBTYPE_ARRAY = new InputMethodSubtype[0];
private AdditionalSubtypeUtils() {
// This utility class is not publicly instantiable.
}
+ @UsedForTesting
public static boolean isAdditionalSubtype(final InputMethodSubtype subtype) {
return subtype.containsExtraValueKey(IS_ADDITIONAL_SUBTYPE);
}
private static final String LOCALE_AND_LAYOUT_SEPARATOR = ":";
+ private static final int INDEX_OF_LOCALE = 0;
+ private static final int INDEX_OF_KEYBOARD_LAYOUT = 1;
+ private static final int INDEX_OF_EXTRA_VALUE = 2;
+ private static final int LENGTH_WITHOUT_EXTRA_VALUE = (INDEX_OF_KEYBOARD_LAYOUT + 1);
+ private static final int LENGTH_WITH_EXTRA_VALUE = (INDEX_OF_EXTRA_VALUE + 1);
private static final String PREF_SUBTYPE_SEPARATOR = ";";
- public static InputMethodSubtype createAdditionalSubtype(final String localeString,
- final String keyboardLayoutSetName, final String extraValue) {
- final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName;
- final String layoutDisplayNameExtraValue;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
- && SubtypeLocaleUtils.isExceptionalLocale(localeString)) {
- final String layoutDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(
- keyboardLayoutSetName);
- layoutDisplayNameExtraValue = StringUtils.appendToCommaSplittableTextIfNotExists(
- UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + layoutDisplayName, extraValue);
- } else {
- layoutDisplayNameExtraValue = extraValue;
- }
- final String additionalSubtypeExtraValue =
- StringUtils.appendToCommaSplittableTextIfNotExists(
- IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue);
+ private static InputMethodSubtype createAdditionalSubtypeInternal(
+ final String localeString, final String keyboardLayoutSetName,
+ final boolean isAsciiCapable, final boolean isEmojiCapable) {
final int nameId = SubtypeLocaleUtils.getSubtypeNameId(localeString, keyboardLayoutSetName);
- return buildInputMethodSubtype(
- nameId, localeString, layoutExtraValue, additionalSubtypeExtraValue);
+ final String platformVersionDependentExtraValues = getPlatformVersionDependentExtraValue(
+ localeString, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable);
+ final int platformVersionIndependentSubtypeId =
+ getPlatformVersionIndependentSubtypeId(localeString, keyboardLayoutSetName);
+ // NOTE: In KitKat and later, InputMethodSubtypeBuilder#setIsAsciiCapable is also available.
+ // TODO: Use InputMethodSubtypeBuilder#setIsAsciiCapable when appropriate.
+ return InputMethodSubtypeCompatUtils.newInputMethodSubtype(nameId,
+ R.drawable.ic_ime_switcher_dark, localeString, KEYBOARD_MODE,
+ platformVersionDependentExtraValues,
+ false /* isAuxiliary */, false /* overrideImplicitlyEnabledSubtype */,
+ platformVersionIndependentSubtypeId);
+ }
+
+ public static InputMethodSubtype createDummyAdditionalSubtype(
+ final String localeString, final String keyboardLayoutSetName) {
+ return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName,
+ false /* isAsciiCapable */, false /* isEmojiCapable */);
+ }
+
+ public static InputMethodSubtype createAsciiEmojiCapableAdditionalSubtype(
+ final String localeString, final String keyboardLayoutSetName) {
+ return createAdditionalSubtypeInternal(localeString, keyboardLayoutSetName,
+ true /* isAsciiCapable */, true /* isEmojiCapable */);
}
public static String getPrefSubtype(final InputMethodSubtype subtype) {
@@ -79,26 +99,26 @@ public final class AdditionalSubtypeUtils {
: basePrefSubtype + LOCALE_AND_LAYOUT_SEPARATOR + extraValue;
}
- public static InputMethodSubtype createAdditionalSubtype(final String prefSubtype) {
- final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
- if (elems.length < 2 || elems.length > 3) {
- throw new RuntimeException("Unknown additional subtype specified: " + prefSubtype);
- }
- final String localeString = elems[0];
- final String keyboardLayoutSetName = elems[1];
- final String extraValue = (elems.length == 3) ? elems[2] : null;
- return createAdditionalSubtype(localeString, keyboardLayoutSetName, extraValue);
- }
-
public static InputMethodSubtype[] createAdditionalSubtypesArray(final String prefSubtypes) {
if (TextUtils.isEmpty(prefSubtypes)) {
return EMPTY_SUBTYPE_ARRAY;
}
final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR);
- final ArrayList<InputMethodSubtype> subtypesList =
- CollectionUtils.newArrayList(prefSubtypeArray.length);
+ final ArrayList<InputMethodSubtype> subtypesList = new ArrayList<>(prefSubtypeArray.length);
for (final String prefSubtype : prefSubtypeArray) {
- final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
+ final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR);
+ if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE
+ && elems.length != LENGTH_WITH_EXTRA_VALUE) {
+ Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype + " in "
+ + prefSubtypes);
+ continue;
+ }
+ final String localeString = elems[INDEX_OF_LOCALE];
+ final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT];
+ // Here we assume that all the additional subtypes have AsciiCapable and EmojiCapable.
+ // This is actually what the setting dialog for additional subtype is doing.
+ final InputMethodSubtype subtype = createAsciiEmojiCapableAdditionalSubtype(
+ localeString, keyboardLayoutSetName);
if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
// layout has been removed.
@@ -137,31 +157,81 @@ public final class AdditionalSubtypeUtils {
return sb.toString();
}
- private static InputMethodSubtype buildInputMethodSubtype(int nameId, String localeString,
- String layoutExtraValue, String additionalSubtypeExtraValue) {
- // CAVEAT! If you want to change subtypeId after changing the extra values,
- // you must change "getInputMethodSubtypeId". But it will remove the additional keyboard
- // from the current users. So, you should be really careful to change it.
- final int subtypeId = getInputMethodSubtypeId(nameId, localeString, layoutExtraValue,
- additionalSubtypeExtraValue);
- final String extraValue;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- extraValue = layoutExtraValue + "," + additionalSubtypeExtraValue
- + "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
- + "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
- } else {
- extraValue = layoutExtraValue + "," + additionalSubtypeExtraValue;
+ /**
+ * Returns the extra value that is optimized for the running OS.
+ * <p>
+ * Historically the extra value has been used as the last resort to annotate various kinds of
+ * attributes. Some of these attributes are valid only on some platform versions. Thus we cannot
+ * assume that the extra values stored in a persistent storage are always valid. We need to
+ * regenerate the extra value on the fly instead.
+ * </p>
+ * @param localeString the locale string (e.g., "en_US").
+ * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak").
+ * @param isAsciiCapable true when ASCII characters are supported with this layout.
+ * @param isEmojiCapable true when Unicode Emoji characters are supported with this layout.
+ * @return extra value that is optimized for the running OS.
+ * @see #getPlatformVersionIndependentSubtypeId(String, String)
+ */
+ private static String getPlatformVersionDependentExtraValue(final String localeString,
+ final String keyboardLayoutSetName, final boolean isAsciiCapable,
+ final boolean isEmojiCapable) {
+ final ArrayList<String> extraValueItems = new ArrayList<>();
+ extraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName);
+ if (isAsciiCapable) {
+ extraValueItems.add(ASCII_CAPABLE);
}
- return InputMethodSubtypeCompatUtils.newInputMethodSubtype(nameId,
- R.drawable.ic_ime_switcher_dark, localeString, KEYBOARD_MODE, extraValue,
- false, false, subtypeId);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
+ SubtypeLocaleUtils.isExceptionalLocale(localeString)) {
+ extraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" +
+ SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName));
+ }
+ if (isEmojiCapable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ extraValueItems.add(EMOJI_CAPABLE);
+ }
+ extraValueItems.add(IS_ADDITIONAL_SUBTYPE);
+ return TextUtils.join(",", extraValueItems);
}
- private static int getInputMethodSubtypeId(int nameId, String localeString,
- String layoutExtraValue, String additionalSubtypeExtraValue) {
- // TODO: Use InputMethodSubtypeBuilder once we use SDK version 19.
- return (new InputMethodSubtype(nameId, R.drawable.ic_ime_switcher_dark,
- localeString, KEYBOARD_MODE, layoutExtraValue + "," + additionalSubtypeExtraValue,
- false, false)).hashCode();
+ /**
+ * Returns the subtype ID that is supposed to be compatible between different version of OSes.
+ * <p>
+ * From the compatibility point of view, it is important to keep subtype id predictable and
+ * stable between different OSes. For this purpose, the calculation code in this method is
+ * carefully chosen and then fixed. Treat the following code as no more or less than a
+ * hash function. Each component to be hashed can be different from the corresponding value
+ * that is used to instantiate {@link InputMethodSubtype} actually.
+ * For example, you don't need to update <code>compatibilityExtraValueItems</code> in this
+ * method even when we need to add some new extra values for the actual instance of
+ * {@link InputMethodSubtype}.
+ * </p>
+ * @param localeString the locale string (e.g., "en_US").
+ * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak").
+ * @return a platform-version independent subtype ID.
+ * @see #getPlatformVersionDependentExtraValue(String, String, boolean, boolean)
+ */
+ private static int getPlatformVersionIndependentSubtypeId(final String localeString,
+ final String keyboardLayoutSetName) {
+ // For compatibility reasons, we concatenate the extra values in the following order.
+ // - KeyboardLayoutSet
+ // - AsciiCapable
+ // - UntranslatableReplacementStringInSubtypeName
+ // - EmojiCapable
+ // - isAdditionalSubtype
+ final ArrayList<String> compatibilityExtraValueItems = new ArrayList<>();
+ compatibilityExtraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName);
+ compatibilityExtraValueItems.add(ASCII_CAPABLE);
+ if (SubtypeLocaleUtils.isExceptionalLocale(localeString)) {
+ compatibilityExtraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" +
+ SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName));
+ }
+ compatibilityExtraValueItems.add(EMOJI_CAPABLE);
+ compatibilityExtraValueItems.add(IS_ADDITIONAL_SUBTYPE);
+ final String compatibilityExtraValues = TextUtils.join(",", compatibilityExtraValueItems);
+ return Arrays.hashCode(new Object[] {
+ localeString,
+ KEYBOARD_MODE,
+ compatibilityExtraValues,
+ false /* isAuxiliary */,
+ false /* overrideImplicitlyEnabledSubtype */ });
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
index 08a2a8c5a..7a4150def 100644
--- a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
@@ -31,7 +31,7 @@ public final class ApplicationUtils {
// This utility class is not publicly instantiable.
}
- public static int getAcitivityTitleResId(final Context context,
+ public static int getActivityTitleResId(final Context context,
final Class<? extends Activity> cls) {
final ComponentName cn = new ComponentName(context, cls);
try {
@@ -62,4 +62,22 @@ public final class ApplicationUtils {
}
return "";
}
+
+ /**
+ * A utility method to get the application's PackageInfo.versionCode
+ * @return the application's PackageInfo.versionCode
+ */
+ public static int getVersionCode(final Context context) {
+ try {
+ if (context == null) {
+ return 0;
+ }
+ final String packageName = context.getPackageName();
+ final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+ return info.versionCode;
+ } catch (final NameNotFoundException e) {
+ Log.e(TAG, "Could not find version info.", e);
+ }
+ return 0;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
index c2e97a36f..d12aad639 100644
--- a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
+++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java
@@ -20,7 +20,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
- * This class is a holder of a result of asynchronous computation.
+ * This class is a holder of the result of an asynchronous computation.
*
* @param <E> the type of the result.
*/
@@ -36,9 +36,9 @@ public class AsyncResultHolder<E> {
}
/**
- * Sets the result value to this holder.
+ * Sets the result value of this holder.
*
- * @param result the value which is set.
+ * @param result the value to set.
*/
public void set(final E result) {
synchronized(mLock) {
@@ -54,12 +54,12 @@ public class AsyncResultHolder<E> {
* Causes the current thread to wait unless the value is set or the specified time is elapsed.
*
* @param defaultValue the default value.
- * @param timeOut the time to wait.
- * @return if the result is set until the time limit then the result, otherwise defaultValue.
+ * @param timeOut the maximum time to wait.
+ * @return if the result is set before the time limit then the result, otherwise defaultValue.
*/
public E get(final E defaultValue, final long timeOut) {
try {
- if(mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
+ if (mLatch.await(timeOut, TimeUnit.MILLISECONDS)) {
return mResult;
} else {
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 066c5fd32..34ee2152a 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -16,16 +16,10 @@
package com.android.inputmethod.latin.utils;
-import com.android.inputmethod.latin.BinaryDictionary;
-import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.Suggest;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import android.text.TextUtils;
import android.util.Log;
-import java.util.concurrent.ConcurrentHashMap;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
public final class AutoCorrectionUtils {
private static final boolean DBG = LatinImeLogger.sDBG;
@@ -36,58 +30,18 @@ public final class AutoCorrectionUtils {
// Purely static class: can't instantiate.
}
- public static boolean isValidWord(final Suggest suggest, final String word,
- final boolean ignoreCase) {
- if (TextUtils.isEmpty(word)) {
- return false;
- }
- final ConcurrentHashMap<String, Dictionary> dictionaries = suggest.getUnigramDictionaries();
- final String lowerCasedWord = word.toLowerCase(suggest.mLocale);
- for (final String key : dictionaries.keySet()) {
- final Dictionary dictionary = dictionaries.get(key);
- // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
- // managing to get null in here. Presumably the language is changing to a language with
- // no main dictionary and the monkey manages to type a whole word before the thread
- // that reads the dictionary is started or something?
- // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and
- // would be immutable once it's finished initializing, but concretely a null test is
- // probably good enough for the time being.
- if (null == dictionary) continue;
- if (dictionary.isValidWord(word)
- || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
- return true;
- }
- }
- return false;
- }
-
- public static int getMaxFrequency(final ConcurrentHashMap<String, Dictionary> dictionaries,
- final String word) {
- if (TextUtils.isEmpty(word)) {
- return Dictionary.NOT_A_PROBABILITY;
- }
- int maxFreq = -1;
- for (final String key : dictionaries.keySet()) {
- final Dictionary dictionary = dictionaries.get(key);
- if (null == dictionary) continue;
- final int tempFreq = dictionary.getFrequency(word);
- if (tempFreq >= maxFreq) {
- maxFreq = tempFreq;
- }
- }
- return maxFreq;
- }
-
public static boolean suggestionExceedsAutoCorrectionThreshold(
final SuggestedWordInfo suggestion, final String consideredWord,
final float autoCorrectionThreshold) {
if (null != suggestion) {
// Shortlist a whitelisted word
- if (suggestion.mKind == SuggestedWordInfo.KIND_WHITELIST) return true;
+ if (suggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) {
+ return true;
+ }
final int autoCorrectionSuggestionScore = suggestion.mScore;
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
- final float normalizedScore = BinaryDictionary.calcNormalizedScore(
+ final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
consideredWord, suggestion.mWord, autoCorrectionSuggestionScore);
if (DBG) {
Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + ","
@@ -118,9 +72,8 @@ public final class AutoCorrectionUtils {
if (typedWordLength < MINIMUM_SAFETY_NET_CHAR_LENGTH) {
return false;
}
- final int maxEditDistanceOfNativeDictionary =
- (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1;
- final int distance = BinaryDictionary.editDistance(typedWord, suggestion);
+ final int maxEditDistanceOfNativeDictionary = (typedWordLength / 2) + 1;
+ final int distance = BinaryDictionaryUtils.editDistance(typedWord, suggestion);
if (DBG) {
Log.d(TAG, "Autocorrected edit distance = " + distance
+ ", " + maxEditDistanceOfNativeDictionary);
diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
new file mode 100644
index 000000000..5d7deba15
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java
@@ -0,0 +1,138 @@
+/*
+ * 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 com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.personalization.PersonalizationHelper;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class BinaryDictionaryUtils {
+ private static final String TAG = BinaryDictionaryUtils.class.getSimpleName();
+
+ private BinaryDictionaryUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ static {
+ JniUtils.loadNativeLibrary();
+ }
+
+ 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)
+ throws IOException, UnsupportedFormatException {
+ return getHeaderWithOffsetAndLength(dictFile, 0 /* offset */, dictFile.length());
+ }
+
+ public static DictionaryHeader getHeaderWithOffsetAndLength(final File dictFile,
+ final long offset, final long length) throws IOException, UnsupportedFormatException {
+ // dictType is never used for reading the header. Passing an empty string.
+ final BinaryDictionary binaryDictionary = new BinaryDictionary(
+ dictFile.getAbsolutePath(), offset, length,
+ true /* useFullEditDistance */, null /* locale */, "" /* dictType */,
+ false /* isUpdatable */);
+ final DictionaryHeader header = binaryDictionary.getHeader();
+ binaryDictionary.close();
+ if (header == null) {
+ throw new IOException();
+ }
+ return header;
+ }
+
+ public static boolean renameDict(final File dictFile, final File newDictFile) {
+ if (dictFile.isFile()) {
+ return dictFile.renameTo(newDictFile);
+ } else if (dictFile.isDirectory()) {
+ final String dictName = dictFile.getName();
+ final String newDictName = newDictFile.getName();
+ if (newDictFile.exists()) {
+ return false;
+ }
+ for (final File file : dictFile.listFiles()) {
+ if (!file.isFile()) {
+ continue;
+ }
+ final String fileName = file.getName();
+ final String newFileName = fileName.replaceFirst(
+ Pattern.quote(dictName), Matcher.quoteReplacement(newDictName));
+ if (!file.renameTo(new File(dictFile, newFileName))) {
+ return false;
+ }
+ }
+ return dictFile.renameTo(newDictFile);
+ }
+ return false;
+ }
+
+ @UsedForTesting
+ public static boolean createEmptyDictFile(final String filePath, final long dictVersion,
+ final Locale locale, final Map<String, String> attributeMap) {
+ final String[] keyArray = new String[attributeMap.size()];
+ final String[] valueArray = new String[attributeMap.size()];
+ int index = 0;
+ for (final String key : attributeMap.keySet()) {
+ keyArray[index] = key;
+ valueArray[index] = attributeMap.get(key);
+ index++;
+ }
+ return createEmptyDictFileNative(filePath, dictVersion, locale.toString(), keyArray,
+ valueArray);
+ }
+
+ public static float calcNormalizedScore(final String before, final String after,
+ final int score) {
+ return calcNormalizedScoreNative(StringUtils.toCodePointArray(before),
+ 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.
+ * In test mode, set timestamp is used as the current time in the native code.
+ * If currentTime < 0, quit the test mode and returns to using time() to get the current time.
+ *
+ * @param currentTime seconds since the unix epoch
+ * @return current time got in the native code.
+ */
+ @UsedForTesting
+ public static int setCurrentTimeForTest(final int currentTime) {
+ final int currentNativeTimestamp = setCurrentTimeForTestNative(currentTime);
+ PersonalizationHelper.currentTimeChangedForTesting(currentNativeTimestamp);
+ return currentNativeTimestamp;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java
deleted file mode 100644
index ae1fd3f79..000000000
--- a/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java
+++ /dev/null
@@ -1,49 +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 com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.TreeSet;
-
-/**
- * A TreeSet that is bounded in size and throws everything that's smaller than its limit
- */
-public final class BoundedTreeSet extends TreeSet<SuggestedWordInfo> {
- private final int mCapacity;
- public BoundedTreeSet(final Comparator<SuggestedWordInfo> comparator, final int capacity) {
- super(comparator);
- mCapacity = capacity;
- }
-
- @Override
- public boolean add(final SuggestedWordInfo e) {
- if (size() < mCapacity) return super.add(e);
- if (comparator().compare(e, last()) > 0) return false;
- super.add(e);
- pollLast(); // removes the last element
- return true;
- }
-
- @Override
- public boolean addAll(final Collection<? extends SuggestedWordInfo> e) {
- if (null == e) return false;
- return super.addAll(e);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java b/java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java
deleted file mode 100644
index 2028298f2..000000000
--- a/java/src/com/android/inputmethod/latin/utils/ByteArrayDictBuffer.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer;
-
-/**
- * This class provides an implementation for the FusionDictionary buffer interface that is backed
- * by a simpled byte array. It allows to create a binary dictionary in memory.
- */
-public final class ByteArrayDictBuffer implements DictBuffer {
- private byte[] mBuffer;
- private int mPosition;
-
- public ByteArrayDictBuffer(final byte[] buffer) {
- mBuffer = buffer;
- mPosition = 0;
- }
-
- @Override
- public int readUnsignedByte() {
- return mBuffer[mPosition++] & 0xFF;
- }
-
- @Override
- public int readUnsignedShort() {
- final int retval = readUnsignedByte();
- return (retval << 8) + readUnsignedByte();
- }
-
- @Override
- public int readUnsignedInt24() {
- final int retval = readUnsignedShort();
- return (retval << 8) + readUnsignedByte();
- }
-
- @Override
- public int readInt() {
- final int retval = readUnsignedShort();
- return (retval << 16) + readUnsignedShort();
- }
-
- @Override
- public int position() {
- return mPosition;
- }
-
- @Override
- public void position(int position) {
- mPosition = position;
- }
-
- @Override
- public void put(final byte b) {
- mBuffer[mPosition++] = b;
- }
-
- @Override
- public int limit() {
- return mBuffer.length - 1;
- }
-
- @Override
- public int capacity() {
- return mBuffer.length;
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 3d4404a98..936219332 100644
--- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -21,7 +21,7 @@ import android.text.TextUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import java.util.Locale;
@@ -62,6 +62,22 @@ public final class CapsModeUtils {
}
/**
+ * Helper method to find out if a code point is starting punctuation.
+ *
+ * This include the Unicode START_PUNCTUATION category, but also some other symbols that are
+ * starting, like the inverted question mark or the double quote.
+ *
+ * @param codePoint the code point
+ * @return true if it's starting punctuation, false otherwise.
+ */
+ private static boolean isStartPunctuation(final int codePoint) {
+ return (codePoint == Constants.CODE_DOUBLE_QUOTE || codePoint == Constants.CODE_SINGLE_QUOTE
+ || codePoint == Constants.CODE_INVERTED_QUESTION_MARK
+ || codePoint == Constants.CODE_INVERTED_EXCLAMATION_MARK
+ || Character.getType(codePoint) == Character.START_PUNCTUATION);
+ }
+
+ /**
* Determine what caps mode should be in effect at the current offset in
* the text. Only the mode bits set in <var>reqModes</var> will be
* checked. Note that the caps mode flags here are explicitly defined
@@ -74,7 +90,7 @@ public final class CapsModeUtils {
* @param reqModes The modes to be checked: may be any combination of
* {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and
* {@link TextUtils#CAP_MODE_SENTENCES}.
- * @param settingsValues The current settings values.
+ * @param spacingAndPunctuations The current spacing and punctuations settings.
* @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs
*
* @return Returns the actual capitalization modes that can be in effect
@@ -83,7 +99,7 @@ public final class CapsModeUtils {
* {@link TextUtils#CAP_MODE_SENTENCES}.
*/
public static int getCapsMode(final CharSequence cs, final int reqModes,
- final SettingsValues settingsValues, final boolean hasSpaceBefore) {
+ final SpacingAndPunctuations spacingAndPunctuations, final boolean hasSpaceBefore) {
// Quick description of what we want to do:
// CAP_MODE_CHARACTERS is always on.
// CAP_MODE_WORDS is on if there is some whitespace before the cursor.
@@ -115,8 +131,7 @@ public final class CapsModeUtils {
} else {
for (i = cs.length(); i > 0; i--) {
final char c = cs.charAt(i - 1);
- if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE
- && Character.getType(c) != Character.START_PUNCTUATION) {
+ if (!isStartPunctuation(c)) {
break;
}
}
@@ -139,6 +154,20 @@ public final class CapsModeUtils {
j--;
}
if (j <= 0 || Character.isWhitespace(prevChar)) {
+ if (spacingAndPunctuations.mUsesGermanRules) {
+ // In German typography rules, there is a specific case that the first character
+ // of a new line should not be capitalized if the previous line ends in a comma.
+ boolean hasNewLine = false;
+ while (--j >= 0 && Character.isWhitespace(prevChar)) {
+ if (Constants.CODE_ENTER == prevChar) {
+ hasNewLine = true;
+ }
+ prevChar = cs.charAt(j);
+ }
+ if (Constants.CODE_COMMA == prevChar && hasNewLine) {
+ return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
+ }
+ }
// There are only spacing chars between the start of the paragraph and the cursor,
// defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both
// MODE_WORDS and MODE_SENTENCES should be active.
@@ -167,8 +196,7 @@ public final class CapsModeUtils {
// No other language has such a rule as far as I know, instead putting inside the quotation
// mark as the exact thing quoted and handling the surrounding punctuation independently,
// e.g. <<Did he say, "let's go home"?>>
- // Hence, specifically for English, we treat this special case here.
- if (Locale.ENGLISH.getLanguage().equals(settingsValues.mLocale.getLanguage())) {
+ if (spacingAndPunctuations.mUsesAmericanTypography) {
for (; j > 0; j--) {
// Here we look to go over any closing punctuation. This is because in dominant
// variants of English, the final period is placed within double quotes and maybe
@@ -191,17 +219,20 @@ public final class CapsModeUtils {
if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) {
return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes;
}
- if (settingsValues.mSentenceSeparator != c || j <= 0) {
+ if (!spacingAndPunctuations.isSentenceSeparator(c) || j <= 0) {
return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
}
// We found out that we have a period. We need to determine if this is a full stop or
// otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation
- // looks like (\w\.){2,}
+ // looks like (\w\.){2,}. Moreover, in German, you put periods after digits for dates
+ // and some other things, and in German specifically we need to not go into autocaps after
+ // a whitespace-digits-period sequence.
// To find out, we will have a simple state machine with the following states :
- // START, WORD, PERIOD, ABBREVIATION
+ // START, WORD, PERIOD, ABBREVIATION, NUMBER
// On START : (just before the first period)
// letter => WORD
+ // digit => NUMBER if German; end with caps otherwise
// whitespace => end with no caps (it was a stand-alone period)
// otherwise => end with caps (several periods/symbols in a row)
// On WORD : (within the word just before the first period)
@@ -215,6 +246,11 @@ public final class CapsModeUtils {
// letter => LETTER
// period => PERIOD
// otherwise => end with no caps (it was an abbreviation)
+ // On NUMBER : (period immediately preceded by one or more digits)
+ // digit => NUMBER
+ // letter => LETTER (promote to word)
+ // otherwise => end with no caps (it was a whitespace-digits-period sequence,
+ // or a punctuation-digits-period sequence like "11.11.")
// "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This
// should capitalize.
@@ -222,6 +258,7 @@ public final class CapsModeUtils {
final int WORD = 1;
final int PERIOD = 2;
final int LETTER = 3;
+ final int NUMBER = 4;
final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS
| TextUtils.CAP_MODE_SENTENCES) & reqModes;
final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes;
@@ -234,6 +271,8 @@ public final class CapsModeUtils {
state = WORD;
} else if (Character.isWhitespace(c)) {
return noCaps;
+ } else if (Character.isDigit(c) && spacingAndPunctuations.mUsesGermanRules) {
+ state = NUMBER;
} else {
return caps;
}
@@ -241,7 +280,7 @@ public final class CapsModeUtils {
case WORD:
if (Character.isLetter(c)) {
state = WORD;
- } else if (settingsValues.mSentenceSeparator == c) {
+ } else if (spacingAndPunctuations.isSentenceSeparator(c)) {
state = PERIOD;
} else {
return caps;
@@ -257,11 +296,20 @@ public final class CapsModeUtils {
case LETTER:
if (Character.isLetter(c)) {
state = LETTER;
- } else if (settingsValues.mSentenceSeparator == c) {
+ } else if (spacingAndPunctuations.isSentenceSeparator(c)) {
state = PERIOD;
} else {
return noCaps;
}
+ break;
+ case NUMBER:
+ if (Character.isLetter(c)) {
+ state = WORD;
+ } else if (Character.isDigit(c)) {
+ state = NUMBER;
+ } else {
+ return noCaps;
+ }
}
}
// Here we arrived at the start of the line. This should behave exactly like whitespace.
diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
index cc25102ce..e3aef29ba 100644
--- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
@@ -16,90 +16,33 @@
package com.android.inputmethod.latin.utils;
-import android.util.SparseArray;
-
-import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.WeakHashMap;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
public final class CollectionUtils {
private CollectionUtils() {
// This utility class is not publicly instantiable.
}
- public static <K,V> HashMap<K,V> newHashMap() {
- return new HashMap<K,V>();
- }
-
- public static <K, V> WeakHashMap<K, V> newWeakHashMap() {
- return new WeakHashMap<K, V>();
- }
-
- public static <K,V> TreeMap<K,V> newTreeMap() {
- return new TreeMap<K,V>();
- }
-
public static <K, V> Map<K,V> newSynchronizedTreeMap() {
- final TreeMap<K,V> treeMap = newTreeMap();
+ final TreeMap<K,V> treeMap = new TreeMap<>();
return Collections.synchronizedMap(treeMap);
}
- public static <K,V> ConcurrentHashMap<K,V> newConcurrentHashMap() {
- return new ConcurrentHashMap<K,V>();
- }
-
- public static <E> HashSet<E> newHashSet() {
- return new HashSet<E>();
- }
-
- public static <E> TreeSet<E> newTreeSet() {
- return new TreeSet<E>();
- }
-
- public static <E> ArrayList<E> newArrayList() {
- return new ArrayList<E>();
- }
-
- public static <E> ArrayList<E> newArrayList(final int initialCapacity) {
- return new ArrayList<E>(initialCapacity);
- }
-
- public static <E> ArrayList<E> newArrayList(final Collection<E> collection) {
- return new ArrayList<E>(collection);
- }
-
- public static <E> LinkedList<E> newLinkedList() {
- return new LinkedList<E>();
- }
-
- public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList() {
- return new CopyOnWriteArrayList<E>();
- }
-
- public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(
- final Collection<E> collection) {
- return new CopyOnWriteArrayList<E>(collection);
- }
-
- public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(final E[] array) {
- return new CopyOnWriteArrayList<E>(array);
- }
-
- public static <E> ArrayDeque<E> newArrayDeque() {
- return new ArrayDeque<E>();
- }
+ public static <E> ArrayList<E> arrayAsList(final E[] array, final int start, final int end) {
+ if (array == null) {
+ throw new NullPointerException();
+ }
+ if (start < 0 || start > end || end > array.length) {
+ throw new IllegalArgumentException();
+ }
- public static <E> SparseArray<E> newSparseArray() {
- return new SparseArray<E>();
+ final ArrayList<E> list = new ArrayList<>(end - start);
+ for (int i = start; i < end; i++) {
+ list.add(array[i]);
+ }
+ return list;
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
new file mode 100644
index 000000000..34f59e8bc
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
@@ -0,0 +1,103 @@
+/*
+ * 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 com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.ProbabilityInfo;
+import com.android.inputmethod.latin.makedict.WeightedString;
+import com.android.inputmethod.latin.makedict.WordProperty;
+
+import java.util.HashMap;
+
+public class CombinedFormatUtils {
+ public static final String DICTIONARY_TAG = "dictionary";
+ public static final String BIGRAM_TAG = "bigram";
+ public static final String SHORTCUT_TAG = "shortcut";
+ public static final String PROBABILITY_TAG = "f";
+ public static final String HISTORICAL_INFO_TAG = "historicalInfo";
+ public static final String HISTORICAL_INFO_SEPARATOR = ":";
+ public static final String WORD_TAG = "word";
+ public static final String BEGINNING_OF_SENTENCE_TAG = "beginning_of_sentence";
+ public static final String NOT_A_WORD_TAG = "not_a_word";
+ public static final String BLACKLISTED_TAG = "blacklisted";
+
+ public static String formatAttributeMap(final HashMap<String, String> attributeMap) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(DICTIONARY_TAG + "=");
+ if (attributeMap.containsKey(DictionaryHeader.DICTIONARY_ID_KEY)) {
+ builder.append(attributeMap.get(DictionaryHeader.DICTIONARY_ID_KEY));
+ }
+ for (final String key : attributeMap.keySet()) {
+ if (key.equals(DictionaryHeader.DICTIONARY_ID_KEY)) {
+ continue;
+ }
+ final String value = attributeMap.get(key);
+ builder.append("," + key + "=" + value);
+ }
+ builder.append("\n");
+ return builder.toString();
+ }
+
+ public static String formatWordProperty(final WordProperty wordProperty) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(" " + WORD_TAG + "=" + wordProperty.mWord);
+ builder.append(",");
+ builder.append(formatProbabilityInfo(wordProperty.mProbabilityInfo));
+ if (wordProperty.mIsBeginningOfSentence) {
+ builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=true");
+ }
+ if (wordProperty.mIsNotAWord) {
+ builder.append("," + NOT_A_WORD_TAG + "=true");
+ }
+ if (wordProperty.mIsBlacklistEntry) {
+ builder.append("," + BLACKLISTED_TAG + "=true");
+ }
+ builder.append("\n");
+ if (wordProperty.mShortcutTargets != null) {
+ for (final WeightedString shortcutTarget : wordProperty.mShortcutTargets) {
+ builder.append(" " + SHORTCUT_TAG + "=" + shortcutTarget.mWord);
+ builder.append(",");
+ builder.append(formatProbabilityInfo(shortcutTarget.mProbabilityInfo));
+ builder.append("\n");
+ }
+ }
+ if (wordProperty.mBigrams != null) {
+ for (final WeightedString bigram : wordProperty.mBigrams) {
+ builder.append(" " + BIGRAM_TAG + "=" + bigram.mWord);
+ builder.append(",");
+ builder.append(formatProbabilityInfo(bigram.mProbabilityInfo));
+ builder.append("\n");
+ }
+ }
+ return builder.toString();
+ }
+
+ public static String formatProbabilityInfo(final ProbabilityInfo probabilityInfo) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append(PROBABILITY_TAG + "=" + probabilityInfo.mProbability);
+ if (probabilityInfo.hasHistoricalInfo()) {
+ builder.append(",");
+ builder.append(HISTORICAL_INFO_TAG + "=");
+ builder.append(probabilityInfo.mTimestamp);
+ builder.append(HISTORICAL_INFO_SEPARATOR);
+ builder.append(probabilityInfo.mLevel);
+ builder.append(HISTORICAL_INFO_SEPARATOR);
+ builder.append(probabilityInfo.mCount);
+ }
+ return builder.toString();
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
index 72f2cd2d9..87df013a6 100644
--- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
@@ -16,17 +16,19 @@
package com.android.inputmethod.latin.utils;
+import java.util.Arrays;
+
public final class CoordinateUtils {
private static final int INDEX_X = 0;
private static final int INDEX_Y = 1;
- private static final int ARRAY_SIZE = INDEX_Y + 1;
+ private static final int ELEMENT_SIZE = INDEX_Y + 1;
private CoordinateUtils() {
// This utility class is not publicly instantiable.
}
public static int[] newInstance() {
- return new int[ARRAY_SIZE];
+ return new int[ELEMENT_SIZE];
}
public static int x(final int[] coords) {
@@ -46,4 +48,44 @@ public final class CoordinateUtils {
destination[INDEX_X] = source[INDEX_X];
destination[INDEX_Y] = source[INDEX_Y];
}
+
+ public static int[] newCoordinateArray(final int arraySize) {
+ return new int[ELEMENT_SIZE * arraySize];
+ }
+
+ public static int[] newCoordinateArray(final int arraySize,
+ final int defaultX, final int defaultY) {
+ final int[] result = new int[ELEMENT_SIZE * arraySize];
+ for (int i = 0; i < arraySize; ++i) {
+ setXYInArray(result, i, defaultX, defaultY);
+ }
+ return result;
+ }
+
+ public static int xFromArray(final int[] coordsArray, final int index) {
+ return coordsArray[ELEMENT_SIZE * index + INDEX_X];
+ }
+
+ public static int yFromArray(final int[] coordsArray, final int index) {
+ return coordsArray[ELEMENT_SIZE * index + INDEX_Y];
+ }
+
+ public static int[] coordinateFromArray(final int[] coordsArray, final int index) {
+ final int baseIndex = ELEMENT_SIZE * index;
+ return Arrays.copyOfRange(coordsArray, baseIndex, baseIndex + ELEMENT_SIZE);
+ }
+
+ public static void setXYInArray(final int[] coordsArray, final int index,
+ final int x, final int y) {
+ final int baseIndex = ELEMENT_SIZE * index;
+ coordsArray[baseIndex + INDEX_X] = x;
+ coordsArray[baseIndex + INDEX_Y] = y;
+ }
+
+ public static void setCoordinateInArray(final int[] coordsArray, final int index,
+ final int[] coords) {
+ final int baseIndex = ELEMENT_SIZE * index;
+ coordsArray[baseIndex + INDEX_X] = coords[INDEX_X];
+ coordsArray[baseIndex + INDEX_Y] = coords[INDEX_Y];
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
index 36b927eea..a21a1373b 100644
--- a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin.utils;
import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.Constants;
import java.util.ArrayList;
@@ -57,9 +58,9 @@ public final class CsvUtils {
// Note that none of these characters match high or low surrogate characters, so we need not
// take care of matching by code point.
- private static final char COMMA = ',';
- private static final char SPACE = ' ';
- private static final char QUOTE = '"';
+ private static final char COMMA = Constants.CODE_COMMA;
+ private static final char SPACE = Constants.CODE_SPACE;
+ private static final char QUOTE = Constants.CODE_DOUBLE_QUOTE;
@SuppressWarnings("serial")
public static class CsvParseException extends RuntimeException {
@@ -208,7 +209,7 @@ public final class CsvUtils {
@UsedForTesting
public static String[] split(final int splitFlags, final String line) throws CsvParseException {
final boolean trimSpaces = (splitFlags & SPLIT_FLAGS_TRIM_SPACES) != 0;
- final ArrayList<String> fields = CollectionUtils.newArrayList();
+ final ArrayList<String> fields = new ArrayList<>();
final int length = line.length();
int start = 0;
do {
diff --git a/java/src/com/android/inputmethod/latin/utils/DialogUtils.java b/java/src/com/android/inputmethod/latin/utils/DialogUtils.java
new file mode 100644
index 000000000..a05c932d0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DialogUtils.java
@@ -0,0 +1,34 @@
+/*
+ * 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.content.Context;
+import android.view.ContextThemeWrapper;
+
+import com.android.inputmethod.latin.R;
+
+public final class DialogUtils {
+ private DialogUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static Context getPlatformDialogThemeContext(final Context context) {
+ // Because {@link AlertDialog.Builder.create()} doesn't honor the specified theme with
+ // createThemeContextWrapper=false, the result dialog box has unneeded paddings around it.
+ return new ContextThemeWrapper(context, R.style.platformDialogTheme);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index 021bf0825..d76ea10c0 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -20,15 +20,19 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
+import android.text.TextUtils;
import android.util.Log;
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.makedict.BinaryDictIOUtils;
-import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.makedict.DictionaryHeader;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Locale;
@@ -278,14 +282,36 @@ public class DictionaryInfoUtils {
BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.getLanguage().toString();
}
- public static FileHeader getDictionaryFileHeaderOrNull(final File file) {
- return BinaryDictIOUtils.getDictionaryFileHeaderOrNull(file, 0, file.length());
+ public static DictionaryHeader getDictionaryFileHeaderOrNull(final File file) {
+ return getDictionaryFileHeaderOrNull(file, 0, file.length());
}
+ private static DictionaryHeader getDictionaryFileHeaderOrNull(final File file,
+ final long offset, final long length) {
+ try {
+ final DictionaryHeader header =
+ BinaryDictionaryUtils.getHeaderWithOffsetAndLength(file, offset, length);
+ return header;
+ } catch (UnsupportedFormatException e) {
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns information of the dictionary.
+ *
+ * @param fileAddress the asset dictionary file address.
+ * @return information of the specified dictionary.
+ */
private static DictionaryInfo createDictionaryInfoFromFileAddress(
final AssetFileAddress fileAddress) {
- final FileHeader header = BinaryDictIOUtils.getDictionaryFileHeaderOrNull(
+ final DictionaryHeader header = getDictionaryFileHeaderOrNull(
new File(fileAddress.mFilename), fileAddress.mOffset, fileAddress.mLength);
+ if (header == null) {
+ return null;
+ }
final String id = header.getId();
final Locale locale = LocaleUtils.constructLocaleFromString(header.getLocaleString());
final String description = header.getDescription();
@@ -310,7 +336,7 @@ public class DictionaryInfoUtils {
public static ArrayList<DictionaryInfo> getCurrentDictionaryFileNameAndVersionInfo(
final Context context) {
- final ArrayList<DictionaryInfo> dictList = CollectionUtils.newArrayList();
+ final ArrayList<DictionaryInfo> dictList = new ArrayList<>();
// Retrieve downloaded dictionaries
final File[] directoryList = getCachedDirectoryList(context);
@@ -328,7 +354,7 @@ public class DictionaryInfoUtils {
// Protect against cases of a less-specific dictionary being found, like an
// en dictionary being used for an en_US locale. In this case, the en dictionary
// should be used for en_US but discounted for listing purposes.
- if (!dictionaryInfo.mLocale.equals(locale)) continue;
+ if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) continue;
addOrUpdateDictInfo(dictList, dictionaryInfo);
}
}
@@ -355,4 +381,32 @@ public class DictionaryInfoUtils {
return dictList;
}
+
+ public static boolean looksValidForDictionaryInsertion(final CharSequence text,
+ final SpacingAndPunctuations spacingAndPunctuations) {
+ if (TextUtils.isEmpty(text)) return false;
+ final int length = text.length();
+ // TODO: Make this test "length > Constants.DICTIONARY_MAX_WORD_LENGTH".
+ if (length >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
+ return false;
+ }
+ int i = 0;
+ int digitCount = 0;
+ while (i < length) {
+ final int codePoint = Character.codePointAt(text, i);
+ final int charCount = Character.charCount(codePoint);
+ i += charCount;
+ if (Character.isDigit(codePoint)) {
+ // Count digits: see below
+ digitCount += charCount;
+ continue;
+ }
+ if (!spacingAndPunctuations.isWordCodePoint(codePoint)) return false;
+ }
+ // We reject strings entirely comprised of digits to avoid using PIN codes or credit
+ // card numbers. It would come in handy for word prediction though; a good example is
+ // when writing one's address where the street number is usually quite discriminative,
+ // as well as the postal code.
+ return digitCount < length;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
new file mode 100644
index 000000000..787e4a59d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.util.List;
+import java.util.Locale;
+
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.PrevWordsInfo;
+
+public interface DistracterFilter {
+ /**
+ * Determine whether a word is a distracter to words in dictionaries.
+ *
+ * @param prevWordsInfo the information of previous words.
+ * @param testedWord the word that will be tested to see whether it is a distracter to words
+ * in dictionaries.
+ * @param locale the locale of word.
+ * @return true if testedWord is a distracter, otherwise false.
+ */
+ public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
+ final String testedWord, final Locale locale);
+
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes);
+
+ public void close();
+
+ public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() {
+ @Override
+ public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
+ String testedWord, Locale locale) {
+ return false;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
+ }
+ };
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java
new file mode 100644
index 000000000..0ee6236b1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.TimeUnit;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.LruCache;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.DictionaryFacilitator;
+import com.android.inputmethod.latin.PrevWordsInfo;
+
+/**
+ * This class is used to prevent distracters being added to personalization
+ * or user history dictionaries
+ */
+public class DistracterFilterCheckingExactMatches implements DistracterFilter {
+ private static final String TAG = DistracterFilterCheckingExactMatches.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120;
+ private static final int MAX_DISTRACTERS_CACHE_SIZE = 512;
+
+ private final Context mContext;
+ private final DictionaryFacilitator mDictionaryFacilitator;
+ private final LruCache<String, Boolean> mDistractersCache;
+ private final Object mLock = new Object();
+
+ /**
+ * Create a DistracterFilter instance.
+ *
+ * @param context the context.
+ */
+ public DistracterFilterCheckingExactMatches(final Context context) {
+ mContext = context;
+ mDictionaryFacilitator = new DictionaryFacilitator();
+ mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE);
+ }
+
+ @Override
+ public void close() {
+ mDictionaryFacilitator.closeDictionaries();
+ }
+
+ @Override
+ public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) {
+ }
+
+ private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException {
+ mDictionaryFacilitator.resetDictionaries(mContext, newlocale,
+ false /* useContactsDict */, false /* usePersonalizedDicts */,
+ false /* forceReloadMainDictionary */, null /* listener */);
+ mDictionaryFacilitator.waitForLoadingMainDictionary(
+ TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Determine whether a word is a distracter to words in dictionaries.
+ *
+ * @param prevWordsInfo the information of previous words. Not used for now.
+ * @param testedWord the word that will be tested to see whether it is a distracter to words
+ * in dictionaries.
+ * @param locale the locale of word.
+ * @return true if testedWord is a distracter, otherwise false.
+ */
+ @Override
+ public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo,
+ final String testedWord, final Locale locale) {
+ if (locale == null) {
+ return false;
+ }
+ if (!locale.equals(mDictionaryFacilitator.getLocale())) {
+ synchronized (mLock) {
+ // Reset dictionaries for the locale.
+ try {
+ mDistractersCache.evictAll();
+ loadDictionariesForLocale(locale);
+ } catch (final InterruptedException e) {
+ Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter",
+ e);
+ return false;
+ }
+ }
+ }
+
+ final Boolean isCachedDistracter = mDistractersCache.get(testedWord);
+ if (isCachedDistracter != null && isCachedDistracter) {
+ if (DEBUG) {
+ Log.d(TAG, "testedWord: " + testedWord);
+ Log.d(TAG, "isDistracter: true (cache hit)");
+ }
+ return true;
+ }
+ // The tested word is a distracter when there is a word that is exact matched to the tested
+ // word and its probability is higher than the tested word's probability.
+ final int perfectMatchFreq = mDictionaryFacilitator.getFrequency(testedWord);
+ final int exactMatchFreq = mDictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord);
+ final boolean isDistracter = perfectMatchFreq < exactMatchFreq;
+ if (DEBUG) {
+ Log.d(TAG, "testedWord: " + testedWord);
+ Log.d(TAG, "perfectMatchFreq: " + perfectMatchFreq);
+ Log.d(TAG, "exactMatchFreq: " + exactMatchFreq);
+ Log.d(TAG, "isDistracter: " + isDistracter);
+ }
+ if (isDistracter) {
+ // Add the word to the cache.
+ mDistractersCache.put(testedWord, Boolean.TRUE);
+ }
+ return isDistracter;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
new file mode 100644
index 000000000..4ad4ba784
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.util.List;
+import java.util.Locale;
+
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.PrevWordsInfo;
+
+public class DistracterFilterCheckingIsInDictionary implements DistracterFilter {
+ private final DistracterFilter mDistracterFilter;
+ private final Dictionary mDictionary;
+
+ public DistracterFilterCheckingIsInDictionary(final DistracterFilter distracterFilter,
+ final Dictionary dictionary) {
+ mDistracterFilter = distracterFilter;
+ mDictionary = dictionary;
+ }
+
+ @Override
+ public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo,
+ String testedWord, Locale locale) {
+ if (mDictionary.isInDictionary(testedWord)) {
+ // This filter treats entries that are already in the dictionary as non-distracters
+ // because they have passed the filtering in the past.
+ return false;
+ } else {
+ return mDistracterFilter.isDistracterToWordsInDictionaries(
+ prevWordsInfo, testedWord, locale);
+ }
+ }
+
+ @Override
+ public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) {
+ // Do nothing.
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
new file mode 100644
index 000000000..61da1b789
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Utilities to manage executors.
+ */
+public class ExecutorUtils {
+ private static final ConcurrentHashMap<String, ExecutorService> sExecutorMap =
+ new ConcurrentHashMap<>();
+
+ private static class ThreadFactoryWithId implements ThreadFactory {
+ private final String mId;
+
+ public ThreadFactoryWithId(final String id) {
+ mId = id;
+ }
+
+ @Override
+ public Thread newThread(final Runnable r) {
+ return new Thread(r, "Executor - " + mId);
+ }
+ }
+
+ /**
+ * Gets the executor for the given id.
+ */
+ public static ExecutorService getExecutor(final String id) {
+ ExecutorService executor = sExecutorMap.get(id);
+ if (executor == null) {
+ synchronized(sExecutorMap) {
+ executor = sExecutorMap.get(id);
+ if (executor == null) {
+ executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithId(id));
+ sExecutorMap.put(id, executor);
+ }
+ }
+ }
+ return executor;
+ }
+
+ /**
+ * Shutdowns all executors and removes all executors from the executor map for testing.
+ */
+ @UsedForTesting
+ public static void shutdownAllExecutors() {
+ synchronized(sExecutorMap) {
+ for (final ExecutorService executor : sExecutorMap.values()) {
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ executor.shutdown();
+ sExecutorMap.remove(executor);
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/FileUtils.java b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
new file mode 100644
index 000000000..f1106a6c6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/FileUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.io.File;
+import java.io.FilenameFilter;
+
+/**
+ * A simple class to help with removing directories recursively.
+ */
+public class FileUtils {
+ public static boolean deleteRecursively(final File path) {
+ if (path.isDirectory()) {
+ final File[] files = path.listFiles();
+ if (files != null) {
+ for (final File child : files) {
+ deleteRecursively(child);
+ }
+ }
+ }
+ return path.delete();
+ }
+
+ public static boolean deleteFilteredFiles(final File dir, final FilenameFilter fileNameFilter) {
+ if (!dir.isDirectory()) {
+ return false;
+ }
+ final File[] files = dir.listFiles(fileNameFilter);
+ if (files == null) {
+ return false;
+ }
+ boolean hasDeletedAllFiles = true;
+ for (final File file : files) {
+ if (!deleteRecursively(file)) {
+ hasDeletedAllFiles = false;
+ }
+ }
+ return hasDeletedAllFiles;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
index ee2b97b2a..e300bd1d3 100644
--- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java
@@ -26,12 +26,11 @@ import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragmen
import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker;
import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
-import com.android.inputmethod.research.FeedbackFragment;
import java.util.HashSet;
public class FragmentUtils {
- private static final HashSet<String> sLatinImeFragments = new HashSet<String>();
+ private static final HashSet<String> sLatinImeFragments = new HashSet<>();
static {
sLatinImeFragments.add(DictionarySettingsFragment.class.getName());
sLatinImeFragments.add(AboutPreferences.class.getName());
@@ -43,7 +42,6 @@ public class FragmentUtils {
sLatinImeFragments.add(UserDictionaryList.class.getName());
sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName());
sLatinImeFragments.add(UserDictionarySettings.class.getName());
- sLatinImeFragments.add(FeedbackFragment.class.getName());
}
public static boolean isValidFragment(String fragmentName) {
diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
new file mode 100644
index 000000000..8b7077879
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -0,0 +1,120 @@
+/*
+ * 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.content.Context;
+import android.content.SharedPreferences;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.latin.R;
+
+public final class ImportantNoticeUtils {
+ private static final String TAG = ImportantNoticeUtils.class.getSimpleName();
+
+ // {@link SharedPreferences} name to save the last important notice version that has been
+ // displayed to users.
+ private static final String PREFERENCE_NAME = "important_notice_pref";
+ private static final String KEY_IMPORTANT_NOTICE_VERSION = "important_notice_version";
+ public static final int VERSION_TO_ENABLE_PERSONALIZED_SUGGESTIONS = 1;
+
+ // Copy of the hidden {@link Settings.Secure#USER_SETUP_COMPLETE} settings key.
+ // The value is zero until each multiuser completes system setup wizard.
+ // Caveat: This is a hidden API.
+ private static final String Settings_Secure_USER_SETUP_COMPLETE = "user_setup_complete";
+ private static final int USER_SETUP_IS_NOT_COMPLETE = 0;
+
+ private ImportantNoticeUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ private static boolean isInSystemSetupWizard(final Context context) {
+ try {
+ final int userSetupComplete = Settings.Secure.getInt(
+ context.getContentResolver(), Settings_Secure_USER_SETUP_COMPLETE);
+ return userSetupComplete == USER_SETUP_IS_NOT_COMPLETE;
+ } catch (final SettingNotFoundException e) {
+ Log.w(TAG, "Can't find settings in Settings.Secure: key="
+ + Settings_Secure_USER_SETUP_COMPLETE);
+ return false;
+ }
+ }
+
+ private static SharedPreferences getImportantNoticePreferences(final Context context) {
+ return context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
+ }
+
+ private static int getCurrentImportantNoticeVersion(final Context context) {
+ return context.getResources().getInteger(R.integer.config_important_notice_version);
+ }
+
+ private static int getLastImportantNoticeVersion(final Context context) {
+ return getImportantNoticePreferences(context).getInt(KEY_IMPORTANT_NOTICE_VERSION, 0);
+ }
+
+ public static int getNextImportantNoticeVersion(final Context context) {
+ return getLastImportantNoticeVersion(context) + 1;
+ }
+
+ private static boolean hasNewImportantNotice(final Context context) {
+ final int lastVersion = getLastImportantNoticeVersion(context);
+ return getCurrentImportantNoticeVersion(context) > lastVersion;
+ }
+
+ public static boolean shouldShowImportantNotice(final Context context) {
+ if (!hasNewImportantNotice(context)) {
+ return false;
+ }
+ final String importantNoticeTitle = getNextImportantNoticeTitle(context);
+ if (TextUtils.isEmpty(importantNoticeTitle)) {
+ return false;
+ }
+ if (isInSystemSetupWizard(context)) {
+ return false;
+ }
+ return true;
+ }
+
+ public static void updateLastImportantNoticeVersion(final Context context) {
+ getImportantNoticePreferences(context)
+ .edit()
+ .putInt(KEY_IMPORTANT_NOTICE_VERSION, getNextImportantNoticeVersion(context))
+ .apply();
+ }
+
+ public static String getNextImportantNoticeTitle(final Context context) {
+ final int nextVersion = getCurrentImportantNoticeVersion(context);
+ final String[] importantNoticeTitleArray = context.getResources().getStringArray(
+ R.array.important_notice_title_array);
+ if (nextVersion > 0 && nextVersion < importantNoticeTitleArray.length) {
+ return importantNoticeTitleArray[nextVersion];
+ }
+ return null;
+ }
+
+ public static String getNextImportantNoticeContents(final Context context) {
+ final int nextVersion = getNextImportantNoticeVersion(context);
+ final String[] importantNoticeContentsArray = context.getResources().getStringArray(
+ R.array.important_notice_contents_array);
+ if (nextVersion > 0 && nextVersion < importantNoticeContentsArray.length) {
+ return importantNoticeContentsArray[nextVersion];
+ }
+ return null;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
new file mode 100644
index 000000000..6dd8d9711
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public final class JsonUtils {
+ private static final String TAG = JsonUtils.class.getSimpleName();
+
+ private static final String INTEGER_CLASS_NAME = Integer.class.getSimpleName();
+ private static final String STRING_CLASS_NAME = String.class.getSimpleName();
+
+ private static final String EMPTY_STRING = "";
+
+ public static List<Object> jsonStrToList(final String s) {
+ final ArrayList<Object> list = new ArrayList<>();
+ final JsonReader reader = new JsonReader(new StringReader(s));
+ try {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ reader.beginObject();
+ while (reader.hasNext()) {
+ final String name = reader.nextName();
+ if (name.equals(INTEGER_CLASS_NAME)) {
+ list.add(reader.nextInt());
+ } else if (name.equals(STRING_CLASS_NAME)) {
+ list.add(reader.nextString());
+ } else {
+ Log.w(TAG, "Invalid name: " + name);
+ reader.skipValue();
+ }
+ }
+ reader.endObject();
+ }
+ reader.endArray();
+ return list;
+ } catch (final IOException e) {
+ } finally {
+ close(reader);
+ }
+ return Collections.<Object>emptyList();
+ }
+
+ public static String listToJsonStr(final List<Object> list) {
+ if (list == null || list.isEmpty()) {
+ return EMPTY_STRING;
+ }
+ final StringWriter sw = new StringWriter();
+ final JsonWriter writer = new JsonWriter(sw);
+ try {
+ writer.beginArray();
+ for (final Object o : list) {
+ writer.beginObject();
+ if (o instanceof Integer) {
+ writer.name(INTEGER_CLASS_NAME).value((Integer)o);
+ } else if (o instanceof String) {
+ writer.name(STRING_CLASS_NAME).value((String)o);
+ }
+ writer.endObject();
+ }
+ writer.endArray();
+ return sw.toString();
+ } catch (final IOException e) {
+ } finally {
+ close(writer);
+ }
+ return EMPTY_STRING;
+ }
+
+ private static void close(final Closeable closeable) {
+ try {
+ if (closeable != null) {
+ closeable.close();
+ }
+ } catch (final IOException e) {
+ // Ignore
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
new file mode 100644
index 000000000..9ec19efa8
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -0,0 +1,187 @@
+/*
+ * 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 String 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 String 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 String word0, final String 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/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
deleted file mode 100644
index e958a7e71..000000000
--- a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.text.TextUtils;
-
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.WordComposer;
-
-public final class LatinImeLoggerUtils {
- private LatinImeLoggerUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static void onNonSeparator(final char code, final int x, final int y) {
- UserLogRingCharBuffer.getInstance().push(code, x, y);
- LatinImeLogger.logOnInputChar();
- }
-
- public static void onSeparator(final int code, final int x, final int y) {
- // Helper method to log a single code point separator
- // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
- onSeparator(new String(new int[]{code}, 0, 1), x, y);
- }
-
- public static void onSeparator(final String separator, final int x, final int y) {
- final int length = separator.length();
- for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
- int codePoint = Character.codePointAt(separator, i);
- // TODO: accept code points
- UserLogRingCharBuffer.getInstance().push((char)codePoint, x, y);
- }
- LatinImeLogger.logOnInputSeparator();
- }
-
- public static void onAutoCorrection(final String typedWord, final String correctedWord,
- final String separatorString, final WordComposer wordComposer) {
- final boolean isBatchMode = wordComposer.isBatchMode();
- if (!isBatchMode && TextUtils.isEmpty(typedWord)) {
- return;
- }
- // TODO: this fails when the separator is more than 1 code point long, but
- // the backend can't handle it yet. The only case when this happens is with
- // smileys and other multi-character keys.
- final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
- : separatorString.codePointAt(0);
- if (!isBatchMode) {
- LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
- } else {
- if (!TextUtils.isEmpty(correctedWord)) {
- // We must make sure that InputPointer contains only the relative timestamps,
- // not actual timestamps.
- LatinImeLogger.logOnAutoCorrectionForGeometric(
- "", correctedWord, codePoint, wordComposer.getInputPointers());
- }
- }
- }
-
- public static void onAutoCorrectionCancellation() {
- LatinImeLogger.logOnAutoCorrectionCancelled();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
index 44e5d17b4..dd6fac671 100644
--- a/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
@@ -21,22 +21,22 @@ import android.os.Looper;
import java.lang.ref.WeakReference;
-public class StaticInnerHandlerWrapper<T> extends Handler {
- private final WeakReference<T> mOuterInstanceRef;
+public class LeakGuardHandlerWrapper<T> extends Handler {
+ private final WeakReference<T> mOwnerInstanceRef;
- public StaticInnerHandlerWrapper(final T outerInstance) {
- this(outerInstance, Looper.myLooper());
+ public LeakGuardHandlerWrapper(final T ownerInstance) {
+ this(ownerInstance, Looper.myLooper());
}
- public StaticInnerHandlerWrapper(final T outerInstance, final Looper looper) {
+ public LeakGuardHandlerWrapper(final T ownerInstance, final Looper looper) {
super(looper);
- if (outerInstance == null) {
- throw new NullPointerException("outerInstance is null");
+ if (ownerInstance == null) {
+ throw new NullPointerException("ownerInstance is null");
}
- mOuterInstanceRef = new WeakReference<T>(outerInstance);
+ mOwnerInstanceRef = new WeakReference<>(ownerInstance);
}
- public T getOuterInstance() {
- return mOuterInstanceRef.get();
+ public T getOwnerInstance() {
+ return mOwnerInstanceRef.get();
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
index 22045aa38..c519a0de6 100644
--- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
@@ -30,9 +30,6 @@ import java.util.Locale;
* dictionary pack.
*/
public final class LocaleUtils {
- private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap();
- private static final String LOCALE_AND_TIME_STR_SEPARATER = ",";
-
private LocaleUtils() {
// Intentional empty constructor for utility class.
}
@@ -162,18 +159,20 @@ public final class LocaleUtils {
return LOCALE_MATCH <= level;
}
- private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
+ private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
/**
* Creates a locale from a string specification.
*/
public static Locale constructLocaleFromString(final String localeStr) {
- if (localeStr == null)
+ if (localeStr == null) {
return null;
+ }
synchronized (sLocaleCache) {
- if (sLocaleCache.containsKey(localeStr))
- return sLocaleCache.get(localeStr);
- Locale retval = null;
+ Locale retval = sLocaleCache.get(localeStr);
+ if (retval != null) {
+ return retval;
+ }
String[] localeParams = localeStr.split("_", 3);
if (localeParams.length == 1) {
retval = new Locale(localeParams[0]);
@@ -188,38 +187,4 @@ public final class LocaleUtils {
return retval;
}
}
-
- public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) {
- if (TextUtils.isEmpty(str)) {
- return EMPTY_LT_HASH_MAP;
- }
- final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER);
- final int N = ss.length;
- if (N < 2 || N % 2 != 0) {
- return EMPTY_LT_HASH_MAP;
- }
- final HashMap<String, Long> retval = CollectionUtils.newHashMap();
- for (int i = 0; i < N / 2; ++i) {
- final String localeStr = ss[i * 2];
- final long time = Long.valueOf(ss[i * 2 + 1]);
- retval.put(localeStr, time);
- }
- return retval;
- }
-
- public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) {
- if (map == null || map.isEmpty()) {
- return "";
- }
- final StringBuilder builder = new StringBuilder();
- for (String localeStr : map.keySet()) {
- if (builder.length() > 0) {
- builder.append(LOCALE_AND_TIME_STR_SEPARATER);
- }
- final Long time = map.get(localeStr);
- builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER);
- builder.append(String.valueOf(time));
- }
- return builder.toString();
- }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java
new file mode 100644
index 000000000..3cd63612c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/PrevWordsInfoUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import java.util.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;
+
+public final class PrevWordsInfoUtils {
+ private PrevWordsInfoUtils() {
+ // Intentional empty constructor for utility class.
+ }
+
+ private static final Pattern SPACE_REGEX = Pattern.compile("\\s+");
+ // Get context information from nth word before the cursor. n = 1 retrieves the words
+ // immediately before the cursor, n = 2 retrieves the words before that, and so on. This splits
+ // on whitespace only.
+ // Also, it won't return words that end in a separator (if the nth word before the cursor
+ // ends in a separator, it returns information representing beginning-of-sentence).
+ // Example (when Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM is 2):
+ // (n = 1) "abc def|" -> abc, def
+ // (n = 1) "abc def |" -> abc, def
+ // (n = 1) "abc 'def|" -> empty, 'def
+ // (n = 1) "abc def. |" -> beginning-of-sentence
+ // (n = 1) "abc def . |" -> beginning-of-sentence
+ // (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.
+ // 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
+ // (n = 2) "abc def . |" -> abc, def
+ // (n = 2) "abc|" -> beginning-of-sentence
+ // (n = 2) "abc |" -> beginning-of-sentence
+ // (n = 2) "abc. def|" -> beginning-of-sentence
+ public static PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(final CharSequence prev,
+ final SpacingAndPunctuations spacingAndPunctuations, final int n) {
+ if (prev == null) return PrevWordsInfo.EMPTY_PREV_WORDS_INFO;
+ final String[] w = SPACE_REGEX.split(prev);
+ final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
+ for (int i = 0; i < prevWordsInfo.length; i++) {
+ final int focusedWordIndex = w.length - n - i;
+ // Referring to the word after the focused word.
+ if ((focusedWordIndex + 1) >= 0 && (focusedWordIndex + 1) < w.length) {
+ final String wordFollowingTheNthPrevWord = w[focusedWordIndex + 1];
+ if (!wordFollowingTheNthPrevWord.isEmpty()) {
+ final char firstChar = wordFollowingTheNthPrevWord.charAt(0);
+ 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;
+ 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;
+ 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;
+ 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);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
deleted file mode 100644
index 201a70d42..000000000
--- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import java.util.Queue;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * An object that executes submitted tasks using a thread.
- */
-public class PrioritizedSerialExecutor {
- public static final String TAG = PrioritizedSerialExecutor.class.getSimpleName();
-
- private final Object mLock = new Object();
-
- private final Queue<Runnable> mTasks;
- private final Queue<Runnable> mPrioritizedTasks;
- private boolean mIsShutdown;
- private final ThreadPoolExecutor mThreadPoolExecutor;
-
- // The task which is running now.
- private Runnable mActive;
-
- public PrioritizedSerialExecutor() {
- mTasks = new ConcurrentLinkedQueue<Runnable>();
- mPrioritizedTasks = new ConcurrentLinkedQueue<Runnable>();
- mIsShutdown = false;
- mThreadPoolExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
- 0 /* keepAliveTime */, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1));
- }
-
- /**
- * Clears all queued tasks.
- */
- public void clearAllTasks() {
- synchronized(mLock) {
- mTasks.clear();
- mPrioritizedTasks.clear();
- }
- }
-
- /**
- * Enqueues the given task into the task queue.
- * @param r the enqueued task
- */
- public void execute(final Runnable r) {
- synchronized(mLock) {
- if (!mIsShutdown) {
- mTasks.offer(new Runnable() {
- @Override
- public void run() {
- try {
- r.run();
- } finally {
- scheduleNext();
- }
- }
- });
- if (mActive == null) {
- scheduleNext();
- }
- }
- }
- }
-
- /**
- * Enqueues the given task into the prioritized task queue.
- * @param r the enqueued task
- */
- public void executePrioritized(final Runnable r) {
- synchronized(mLock) {
- if (!mIsShutdown) {
- mPrioritizedTasks.offer(new Runnable() {
- @Override
- public void run() {
- try {
- r.run();
- } finally {
- scheduleNext();
- }
- }
- });
- if (mActive == null) {
- scheduleNext();
- }
- }
- }
- }
-
- private boolean fetchNextTasksLocked() {
- mActive = mPrioritizedTasks.poll();
- if (mActive == null) {
- mActive = mTasks.poll();
- }
- return mActive != null;
- }
-
- private void scheduleNext() {
- synchronized(mLock) {
- if (fetchNextTasksLocked()) {
- mThreadPoolExecutor.execute(mActive);
- }
- }
- }
-
- public void remove(final Runnable r) {
- synchronized(mLock) {
- mTasks.remove(r);
- mPrioritizedTasks.remove(r);
- }
- }
-
- public void replaceAndExecute(final Runnable oldTask, final Runnable newTask) {
- synchronized(mLock) {
- if (oldTask != null) remove(oldTask);
- execute(newTask);
- }
- }
-
- public void shutdown() {
- synchronized(mLock) {
- mIsShutdown = true;
- }
- }
-
- public boolean isTerminated() {
- synchronized(mLock) {
- if (!mIsShutdown) {
- return false;
- }
- return mPrioritizedTasks.isEmpty() && mTasks.isEmpty() && mActive == null;
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index 0f5cd80db..e3cac97f0 100644
--- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
@@ -37,12 +37,12 @@ public class RecapitalizeStatus {
CAPS_MODE_ALL_UPPER
};
- private static final int getStringMode(final String string, final String separators) {
+ private static final int getStringMode(final String string, final int[] sortedSeparators) {
if (StringUtils.isIdenticalAfterUpcase(string)) {
return CAPS_MODE_ALL_UPPER;
} else if (StringUtils.isIdenticalAfterDowncase(string)) {
return CAPS_MODE_ALL_LOWER;
- } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, separators)) {
+ } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, sortedSeparators)) {
return CAPS_MODE_FIRST_WORD_UPPER;
} else {
return CAPS_MODE_ORIGINAL_MIXED_CASE;
@@ -60,26 +60,32 @@ public class RecapitalizeStatus {
private int mRotationStyleCurrentIndex;
private boolean mSkipOriginalMixedCaseMode;
private Locale mLocale;
- private String mSeparators;
+ private int[] mSortedSeparators;
private String mStringAfter;
- private boolean mIsActive;
+ private boolean mIsStarted;
+ private boolean mIsEnabled = true;
+
+ private static final int[] EMPTY_STORTED_SEPARATORS = {};
public RecapitalizeStatus() {
// By default, initialize with dummy values that won't match any real recapitalize.
- initialize(-1, -1, "", Locale.getDefault(), "");
- deactivate();
+ start(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS);
+ stop();
}
- public void initialize(final int cursorStart, final int cursorEnd, final String string,
- final Locale locale, final String separators) {
+ public void start(final int cursorStart, final int cursorEnd, final String string,
+ final Locale locale, final int[] sortedSeparators) {
+ if (!mIsEnabled) {
+ return;
+ }
mCursorStartBefore = cursorStart;
mStringBefore = string;
mCursorStartAfter = cursorStart;
mCursorEndAfter = cursorEnd;
mStringAfter = string;
- final int initialMode = getStringMode(mStringBefore, separators);
+ final int initialMode = getStringMode(mStringBefore, sortedSeparators);
mLocale = locale;
- mSeparators = separators;
+ mSortedSeparators = sortedSeparators;
if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) {
mRotationStyleCurrentIndex = 0;
mSkipOriginalMixedCaseMode = false;
@@ -94,15 +100,27 @@ public class RecapitalizeStatus {
mRotationStyleCurrentIndex = currentMode;
mSkipOriginalMixedCaseMode = true;
}
- mIsActive = true;
+ mIsStarted = true;
+ }
+
+ public void stop() {
+ mIsStarted = false;
+ }
+
+ public boolean isStarted() {
+ return mIsStarted;
+ }
+
+ public void enable() {
+ mIsEnabled = true;
}
- public void deactivate() {
- mIsActive = false;
+ public void disable() {
+ mIsEnabled = false;
}
- public boolean isActive() {
- return mIsActive;
+ public boolean mIsEnabled() {
+ return mIsEnabled;
}
public boolean isSetAt(final int cursorStart, final int cursorEnd) {
@@ -131,7 +149,7 @@ public class RecapitalizeStatus {
mStringAfter = mStringBefore.toLowerCase(mLocale);
break;
case CAPS_MODE_FIRST_WORD_UPPER:
- mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators,
+ mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSortedSeparators,
mLocale);
break;
case CAPS_MODE_ALL_UPPER:
diff --git a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
index 7c6fe93ac..64c9e2cff 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
@@ -34,7 +34,7 @@ public final class ResizableIntArray {
throw new ArrayIndexOutOfBoundsException("length=" + mLength + "; index=" + index);
}
- public void add(final int index, final int val) {
+ public void addAt(final int index, final int val) {
if (index < mLength) {
mArray[index] = val;
} else {
diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
index 22c92446a..093c5a6c1 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -41,8 +41,7 @@ public final class ResourceUtils {
// This utility class is not publicly instantiable.
}
- private static final HashMap<String, String> sDeviceOverrideValueMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, String> sDeviceOverrideValueMap = new HashMap<>();
private static final String[] BUILD_KEYS_AND_VALUES = {
"HARDWARE", Build.HARDWARE,
@@ -54,8 +53,8 @@ public final class ResourceUtils {
private static final String sBuildKeyValuesDebugString;
static {
- sBuildKeyValues = CollectionUtils.newHashMap();
- final ArrayList<String> keyValuePairs = CollectionUtils.newArrayList();
+ sBuildKeyValues = new HashMap<>();
+ final ArrayList<String> keyValuePairs = new ArrayList<>();
final int keyCount = BUILD_KEYS_AND_VALUES.length / 2;
for (int i = 0; i < keyCount; i++) {
final int index = i * 2;
@@ -67,7 +66,8 @@ public final class ResourceUtils {
sBuildKeyValuesDebugString = "[" + TextUtils.join(" ", keyValuePairs) + "]";
}
- public static String getDeviceOverrideValue(final Resources res, final int overrideResId) {
+ public static String getDeviceOverrideValue(final Resources res, final int overrideResId,
+ final String defaultValue) {
final int orientation = res.getConfiguration().orientation;
final String key = overrideResId + "-" + orientation;
if (sDeviceOverrideValueMap.containsKey(key)) {
@@ -86,23 +86,6 @@ public final class ResourceUtils {
return overrideValue;
}
- String defaultValue = null;
- try {
- defaultValue = findDefaultConstant(overrideArray);
- // The defaultValue might be an empty string.
- if (defaultValue == null) {
- Log.w(TAG, "Couldn't find override value nor default value:"
- + " resource="+ res.getResourceEntryName(overrideResId)
- + " build=" + sBuildKeyValuesDebugString);
- } else {
- Log.i(TAG, "Found default value:"
- + " resource="+ res.getResourceEntryName(overrideResId)
- + " build=" + sBuildKeyValuesDebugString
- + " default=" + defaultValue);
- }
- } catch (final DeviceOverridePatternSyntaxError e) {
- Log.w(TAG, "Syntax error, ignored", e);
- }
sDeviceOverrideValueMap.put(key, defaultValue);
return defaultValue;
}
@@ -152,8 +135,7 @@ public final class ResourceUtils {
}
final String condition = conditionConstant.substring(0, posComma);
if (condition.isEmpty()) {
- // Default condition. The default condition should be searched by
- // {@link #findConstantForDefault(String[])}.
+ Log.w(TAG, "Array element has no condition: " + conditionConstant);
continue;
}
try {
@@ -199,24 +181,6 @@ public final class ResourceUtils {
return matchedAll;
}
- @UsedForTesting
- static String findDefaultConstant(final String[] conditionConstantArray)
- throws DeviceOverridePatternSyntaxError {
- if (conditionConstantArray == null) {
- return null;
- }
- for (final String condition : conditionConstantArray) {
- final int posComma = condition.indexOf(',');
- if (posComma < 0) {
- throw new DeviceOverridePatternSyntaxError("Array element has no comma", condition);
- }
- if (posComma == 0) { // condition is empty.
- return condition.substring(posComma + 1);
- }
- }
- return null;
- }
-
public static int getDefaultKeyboardWidth(final Resources res) {
final DisplayMetrics dm = res.getDisplayMetrics();
return dm.widthPixels;
@@ -224,22 +188,23 @@ public final class ResourceUtils {
public static int getDefaultKeyboardHeight(final Resources res) {
final DisplayMetrics dm = res.getDisplayMetrics();
- final String keyboardHeightString = getDeviceOverrideValue(res, R.array.keyboard_heights);
+ final String keyboardHeightInDp = getDeviceOverrideValue(
+ res, R.array.keyboard_heights, null /* defaultValue */);
final float keyboardHeight;
- if (TextUtils.isEmpty(keyboardHeightString)) {
- keyboardHeight = res.getDimension(R.dimen.keyboardHeight);
+ if (TextUtils.isEmpty(keyboardHeightInDp)) {
+ keyboardHeight = res.getDimension(R.dimen.config_default_keyboard_height);
} else {
- keyboardHeight = Float.parseFloat(keyboardHeightString) * dm.density;
+ keyboardHeight = Float.parseFloat(keyboardHeightInDp) * dm.density;
}
final float maxKeyboardHeight = res.getFraction(
- R.fraction.maxKeyboardHeight, dm.heightPixels, dm.heightPixels);
+ R.fraction.config_max_keyboard_height, dm.heightPixels, dm.heightPixels);
float minKeyboardHeight = res.getFraction(
- R.fraction.minKeyboardHeight, dm.heightPixels, dm.heightPixels);
+ R.fraction.config_min_keyboard_height, dm.heightPixels, dm.heightPixels);
if (minKeyboardHeight < 0.0f) {
// Specified fraction was negative, so it should be calculated against display
// width.
minKeyboardHeight = -res.getFraction(
- R.fraction.minKeyboardHeight, dm.widthPixels, dm.widthPixels);
+ R.fraction.config_min_keyboard_height, dm.widthPixels, dm.widthPixels);
}
// Keyboard height will not exceed maxKeyboardHeight and will not be less than
// minKeyboardHeight.
@@ -260,6 +225,10 @@ public final class ResourceUtils {
return dimension >= 0;
}
+ public static float getFloatFromFraction(final Resources res, final int fractionResId) {
+ return res.getFraction(fractionResId, 1, 1);
+ }
+
public static float getFraction(final TypedArray a, final int index, final float defValue) {
final TypedValue value = a.peekValue(index);
if (value == null || !isFractionValue(value)) {
diff --git a/java/src/com/android/inputmethod/latin/utils/RunInLocale.java b/java/src/com/android/inputmethod/latin/utils/RunInLocale.java
index 2c9e3b191..1ea16e6ef 100644
--- a/java/src/com/android/inputmethod/latin/utils/RunInLocale.java
+++ b/java/src/com/android/inputmethod/latin/utils/RunInLocale.java
@@ -30,25 +30,23 @@ public abstract class RunInLocale<T> {
* Execute {@link #job(Resources)} method in specified system locale exclusively.
*
* @param res the resources to use.
- * @param newLocale the locale to change to.
+ * @param newLocale the locale to change to. Run in system locale if null.
* @return the value returned from {@link #job(Resources)}.
*/
public T runInLocale(final Resources res, final Locale newLocale) {
synchronized (sLockForRunInLocale) {
final Configuration conf = res.getConfiguration();
- final Locale oldLocale = conf.locale;
- final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale));
+ if (newLocale == null || newLocale.equals(conf.locale)) {
+ return job(res);
+ }
+ final Locale savedLocale = conf.locale;
try {
- if (needsChange) {
- conf.locale = newLocale;
- res.updateConfiguration(conf, null);
- }
+ conf.locale = newLocale;
+ res.updateConfiguration(conf, null);
return job(res);
} finally {
- if (needsChange) {
- conf.locale = oldLocale;
- res.updateConfiguration(conf, null);
- }
+ conf.locale = savedLocale;
+ res.updateConfiguration(conf, null);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
new file mode 100644
index 000000000..a76a6dfd7
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ScriptUtils.java
@@ -0,0 +1,141 @@
+/*
+ * 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.Locale;
+import java.util.TreeMap;
+
+/**
+ * A class to help with handling different writing scripts.
+ */
+public class ScriptUtils {
+ // Used for hardware keyboards
+ public static final int SCRIPT_UNKNOWN = -1;
+ // TODO: should we use ISO 15924 identifiers instead?
+ public static final int SCRIPT_LATIN = 0;
+ public static final int SCRIPT_CYRILLIC = 1;
+ public static final int SCRIPT_GREEK = 2;
+ public static final int SCRIPT_ARABIC = 3;
+ public static final int SCRIPT_HEBREW = 4;
+ public static final int SCRIPT_ARMENIAN = 5;
+ public static final int SCRIPT_GEORGIAN = 6;
+ public static final TreeMap<String, Integer> mSpellCheckerLanguageToScript;
+ static {
+ // List of the supported languages and their associated script. We won't check
+ // words written in another script than the selected script, because we know we
+ // don't have those in our dictionary so we will underline everything and we
+ // will never have any suggestions, so it makes no sense checking them, and this
+ // is done in {@link #shouldFilterOut}. Also, the script is used to choose which
+ // proximity to pass to the dictionary descent algorithm.
+ // IMPORTANT: this only contains languages - do not write countries in there.
+ // Only the language is searched from the map.
+ mSpellCheckerLanguageToScript = new TreeMap<>();
+ mSpellCheckerLanguageToScript.put("cs", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("da", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("de", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("el", SCRIPT_GREEK);
+ mSpellCheckerLanguageToScript.put("en", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("es", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("fi", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("fr", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("hr", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("it", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("lt", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("lv", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("nb", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("nl", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("pt", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("sl", SCRIPT_LATIN);
+ mSpellCheckerLanguageToScript.put("ru", SCRIPT_CYRILLIC);
+ }
+ /*
+ * Returns whether the code point is a letter that makes sense for the specified
+ * locale for this spell checker.
+ * The dictionaries supported by Latin IME are described in res/xml/spellchecker.xml
+ * and is limited to EFIGS languages and Russian.
+ * Hence at the moment this explicitly tests for Cyrillic characters or Latin characters
+ * as appropriate, and explicitly excludes CJK, Arabic and Hebrew characters.
+ */
+ public static boolean isLetterPartOfScript(final int codePoint, final int scriptId) {
+ switch (scriptId) {
+ case SCRIPT_LATIN:
+ // Our supported latin script dictionaries (EFIGS) at the moment only include
+ // characters in the C0, C1, Latin Extended A and B, IPA extensions unicode
+ // blocks. As it happens, those are back-to-back in the code range 0x40 to 0x2AF,
+ // so the below is a very efficient way to test for it. As for the 0-0x3F, it's
+ // excluded from isLetter anyway.
+ return codePoint <= 0x2AF && Character.isLetter(codePoint);
+ case SCRIPT_CYRILLIC:
+ // All Cyrillic characters are in the 400~52F block. There are some in the upper
+ // Unicode range, but they are archaic characters that are not used in modern
+ // Russian and are not used by our dictionary.
+ return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint);
+ case SCRIPT_GREEK:
+ // Greek letters are either in the 370~3FF range (Greek & Coptic), or in the
+ // 1F00~1FFF range (Greek extended). Our dictionary contains both sort of characters.
+ // Our dictionary also contains a few words with 0xF2; it would be best to check
+ // if that's correct, but a web search does return results for these words so
+ // they are probably okay.
+ return (codePoint >= 0x370 && codePoint <= 0x3FF)
+ || (codePoint >= 0x1F00 && codePoint <= 0x1FFF)
+ || codePoint == 0xF2;
+ case SCRIPT_ARABIC:
+ // Arabic letters can be in any of the following blocks:
+ // Arabic U+0600..U+06FF
+ // Arabic Supplement U+0750..U+077F
+ // Arabic Extended-A U+08A0..U+08FF
+ // Arabic Presentation Forms-A U+FB50..U+FDFF
+ // Arabic Presentation Forms-B U+FE70..U+FEFF
+ return (codePoint >= 0x600 && codePoint <= 0x6FF)
+ || (codePoint >= 0x750 && codePoint <= 0x77F)
+ || (codePoint >= 0x8A0 && codePoint <= 0x8FF)
+ || (codePoint >= 0xFB50 && codePoint <= 0xFDFF)
+ || (codePoint >= 0xFE70 && codePoint <= 0xFEFF);
+ case SCRIPT_HEBREW:
+ // Hebrew letters are in the Hebrew unicode block, which spans from U+0590 to U+05FF,
+ // or in the Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the
+ // Hebrew part of that block, which is U+FB1D..U+FB4F.
+ return (codePoint >= 0x590 && codePoint <= 0x5FF
+ || codePoint >= 0xFB1D && codePoint <= 0xFB4F);
+ case SCRIPT_ARMENIAN:
+ // Armenian letters are in the Armenian unicode block, U+0530..U+058F and
+ // Alphabetic Presentation Forms block, U+FB00..U+FB4F, but only in the Armenian part
+ // of that block, which is U+FB13..U+FB17.
+ return (codePoint >= 0x530 && codePoint <= 0x58F
+ || codePoint >= 0xFB13 && codePoint <= 0xFB17);
+ case SCRIPT_GEORGIAN:
+ // Georgian letters are in the Georgian unicode block, U+10A0..U+10FF,
+ // or Georgian supplement block, U+2D00..U+2D2F
+ return (codePoint >= 0x10A0 && codePoint <= 0x10FF
+ || codePoint >= 0x2D00 && codePoint <= 0x2D2F);
+ case SCRIPT_UNKNOWN:
+ return true;
+ default:
+ // Should never come here
+ throw new RuntimeException("Impossible value of script: " + scriptId);
+ }
+ }
+
+ public static int getScriptFromSpellCheckerLocale(final Locale locale) {
+ final Integer script = mSpellCheckerLanguageToScript.get(locale.getLanguage());
+ if (null == script) {
+ throw new RuntimeException("We have been called with an unsupported language: \""
+ + locale.getLanguage() + "\". Framework bug?");
+ }
+ return script;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
new file mode 100644
index 000000000..1ca895fdb
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/SpacebarLanguageUtils.java
@@ -0,0 +1,58 @@
+/*
+ * 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 b51fd9377..38164cb36 100644
--- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java
@@ -22,6 +22,7 @@ import android.text.Spanned;
import android.text.SpannedString;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
+import android.text.style.URLSpan;
public final class SpannableStringUtils {
/**
@@ -40,12 +41,17 @@ public final class SpannableStringUtils {
* are out of range in <code>dest</code>.
*/
public static void copyNonParagraphSuggestionSpansFrom(Spanned source, int start, int end,
- Spannable dest, int destoff) {
+ Spannable dest, int destoff) {
Object[] spans = source.getSpans(start, end, SuggestionSpan.class);
for (int i = 0; i < spans.length; i++) {
int fl = source.getSpanFlags(spans[i]);
- if (0 != (fl & Spannable.SPAN_PARAGRAPH)) continue;
+ // We don't care about the PARAGRAPH flag in LatinIME code. However, if this flag
+ // is set, Spannable#setSpan will throw an exception unless the span is on the edge
+ // 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;
int st = source.getSpanStart(spans[i]);
int en = source.getSpanEnd(spans[i]);
@@ -107,4 +113,16 @@ public final class SpannableStringUtils {
return new SpannedString(ss);
}
+
+ public static boolean hasUrlSpans(final CharSequence text,
+ final int startIndex, final int endIndex) {
+ if (!(text instanceof Spanned)) {
+ return false; // Not spanned, so no link
+ }
+ final Spanned spanned = (Spanned)text;
+ // getSpans(x, y) does not return spans that start on x or end on y. x-1, y+1 does the
+ // trick, and works in all cases even if startIndex <= 0 or endIndex >= text.length().
+ final URLSpan[] spans = spanned.getSpans(startIndex - 1, endIndex + 1, URLSpan.class);
+ return null != spans && spans.length > 0;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/StatsUtils.java b/java/src/com/android/inputmethod/latin/utils/StatsUtils.java
new file mode 100644
index 000000000..79c19d077
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/StatsUtils.java
@@ -0,0 +1,34 @@
+/*
+ * 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.content.Context;
+import com.android.inputmethod.latin.settings.SettingsValues;
+
+public final class StatsUtils {
+ public static void init(final Context context) {
+ }
+
+ public static void onCreate(final SettingsValues settingsValues) {
+ }
+
+ public static void onLoadSettings(final SettingsValues settingsValues) {
+ }
+
+ public static void onDestroy() {
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index a36548392..ceb038371 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -16,29 +16,24 @@
package com.android.inputmethod.latin.utils;
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.settings.SettingsValues;
+import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
import android.text.TextUtils;
-import android.util.JsonReader;
-import android.util.JsonWriter;
-import android.util.Log;
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.Constants;
+
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
+import java.util.Arrays;
import java.util.Locale;
public final class StringUtils {
- private static final String TAG = StringUtils.class.getSimpleName();
public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case
public static final int CAPITALIZE_FIRST = 1; // First only
public static final int CAPITALIZE_ALL = 2; // All caps
+ private static final String EMPTY_STRING = "";
+
private StringUtils() {
// This utility class is not publicly instantiable.
}
@@ -50,7 +45,7 @@ public final class StringUtils {
public static String newSingleCodePointString(int codePoint) {
if (Character.charCount(codePoint) == 1) {
- // Optimization: avoid creating an temporary array for characters that are
+ // Optimization: avoid creating a temporary array for characters that are
// represented by a single char value
return String.valueOf((char) codePoint);
}
@@ -80,27 +75,16 @@ public final class StringUtils {
return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
}
- public static String appendToCommaSplittableTextIfNotExists(final String text,
- final String extraValues) {
- if (TextUtils.isEmpty(extraValues)) {
- return text;
- }
- if (containsInCommaSplittableText(text, extraValues)) {
- return extraValues;
- }
- return extraValues + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + text;
- }
-
public static String removeFromCommaSplittableTextIfExists(final String text,
final String extraValues) {
if (TextUtils.isEmpty(extraValues)) {
- return "";
+ return EMPTY_STRING;
}
final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
if (!containsInArray(text, elements)) {
return extraValues;
}
- final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
+ final ArrayList<String> result = new ArrayList<>(elements.length - 1);
for (final String element : elements) {
if (!text.equals(element)) {
result.add(element);
@@ -162,20 +146,87 @@ public final class StringUtils {
private static final int[] EMPTY_CODEPOINTS = {};
- public static int[] toCodePointArray(final String string) {
- final int length = string.length();
+ 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[string.codePointCount(0, length)];
+ 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 = 0; index < length; index = string.offsetByCodePoints(index, 1)) {
- codePoints[destIndex] = string.codePointAt(index);
+ 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
@@ -239,65 +290,40 @@ public final class StringUtils {
return true;
}
- @UsedForTesting
- public static boolean looksValidForDictionaryInsertion(final CharSequence text,
- final SettingsValues settings) {
- if (TextUtils.isEmpty(text)) return false;
- final int length = text.length();
- int i = 0;
- int digitCount = 0;
- while (i < length) {
- final int codePoint = Character.codePointAt(text, i);
- final int charCount = Character.charCount(codePoint);
- i += charCount;
- if (Character.isDigit(codePoint)) {
- // Count digits: see below
- digitCount += charCount;
- continue;
- }
- if (!settings.isWordCodePoint(codePoint)) return false;
- }
- // We reject strings entirely comprised of digits to avoid using PIN codes or credit
- // card numbers. It would come in handy for word prediction though; a good example is
- // when writing one's address where the street number is usually quite discriminative,
- // as well as the postal code.
- return digitCount < length;
- }
-
public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
- final String separators) {
- boolean needCapsNext = true;
+ 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 ((needCapsNext && !Character.isUpperCase(codePoint))
- || (!needCapsNext && !Character.isLowerCase(codePoint))) {
+ if ((needsCapsNext && !Character.isUpperCase(codePoint))
+ || (!needsCapsNext && !Character.isLowerCase(codePoint))) {
return false;
}
}
// We need a capital letter next if this is a separator.
- needCapsNext = (-1 != separators.indexOf(codePoint));
+ 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 String separators,
+ public static String capitalizeEachWord(final String text, final int[] sortedSeparators,
final Locale locale) {
final StringBuilder builder = new StringBuilder();
- boolean needCapsNext = true;
+ 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 (needCapsNext) {
+ if (needsCapsNext) {
builder.append(nextChar.toUpperCase(locale));
} else {
builder.append(nextChar.toLowerCase(locale));
}
// We need a capital letter next if this is a separator.
- needCapsNext = (-1 != separators.indexOf(nextChar.codePointAt(0)));
+ needsCapsNext = (Arrays.binarySearch(sortedSeparators, nextChar.codePointAt(0)) >= 0);
}
return builder.toString();
}
@@ -328,7 +354,7 @@ public final class StringUtils {
boolean hasPeriod = false;
int codePoint = 0;
while (i > 0) {
- codePoint = Character.codePointBefore(text, i);
+ 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,
@@ -367,7 +393,49 @@ public final class StringUtils {
return false;
}
- public static boolean isEmptyStringOrWhiteSpaces(String s) {
+ /**
+ * 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))) {
@@ -378,9 +446,9 @@ public final class StringUtils {
}
@UsedForTesting
- public static String byteArrayToHexString(byte[] bytes) {
+ public static String byteArrayToHexString(final byte[] bytes) {
if (bytes == null || bytes.length == 0) {
- return "";
+ return EMPTY_STRING;
}
final StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
@@ -393,7 +461,7 @@ public final class StringUtils {
* Convert hex string to byte array. The string length must be an even number.
*/
@UsedForTesting
- public static byte[] hexStringToByteArray(String hexString) {
+ public static byte[] hexStringToByteArray(final String hexString) {
if (TextUtils.isEmpty(hexString)) {
return null;
}
@@ -410,66 +478,68 @@ public final class StringUtils {
return bytes;
}
- public static List<Object> jsonStrToList(String s) {
- final ArrayList<Object> retval = CollectionUtils.newArrayList();
- final JsonReader reader = new JsonReader(new StringReader(s));
- try {
- reader.beginArray();
- while(reader.hasNext()) {
- reader.beginObject();
- while (reader.hasNext()) {
- final String name = reader.nextName();
- if (name.equals(Integer.class.getSimpleName())) {
- retval.add(reader.nextInt());
- } else if (name.equals(String.class.getSimpleName())) {
- retval.add(reader.nextString());
- } else {
- Log.w(TAG, "Invalid name: " + name);
- reader.skipValue();
- }
- }
- reader.endObject();
- }
- reader.endArray();
- return retval;
- } catch (IOException e) {
- } finally {
- try {
- reader.close();
- } catch (IOException e) {
- }
+ 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 Collections.<Object>emptyList();
+ return lastIndex - i;
}
- public static String listToJsonStr(List<Object> list) {
- if (list == null || list.isEmpty()) {
- return "";
- }
- final StringWriter sw = new StringWriter();
- final JsonWriter writer = new JsonWriter(sw);
- try {
- writer.beginArray();
- for (final Object o : list) {
- writer.beginObject();
- if (o instanceof Integer) {
- writer.name(Integer.class.getSimpleName()).value((Integer)o);
- } else if (o instanceof String) {
- writer.name(String.class.getSimpleName()).value((String)o);
- }
- writer.endObject();
+ @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]);
}
- writer.endArray();
- return sw.toString();
- } catch (IOException e) {
- } finally {
- try {
- if (writer != null) {
- writer.close();
- }
- } catch (IOException e) {
+ 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 + "]";
}
- return "";
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 102a41b4e..351d01400 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -25,17 +25,18 @@ import android.os.Build;
import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
-import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
public final class SubtypeLocaleUtils {
- static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
- // This class must be located in the same package as LatinIME.java.
- private static final String RESOURCE_PACKAGE_NAME =
- DictionaryFactory.class.getPackage().getName();
+ private 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();
// Special language code to represent "no language".
public static final String NO_LANGUAGE = "zz";
@@ -43,21 +44,19 @@ public final class SubtypeLocaleUtils {
public static final String EMOJI = "emoji";
public static final int UNKNOWN_KEYBOARD_LAYOUT = R.string.subtype_generic;
- private static boolean sInitialized = false;
+ private static volatile boolean sInitialized = false;
+ private static final Object sInitializeLock = new Object();
private static Resources sResources;
private static String[] sPredefinedKeyboardLayoutSet;
// Keyboard layout to its display name map.
- private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>();
// Keyboard layout to subtype name resource id map.
- private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>();
// Exceptional locale to subtype name resource id map.
- private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap =
- CollectionUtils.newHashMap();
+ private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>();
// Exceptional locale to subtype name with layout resource id map.
private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
- CollectionUtils.newHashMap();
+ new HashMap<>();
private static final String SUBTYPE_NAME_RESOURCE_PREFIX =
"string/subtype_";
private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
@@ -69,16 +68,23 @@ public final class SubtypeLocaleUtils {
// Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
// This is for compatibility to keep the same subtype ids as pre-JellyBean.
private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
- CollectionUtils.newHashMap();
+ new HashMap<>();
private SubtypeLocaleUtils() {
// Intentional empty constructor for utility class.
}
// Note that this initialization method can be called multiple times.
- public static synchronized void init(final Context context) {
- if (sInitialized) return;
+ public static void init(final Context context) {
+ synchronized (sInitializeLock) {
+ if (sInitialized == false) {
+ initLocked(context);
+ sInitialized = true;
+ }
+ }
+ }
+ private static void initLocked(final Context context) {
final Resources res = context.getResources();
sResources = res;
@@ -121,8 +127,6 @@ public final class SubtypeLocaleUtils {
final String keyboardLayoutSet = keyboardLayoutSetMap[i + 1];
sLocaleAndExtraValueToKeyboardLayoutSetMap.put(key, keyboardLayoutSet);
}
-
- sInitialized = true;
}
public static String[] getPredefinedKeyboardLayoutSet() {
@@ -166,8 +170,18 @@ public final class SubtypeLocaleUtils {
return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
}
+ public static String getSubtypeLanguageDisplayName(final String localeString) {
+ final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
+ final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
+ return getSubtypeLocaleDisplayNameInternal(locale.getLanguage(), displayLocale);
+ }
+
private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
final Locale displayLocale) {
+ if (NO_LANGUAGE.equals(localeString)) {
+ // No language subtype should be displayed in system locale.
+ return sResources.getString(R.string.subtype_no_language);
+ }
final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
final String displayName;
if (exceptionalNameResId != null) {
@@ -178,9 +192,6 @@ public final class SubtypeLocaleUtils {
}
};
displayName = getExceptionalName.runInLocale(sResources, displayLocale);
- } else if (NO_LANGUAGE.equals(localeString)) {
- // No language subtype should be displayed in system locale.
- return sResources.getString(R.string.subtype_no_language);
} else {
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
displayName = locale.getDisplayName(displayLocale);
@@ -197,12 +208,14 @@ public final class SubtypeLocaleUtils {
// es_US spanish F Español (EE.UU.) exception
// fr azerty F Français
// fr_CA qwerty F Français (Canada)
+ // fr_CH swiss F Français (Suisse)
// de qwertz F Deutsch
- // zz qwerty F No language (QWERTY) in system locale
+ // de_CH swiss T Deutsch (Schweiz)
+ // zz qwerty F Alphabet (QWERTY) in system locale
// fr qwertz T Français (QWERTZ)
// de qwerty T Deutsch (QWERTY)
// en_US azerty T English (US) (AZERTY) exception
- // zz azerty T No language (AZERTY) in system locale
+ // zz azerty T Alphabet (AZERTY) in system locale
private static String getReplacementString(final InputMethodSubtype subtype,
final Locale displayLocale) {
@@ -289,45 +302,27 @@ public final class SubtypeLocaleUtils {
return keyboardLayoutSet;
}
- // InputMethodSubtype's display name for spacebar text in its locale.
- // isAdditionalSubtype (T=true, F=false)
- // locale layout | Short Middle Full
- // ------ ------- - ---- --------- ----------------------
- // en_US qwerty F En English English (US) exception
- // en_GB qwerty F En English English (UK) exception
- // es_US spanish F Es Español Español (EE.UU.) exception
- // fr azerty F Fr Français Français
- // fr_CA qwerty F Fr Français Français (Canada)
- // de qwertz F De Deutsch Deutsch
- // zz qwerty F QWERTY QWERTY
- // fr qwertz T Fr Français Français
- // de qwerty T De Deutsch Deutsch
- // en_US azerty T En 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 (isNoLanguage(subtype)) {
- return getKeyboardLayoutSetDisplayName(subtype);
- }
- return getSubtypeLocaleDisplayName(subtype.getLocale());
+ // TODO: Get this information from the framework instead of maintaining here by ourselves.
+ // Sorted list of known Right-To-Left language codes.
+ private static final String[] SORTED_RTL_LANGUAGES = {
+ "ar", // Arabic
+ "fa", // Persian
+ "iw", // Hebrew
+ };
+ static {
+ Arrays.sort(SORTED_RTL_LANGUAGES);
}
- // Get InputMethodSubtype's middle display name in its locale.
- public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
- if (isNoLanguage(subtype)) {
- return getKeyboardLayoutSetDisplayName(subtype);
- }
- final Locale locale = getSubtypeLocale(subtype);
- return getSubtypeLocaleDisplayName(locale.getLanguage());
+ public static boolean isRtlLanguage(final Locale locale) {
+ final String language = locale.getLanguage();
+ return Arrays.binarySearch(SORTED_RTL_LANGUAGES, language) >= 0;
}
- // Get InputMethodSubtype's short display name in its locale.
- public static String getShortDisplayName(final InputMethodSubtype subtype) {
- if (isNoLanguage(subtype)) {
- return "";
- }
- final Locale locale = getSubtypeLocale(subtype);
- return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale);
+ public static boolean isRtlLanguage(final InputMethodSubtype subtype) {
+ return isRtlLanguage(getSubtypeLocale(subtype));
+ }
+
+ public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) {
+ return subtype.getExtraValueOf(Constants.Subtype.ExtraValue.COMBINING_RULES);
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
new file mode 100644
index 000000000..5c109a68c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
@@ -0,0 +1,84 @@
+/*
+ * 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 com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.define.ProductionFlag;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.TreeSet;
+
+/**
+ * A TreeSet of SuggestedWordInfo that is bounded in size and throws everything that's smaller
+ * than its limit
+ */
+public final class SuggestionResults extends TreeSet<SuggestedWordInfo> {
+ public final Locale mLocale;
+ public final ArrayList<SuggestedWordInfo> mRawSuggestions;
+ private final int mCapacity;
+
+ public SuggestionResults(final Locale locale, final int capacity) {
+ this(locale, sSuggestedWordInfoComparator, capacity);
+ }
+
+ public SuggestionResults(final Locale locale, final Comparator<SuggestedWordInfo> comparator,
+ final int capacity) {
+ super(comparator);
+ mLocale = locale;
+ mCapacity = capacity;
+ if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) {
+ mRawSuggestions = new ArrayList<>();
+ } else {
+ mRawSuggestions = null;
+ }
+ }
+
+ @Override
+ public boolean add(final SuggestedWordInfo e) {
+ if (size() < mCapacity) return super.add(e);
+ if (comparator().compare(e, last()) > 0) return false;
+ super.add(e);
+ pollLast(); // removes the last element
+ return true;
+ }
+
+ @Override
+ public boolean addAll(final Collection<? extends SuggestedWordInfo> e) {
+ if (null == e) return false;
+ return super.addAll(e);
+ }
+
+ private 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
+ public int compare(final SuggestedWordInfo o1, final SuggestedWordInfo o2) {
+ if (o1.mScore > o2.mScore) return -1;
+ if (o1.mScore < o2.mScore) return 1;
+ if (o1.mCodePointCount < o2.mCodePointCount) return -1;
+ if (o1.mCodePointCount > o2.mCodePointCount) return 1;
+ return o1.mWord.compareTo(o2.mWord);
+ }
+ }
+
+ private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator =
+ new SuggestedWordInfoComparator();
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
index afbe2ecad..ab2b00e36 100644
--- a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
+++ b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
@@ -22,11 +22,12 @@ import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.util.LruCache;
+import com.android.inputmethod.compat.AppWorkaroundsUtils;
+
public final class TargetPackageInfoGetterTask extends
AsyncTask<String, Void, PackageInfo> {
private static final int MAX_CACHE_ENTRIES = 64; // arbitrary
- private static final LruCache<String, PackageInfo> sCache =
- new LruCache<String, PackageInfo>(MAX_CACHE_ENTRIES);
+ private static final LruCache<String, PackageInfo> sCache = new LruCache<>(MAX_CACHE_ENTRIES);
public static PackageInfo getCachedPackageInfo(final String packageName) {
if (null == packageName) return null;
@@ -37,17 +38,13 @@ public final class TargetPackageInfoGetterTask extends
sCache.remove(packageName);
}
- public interface OnTargetPackageInfoKnownListener {
- public void onTargetPackageInfoKnown(final PackageInfo info);
- }
-
private Context mContext;
- private final OnTargetPackageInfoKnownListener mListener;
+ private final AsyncResultHolder<AppWorkaroundsUtils> mResult;
public TargetPackageInfoGetterTask(final Context context,
- final OnTargetPackageInfoKnownListener listener) {
+ final AsyncResultHolder<AppWorkaroundsUtils> result) {
mContext = context;
- mListener = listener;
+ mResult = result;
}
@Override
@@ -65,6 +62,6 @@ public final class TargetPackageInfoGetterTask extends
@Override
protected void onPostExecute(final PackageInfo info) {
- mListener.onTargetPackageInfoKnown(info);
+ mResult.set(new AppWorkaroundsUtils(info));
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/TextRange.java b/java/src/com/android/inputmethod/latin/utils/TextRange.java
index 48b443ddd..dbf3b5060 100644
--- a/java/src/com/android/inputmethod/latin/utils/TextRange.java
+++ b/java/src/com/android/inputmethod/latin/utils/TextRange.java
@@ -31,6 +31,7 @@ public final class TextRange {
private final int mCursorIndex;
public final CharSequence mWord;
+ public final boolean mHasUrlSpans;
public int getNumberOfCharsInWordBeforeCursor() {
return mCursorIndex - mWordAtCursorStartIndex;
@@ -95,7 +96,7 @@ public final class TextRange {
}
}
if (spanStart == mWordAtCursorStartIndex && spanEnd == mWordAtCursorEndIndex) {
- // If the span does not start and stop here, we ignore it. It probably extends
+ // If the span does not start and stop here, ignore it. It probably extends
// past the start or end of the word, as happens in missing space correction
// or EasyEditSpans put by voice input.
spans[writeIndex++] = spans[readIndex];
@@ -105,7 +106,7 @@ public final class TextRange {
}
public TextRange(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
- final int wordAtCursorEndIndex, final int cursorIndex) {
+ final int wordAtCursorEndIndex, final int cursorIndex, final boolean hasUrlSpans) {
if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
|| cursorIndex > wordAtCursorEndIndex
|| wordAtCursorEndIndex > textAtCursor.length()) {
@@ -115,6 +116,7 @@ public final class TextRange {
mWordAtCursorStartIndex = wordAtCursorStartIndex;
mWordAtCursorEndIndex = wordAtCursorEndIndex;
mCursorIndex = cursorIndex;
+ mHasUrlSpans = hasUrlSpans;
mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex);
}
} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index 47ea1ea75..fafba79c2 100644
--- a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -22,16 +22,19 @@ import android.graphics.Typeface;
import android.util.SparseArray;
public final class TypefaceUtils {
+ private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
+ private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
+
private TypefaceUtils() {
// This utility class is not publicly instantiable.
}
// This sparse array caches key label text height in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
+ private static final SparseArray<Float> sTextHeightCache = new SparseArray<>();
// Working variable for the following method.
private static final Rect sTextHeightBounds = new Rect();
- public static float getCharHeight(final char[] referenceChar, final Paint paint) {
+ private static float getCharHeight(final char[] referenceChar, final Paint paint) {
final int key = getCharGeometryCacheKey(referenceChar[0], paint);
synchronized (sTextHeightCache) {
final Float cachedValue = sTextHeightCache.get(key);
@@ -47,11 +50,11 @@ public final class TypefaceUtils {
}
// This sparse array caches key label text width in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
+ private static final SparseArray<Float> sTextWidthCache = new SparseArray<>();
// Working variable for the following method.
private static final Rect sTextWidthBounds = new Rect();
- public static float getCharWidth(final char[] referenceChar, final Paint paint) {
+ private static float getCharWidth(final char[] referenceChar, final Paint paint) {
final int key = getCharGeometryCacheKey(referenceChar[0], paint);
synchronized (sTextWidthCache) {
final Float cachedValue = sTextWidthCache.get(key);
@@ -66,11 +69,6 @@ public final class TypefaceUtils {
}
}
- public static float getStringWidth(final String string, final Paint paint) {
- paint.getTextBounds(string, 0, string.length(), sTextWidthBounds);
- return sTextWidthBounds.width();
- }
-
private static int getCharGeometryCacheKey(final char referenceChar, final Paint paint) {
final int labelSize = (int)paint.getTextSize();
final Typeface face = paint.getTypeface();
@@ -86,9 +84,25 @@ public final class TypefaceUtils {
}
}
- public static float getLabelWidth(final String label, final Paint paint) {
- final Rect textBounds = new Rect();
- paint.getTextBounds(label, 0, label.length(), textBounds);
- return textBounds.width();
+ public static float getReferenceCharHeight(final Paint paint) {
+ return getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ public static float getReferenceCharWidth(final Paint paint) {
+ return getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ public static float getReferenceDigitWidth(final Paint paint) {
+ return getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint);
+ }
+
+ // Working variable for the following method.
+ private static final Rect sStringWidthBounds = new Rect();
+
+ public static float getStringWidth(final String string, final Paint paint) {
+ synchronized (sStringWidthBounds) {
+ paint.getTextBounds(string, 0, string.length(), sStringWidthBounds);
+ return sStringWidthBounds.width();
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java b/java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java
new file mode 100644
index 000000000..5df00efb9
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/UncachedInputMethodManagerUtils.java
@@ -0,0 +1,84 @@
+/*
+ * 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.content.Context;
+import android.provider.Settings;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+
+/*
+ * A utility class for {@link InputMethodManager}. Unlike {@link RichInputMethodManager}, this
+ * class provides synchronous, non-cached access to {@link InputMethodManager}. The setup activity
+ * is a good example to use this class because {@link InputMethodManagerService} may not be aware of
+ * this IME immediately after this IME is installed.
+ */
+public final class UncachedInputMethodManagerUtils {
+ /**
+ * Check if the IME specified by the context is enabled.
+ * CAVEAT: This may cause a round trip IPC.
+ *
+ * @param context package context of the IME to be checked.
+ * @param imm the {@link InputMethodManager}.
+ * @return true if this IME is enabled.
+ */
+ public static boolean isThisImeEnabled(final Context context,
+ final InputMethodManager imm) {
+ final String packageName = context.getPackageName();
+ for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+ if (packageName.equals(imi.getPackageName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if the IME specified by the context is the current IME.
+ * CAVEAT: This may cause a round trip IPC.
+ *
+ * @param context package context of the IME to be checked.
+ * @param imm the {@link InputMethodManager}.
+ * @return true if this IME is the current IME.
+ */
+ public static boolean isThisImeCurrent(final Context context,
+ final InputMethodManager imm) {
+ final InputMethodInfo imi = getInputMethodInfoOf(context.getPackageName(), imm);
+ final String currentImeId = Settings.Secure.getString(
+ context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+ return imi != null && imi.getId().equals(currentImeId);
+ }
+
+ /**
+ * Get {@link InputMethodInfo} of the IME specified by the package name.
+ * CAVEAT: This may cause a round trip IPC.
+ *
+ * @param packageName package name of the IME.
+ * @param imm the {@link InputMethodManager}.
+ * @return the {@link InputMethodInfo} of the IME specified by the <code>packageName</code>,
+ * or null if not found.
+ */
+ public static InputMethodInfo getInputMethodInfoOf(final String packageName,
+ final InputMethodManager imm) {
+ for (final InputMethodInfo imi : imm.getInputMethodList()) {
+ if (packageName.equals(imi.getPackageName())) {
+ return imi;
+ }
+ }
+ return null;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
deleted file mode 100644
index 06826dac0..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.inputmethodservice.InputMethodService;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.inputmethod.latin.LatinImeLogger;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.channels.FileChannel;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-public final class UsabilityStudyLogUtils {
- // TODO: remove code duplication with ResearchLog class
- private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
- private static final String FILENAME = "log.txt";
- private final Handler mLoggingHandler;
- private File mFile;
- private File mDirectory;
- private InputMethodService mIms;
- private PrintWriter mWriter;
- private final Date mDate;
- private final SimpleDateFormat mDateFormat;
-
- private UsabilityStudyLogUtils() {
- mDate = new Date();
- mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
-
- HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
- Process.THREAD_PRIORITY_BACKGROUND);
- handlerThread.start();
- mLoggingHandler = new Handler(handlerThread.getLooper());
- }
-
- // Initialization-on-demand holder
- private static final class OnDemandInitializationHolder {
- public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
- }
-
- public static UsabilityStudyLogUtils getInstance() {
- return OnDemandInitializationHolder.sInstance;
- }
-
- public void init(final InputMethodService ims) {
- mIms = ims;
- mDirectory = ims.getFilesDir();
- }
-
- private void createLogFileIfNotExist() {
- if ((mFile == null || !mFile.exists())
- && (mDirectory != null && mDirectory.exists())) {
- try {
- mWriter = getPrintWriter(mDirectory, FILENAME, false);
- } catch (final IOException e) {
- Log.e(USABILITY_TAG, "Can't create log file.");
- }
- }
- }
-
- public static void writeBackSpace(final int x, final int y) {
- UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
- }
-
- public static void writeChar(final char c, final int x, final int y) {
- String inputChar = String.valueOf(c);
- switch (c) {
- case '\n':
- inputChar = "<enter>";
- break;
- case '\t':
- inputChar = "<tab>";
- break;
- case ' ':
- inputChar = "<space>";
- break;
- }
- UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
- LatinImeLogger.onPrintAllUsabilityStudyLogs();
- }
-
- public static void writeMotionEvent(final MotionEvent me) {
- final int action = me.getActionMasked();
- final long eventTime = me.getEventTime();
- final int pointerCount = me.getPointerCount();
- for (int index = 0; index < pointerCount; index++) {
- final int id = me.getPointerId(index);
- final int x = (int)me.getX(index);
- final int y = (int)me.getY(index);
- final float size = me.getSize(index);
- final float pressure = me.getPressure(index);
-
- final String eventTag;
- switch (action) {
- case MotionEvent.ACTION_UP:
- eventTag = "[Up]";
- break;
- case MotionEvent.ACTION_DOWN:
- eventTag = "[Down]";
- break;
- case MotionEvent.ACTION_POINTER_UP:
- eventTag = "[PointerUp]";
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- eventTag = "[PointerDown]";
- break;
- case MotionEvent.ACTION_MOVE:
- eventTag = "[Move]";
- break;
- default:
- eventTag = "[Action" + action + "]";
- break;
- }
- getInstance().write(eventTag + eventTime + "," + id + "," + x + "," + y + "," + size
- + "," + pressure);
- }
- }
-
- public void write(final String log) {
- mLoggingHandler.post(new Runnable() {
- @Override
- public void run() {
- createLogFileIfNotExist();
- final long currentTime = System.currentTimeMillis();
- mDate.setTime(currentTime);
-
- final String printString = String.format(Locale.US, "%s\t%d\t%s\n",
- mDateFormat.format(mDate), currentTime, log);
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "Write: " + log);
- }
- mWriter.print(printString);
- }
- });
- }
-
- private synchronized String getBufferedLogs() {
- mWriter.flush();
- final StringBuilder sb = new StringBuilder();
- final BufferedReader br = getBufferedReader();
- String line;
- try {
- while ((line = br.readLine()) != null) {
- sb.append('\n');
- sb.append(line);
- }
- } catch (final IOException e) {
- Log.e(USABILITY_TAG, "Can't read log file.");
- } finally {
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
- }
- try {
- br.close();
- } catch (final IOException e) {
- // ignore.
- }
- }
- return sb.toString();
- }
-
- public void emailResearcherLogsAll() {
- mLoggingHandler.post(new Runnable() {
- @Override
- public void run() {
- final Date date = new Date();
- date.setTime(System.currentTimeMillis());
- final String currentDateTimeString =
- new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date);
- if (mFile == null) {
- Log.w(USABILITY_TAG, "No internal log file found.");
- return;
- }
- if (mIms.checkCallingOrSelfPermission(
- android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
- != PackageManager.PERMISSION_GRANTED) {
- Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
- return;
- }
- mWriter.flush();
- final String destPath = Environment.getExternalStorageDirectory()
- + "/research-" + currentDateTimeString + ".log";
- final File destFile = new File(destPath);
- try {
- final FileInputStream srcStream = new FileInputStream(mFile);
- final FileOutputStream destStream = new FileOutputStream(destFile);
- final FileChannel src = srcStream.getChannel();
- final FileChannel dest = destStream.getChannel();
- src.transferTo(0, src.size(), dest);
- src.close();
- srcStream.close();
- dest.close();
- destStream.close();
- } catch (final FileNotFoundException e1) {
- Log.w(USABILITY_TAG, e1);
- return;
- } catch (final IOException e2) {
- Log.w(USABILITY_TAG, e2);
- return;
- }
- if (!destFile.exists()) {
- Log.w(USABILITY_TAG, "Dest file doesn't exist.");
- return;
- }
- final Intent intent = new Intent(Intent.ACTION_SEND);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
- }
- intent.setType("text/plain");
- intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
- intent.putExtra(Intent.EXTRA_SUBJECT,
- "[Research Logs] " + currentDateTimeString);
- mIms.startActivity(intent);
- }
- });
- }
-
- public void printAll() {
- mLoggingHandler.post(new Runnable() {
- @Override
- public void run() {
- mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
- }
- });
- }
-
- public void clearAll() {
- mLoggingHandler.post(new Runnable() {
- @Override
- public void run() {
- if (mFile != null && mFile.exists()) {
- if (LatinImeLogger.sDBG) {
- Log.d(USABILITY_TAG, "Delete log file.");
- }
- mFile.delete();
- mWriter.close();
- }
- }
- });
- }
-
- private BufferedReader getBufferedReader() {
- createLogFileIfNotExist();
- try {
- return new BufferedReader(new FileReader(mFile));
- } catch (final FileNotFoundException e) {
- return null;
- }
- }
-
- private PrintWriter getPrintWriter(final File dir, final String filename,
- final boolean renew) throws IOException {
- mFile = new File(dir, filename);
- if (mFile.exists()) {
- if (renew) {
- mFile.delete();
- }
- }
- return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
deleted file mode 100644
index 635afe7cc..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
+++ /dev/null
@@ -1,182 +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 android.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
-import com.android.inputmethod.latin.makedict.DictDecoder;
-import com.android.inputmethod.latin.makedict.DictEncoder;
-import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
-import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.PtNodeArray;
-import com.android.inputmethod.latin.makedict.PendingAttribute;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
-import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Reads and writes Binary files for a UserHistoryDictionary.
- *
- * All the methods in this class are static.
- */
-public final class UserHistoryDictIOUtils {
- private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName();
- private static final boolean DEBUG = false;
- private static final String USES_FORGETTING_CURVE_KEY = "USES_FORGETTING_CURVE";
- private static final String USES_FORGETTING_CURVE_VALUE = "1";
- private static final String LAST_UPDATED_TIME_KEY = "date";
-
- public interface OnAddWordListener {
- /**
- * Callback to be notified when a word is added to the dictionary.
- * @param word The added word.
- * @param shortcutTarget A shortcut target for this word, or null if none.
- * @param frequency The frequency for this word.
- * @param shortcutFreq The frequency of the shortcut (0~15, with 15 = whitelist).
- * Unspecified if shortcutTarget is null - do not rely on its value.
- */
- public void setUnigram(final String word, final String shortcutTarget, final int frequency,
- final int shortcutFreq);
- public void setBigram(final String word1, final String word2, final int frequency);
- }
-
- @UsedForTesting
- public interface BigramDictionaryInterface {
- public int getFrequency(final String word1, final String word2);
- }
-
- /**
- * Writes dictionary to file.
- */
- public static void writeDictionary(final DictEncoder dictEncoder,
- final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams,
- final FormatOptions formatOptions) {
- final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams);
- fusionDict.addOptionAttribute(USES_FORGETTING_CURVE_KEY, USES_FORGETTING_CURVE_VALUE);
- fusionDict.addOptionAttribute(LAST_UPDATED_TIME_KEY,
- String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())));
- try {
- dictEncoder.writeDictionary(fusionDict, formatOptions);
- Log.d(TAG, "end writing");
- } catch (IOException e) {
- Log.e(TAG, "IO exception while writing file", e);
- } catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported format", e);
- }
- }
-
- /**
- * Constructs a new FusionDictionary from BigramDictionaryInterface.
- */
- @UsedForTesting
- static FusionDictionary constructFusionDictionary(
- final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) {
- final FusionDictionary fusionDict = new FusionDictionary(new PtNodeArray(),
- new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false,
- false));
- int profTotal = 0;
- for (final String word1 : bigrams.keySet()) {
- final HashMap<String, Byte> word1Bigrams = bigrams.getBigrams(word1);
- for (final String word2 : word1Bigrams.keySet()) {
- final int freq = dict.getFrequency(word1, word2);
- if (freq == -1) {
- // don't add this bigram.
- continue;
- }
- if (DEBUG) {
- if (word1 == null) {
- Log.d(TAG, "add unigram: " + word2 + "," + Integer.toString(freq));
- } else {
- Log.d(TAG, "add bigram: " + word1
- + "," + word2 + "," + Integer.toString(freq));
- }
- profTotal++;
- }
- if (word1 == null) { // unigram
- fusionDict.add(word2, freq, null, false /* isNotAWord */);
- } else { // bigram
- if (FusionDictionary.findWordInTree(fusionDict.mRootNodeArray, word1) == null) {
- fusionDict.add(word1, 2, null, false /* isNotAWord */);
- }
- fusionDict.setBigram(word1, word2, freq);
- }
- bigrams.updateBigram(word1, word2, (byte)freq);
- }
- }
- if (DEBUG) {
- Log.d(TAG, "add " + profTotal + "words");
- }
- return fusionDict;
- }
-
- /**
- * Reads dictionary from file.
- */
- public static void readDictionaryBinary(final DictDecoder dictDecoder,
- final OnAddWordListener dict) {
- final TreeMap<Integer, String> unigrams = CollectionUtils.newTreeMap();
- final TreeMap<Integer, Integer> frequencies = CollectionUtils.newTreeMap();
- final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap();
- try {
- dictDecoder.readUnigramsAndBigramsBinary(unigrams, frequencies, bigrams);
- } catch (IOException e) {
- Log.e(TAG, "IO exception while reading file", e);
- } catch (UnsupportedFormatException e) {
- Log.e(TAG, "Unsupported format", e);
- } catch (ArrayIndexOutOfBoundsException e) {
- Log.e(TAG, "ArrayIndexOutOfBoundsException while reading file", e);
- }
- addWordsFromWordMap(unigrams, frequencies, bigrams, dict);
- }
-
- /**
- * Adds all unigrams and bigrams in maps to OnAddWordListener.
- */
- @UsedForTesting
- static void addWordsFromWordMap(final TreeMap<Integer, String> unigrams,
- final TreeMap<Integer, Integer> frequencies,
- final TreeMap<Integer, ArrayList<PendingAttribute>> bigrams,
- final OnAddWordListener to) {
- for (Entry<Integer, String> entry : unigrams.entrySet()) {
- final String word1 = entry.getValue();
- final int unigramFrequency = frequencies.get(entry.getKey());
- to.setUnigram(word1, null /* shortcutTarget */, unigramFrequency, 0 /* shortcutFreq */);
- final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey());
- if (attrList != null) {
- for (final PendingAttribute attr : attrList) {
- final String word2 = unigrams.get(attr.mAddress);
- if (word1 == null || word2 == null) {
- Log.e(TAG, "Invalid bigram pair detected: " + word1 + ", " + word2);
- continue;
- }
- to.setBigram(word1, word2,
- BinaryDictIOUtils.reconstructBigramFrequency(unigramFrequency,
- attr.mFrequency));
- }
- }
- }
-
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
deleted file mode 100644
index 1992b2f5d..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
+++ /dev/null
@@ -1,233 +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 android.util.Log;
-
-import java.util.concurrent.TimeUnit;
-
-public final class UserHistoryForgettingCurveUtils {
- private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
- private static final boolean DEBUG = false;
- private static final int DEFAULT_FC_FREQ = 127;
- private static final int BOOSTED_FC_FREQ = 200;
- private static int FC_FREQ_MAX = DEFAULT_FC_FREQ;
- /* package */ static final int COUNT_MAX = 3;
- private static final int FC_LEVEL_MAX = 3;
- /* package */ static final int ELAPSED_TIME_MAX = 15;
- private static final int ELAPSED_TIME_INTERVAL_HOURS = 6;
- private static final long ELAPSED_TIME_INTERVAL_MILLIS =
- TimeUnit.HOURS.toMillis(ELAPSED_TIME_INTERVAL_HOURS);
- private static final int HALF_LIFE_HOURS = 48;
- private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1);
-
- public static void boostMaxFreqForDebug() {
- FC_FREQ_MAX = BOOSTED_FC_FREQ;
- }
-
- public static void resetMaxFreqForDebug() {
- FC_FREQ_MAX = DEFAULT_FC_FREQ;
- }
-
- private UserHistoryForgettingCurveUtils() {
- // This utility class is not publicly instantiable.
- }
-
- public static final class ForgettingCurveParams {
- private byte mFc;
- long mLastTouchedTime = 0;
- private final boolean mIsValid;
-
- private void updateLastTouchedTime() {
- mLastTouchedTime = System.currentTimeMillis();
- }
-
- public ForgettingCurveParams(boolean isValid) {
- this(System.currentTimeMillis(), isValid);
- }
-
- private ForgettingCurveParams(long now, boolean isValid) {
- this(pushCount((byte)0, isValid), now, now, isValid);
- }
-
- /** This constructor is called when the user history bigram dictionary is being restored. */
- public ForgettingCurveParams(int fc, long now, long last) {
- // All words with level >= 1 had been saved.
- // Invalid words with level == 0 had been saved.
- // Valid words words with level == 0 had *not* been saved.
- this(fc, now, last, fcToLevel((byte)fc) > 0);
- }
-
- private ForgettingCurveParams(int fc, long now, long last, boolean isValid) {
- mIsValid = isValid;
- mFc = (byte)fc;
- mLastTouchedTime = last;
- updateElapsedTime(now);
- }
-
- public boolean isValid() {
- return mIsValid;
- }
-
- public byte getFc() {
- updateElapsedTime(System.currentTimeMillis());
- return mFc;
- }
-
- public int getFrequency() {
- updateElapsedTime(System.currentTimeMillis());
- return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
- }
-
- public int notifyTypedAgainAndGetFrequency() {
- updateLastTouchedTime();
- // TODO: Check whether this word is valid or not
- mFc = pushCount(mFc, false);
- return UserHistoryForgettingCurveUtils.fcToFreq(mFc);
- }
-
- private void updateElapsedTime(long now) {
- final int elapsedTimeCount =
- (int)((now - mLastTouchedTime) / ELAPSED_TIME_INTERVAL_MILLIS);
- if (elapsedTimeCount <= 0) {
- return;
- }
- if (elapsedTimeCount >= MAX_PUSH_ELAPSED) {
- mLastTouchedTime = now;
- mFc = 0;
- return;
- }
- for (int i = 0; i < elapsedTimeCount; ++i) {
- mLastTouchedTime += ELAPSED_TIME_INTERVAL_MILLIS;
- mFc = pushElapsedTime(mFc);
- }
- }
- }
-
- /* package */ static int fcToElapsedTime(byte fc) {
- return fc & 0x0F;
- }
-
- /* package */ static int fcToCount(byte fc) {
- return (fc >> 4) & 0x03;
- }
-
- /* package */ static int fcToLevel(byte fc) {
- return (fc >> 6) & 0x03;
- }
-
- private static int calcFreq(int elapsedTime, int count, int level) {
- if (level <= 0) {
- // Reserved words, just return -1
- return -1;
- }
- if (count == COUNT_MAX) {
- // Temporary promote because it's frequently typed recently
- ++level;
- }
- final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
- final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
- return MathUtils.SCORE_TABLE[l - 1][et];
- }
-
- /* pakcage */ static byte calcFc(int elapsedTime, int count, int level) {
- final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime));
- final int c = Math.min(COUNT_MAX, Math.max(0, count));
- final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level));
- return (byte)(et | (c << 4) | (l << 6));
- }
-
- public static int fcToFreq(byte fc) {
- final int elapsedTime = fcToElapsedTime(fc);
- final int count = fcToCount(fc);
- final int level = fcToLevel(fc);
- return calcFreq(elapsedTime, count, level);
- }
-
- public static byte pushElapsedTime(byte fc) {
- int elapsedTime = fcToElapsedTime(fc);
- int count = fcToCount(fc);
- int level = fcToLevel(fc);
- if (elapsedTime >= ELAPSED_TIME_MAX) {
- // Downgrade level
- elapsedTime = 0;
- count = COUNT_MAX;
- --level;
- } else {
- ++elapsedTime;
- }
- return calcFc(elapsedTime, count, level);
- }
-
- public static byte pushCount(byte fc, boolean isValid) {
- final int elapsedTime = fcToElapsedTime(fc);
- int count = fcToCount(fc);
- int level = fcToLevel(fc);
- if ((elapsedTime == 0 && count >= COUNT_MAX) || (isValid && level == 0)) {
- // Upgrade level
- ++level;
- count = 0;
- if (DEBUG) {
- Log.d(TAG, "Upgrade level.");
- }
- } else {
- ++count;
- }
- return calcFc(0, count, level);
- }
-
- // TODO: isValid should be false for a word whose frequency is 0,
- // or that is not in the dictionary.
- /**
- * Check wheather we should save the bigram to the SQL DB or not
- */
- public static boolean needsToSave(byte fc, boolean isValid, boolean addLevel0Bigram) {
- int level = fcToLevel(fc);
- if (level == 0) {
- if (isValid || !addLevel0Bigram) {
- return false;
- }
- }
- final int elapsedTime = fcToElapsedTime(fc);
- return (elapsedTime < ELAPSED_TIME_MAX - 1 || level > 0);
- }
-
- private static final class MathUtils {
- public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1];
- static {
- for (int i = 0; i < FC_LEVEL_MAX; ++i) {
- final float initialFreq;
- if (i >= 2) {
- initialFreq = FC_FREQ_MAX;
- } else if (i == 1) {
- initialFreq = FC_FREQ_MAX / 2;
- } else if (i == 0) {
- initialFreq = FC_FREQ_MAX / 4;
- } else {
- continue;
- }
- for (int j = 0; j < ELAPSED_TIME_MAX; ++j) {
- final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS;
- final float freq = initialFreq
- * (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS);
- final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq));
- SCORE_TABLE[i][j] = intFreq;
- }
- }
- }
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
deleted file mode 100644
index a75d353c9..000000000
--- a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.inputmethodservice.InputMethodService;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.settings.Settings;
-
-public final class UserLogRingCharBuffer {
- public /* for test */ static final int BUFSIZE = 20;
- public /* for test */ int mLength = 0;
-
- private static UserLogRingCharBuffer sUserLogRingCharBuffer = new UserLogRingCharBuffer();
- private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
- private static final int INVALID_COORDINATE = -2;
- private boolean mEnabled = false;
- private int mEnd = 0;
- private char[] mCharBuf = new char[BUFSIZE];
- private int[] mXBuf = new int[BUFSIZE];
- private int[] mYBuf = new int[BUFSIZE];
-
- private UserLogRingCharBuffer() {
- // Intentional empty constructor for singleton.
- }
-
- @UsedForTesting
- public static UserLogRingCharBuffer getInstance() {
- return sUserLogRingCharBuffer;
- }
-
- public static UserLogRingCharBuffer init(final InputMethodService context,
- final boolean enabled, final boolean usabilityStudy) {
- if (!(enabled || usabilityStudy)) {
- return null;
- }
- sUserLogRingCharBuffer.mEnabled = true;
- UsabilityStudyLogUtils.getInstance().init(context);
- return sUserLogRingCharBuffer;
- }
-
- private static int normalize(final int in) {
- int ret = in % BUFSIZE;
- return ret < 0 ? ret + BUFSIZE : ret;
- }
-
- // TODO: accept code points
- @UsedForTesting
- public void push(final char c, final int x, final int y) {
- if (!mEnabled) {
- return;
- }
- if (LatinImeLogger.sUsabilityStudy) {
- UsabilityStudyLogUtils.getInstance().writeChar(c, x, y);
- }
- mCharBuf[mEnd] = c;
- mXBuf[mEnd] = x;
- mYBuf[mEnd] = y;
- mEnd = normalize(mEnd + 1);
- if (mLength < BUFSIZE) {
- ++mLength;
- }
- }
-
- public char pop() {
- if (mLength < 1) {
- return PLACEHOLDER_DELIMITER_CHAR;
- }
- mEnd = normalize(mEnd - 1);
- --mLength;
- return mCharBuf[mEnd];
- }
-
- public char getBackwardNthChar(final int n) {
- if (mLength <= n || n < 0) {
- return PLACEHOLDER_DELIMITER_CHAR;
- }
- return mCharBuf[normalize(mEnd - n - 1)];
- }
-
- public int getPreviousX(final char c, final int back) {
- final int index = normalize(mEnd - 2 - back);
- if (mLength <= back
- || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
- return INVALID_COORDINATE;
- }
- return mXBuf[index];
- }
-
- public int getPreviousY(final char c, final int back) {
- int index = normalize(mEnd - 2 - back);
- if (mLength <= back
- || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
- return INVALID_COORDINATE;
- }
- return mYBuf[index];
- }
-
- public String getLastWord(final int ignoreCharCount) {
- final StringBuilder sb = new StringBuilder();
- int i = ignoreCharCount;
- for (; i < mLength; ++i) {
- final char c = mCharBuf[normalize(mEnd - 1 - i)];
- if (!Settings.getInstance().isWordSeparator(c)) {
- break;
- }
- }
- for (; i < mLength; ++i) {
- char c = mCharBuf[normalize(mEnd - 1 - i)];
- if (!Settings.getInstance().isWordSeparator(c)) {
- sb.append(c);
- } else {
- break;
- }
- }
- return sb.reverse().toString();
- }
-
- public void reset() {
- mLength = 0;
- }
-}