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.java81
-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.java54
-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.java29
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CollectionUtils.java15
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java99
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java46
-rw-r--r--java/src/com/android/inputmethod/latin/utils/CsvUtils.java7
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DialogUtils.java34
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java66
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java48
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java60
-rw-r--r--java/src/com/android/inputmethod/latin/utils/FileUtils.java54
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java125
-rw-r--r--java/src/com/android/inputmethod/latin/utils/JsonUtils.java103
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java177
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java2
-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.java47
-rw-r--r--java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java37
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java18
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java2
-rw-r--r--java/src/com/android/inputmethod/latin/utils/ResourceUtils.java60
-rw-r--r--java/src/com/android/inputmethod/latin/utils/RunInLocale.java20
-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.java53
-rw-r--r--java/src/com/android/inputmethod/latin/utils/StringUtils.java315
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java96
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SuggestionResults.java76
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java14
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TextRange.java6
-rw-r--r--java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java36
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java182
-rw-r--r--java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java233
39 files changed, 1612 insertions, 983 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..2bb30a2ba 100644
--- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -17,32 +17,43 @@
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.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,
@@ -79,17 +90,6 @@ 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;
@@ -98,7 +98,19 @@ public final class AdditionalSubtypeUtils {
final ArrayList<InputMethodSubtype> subtypesList =
CollectionUtils.newArrayList(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];
+ final String extraValue = (elems.length == LENGTH_WITH_EXTRA_VALUE)
+ ? elems[INDEX_OF_EXTRA_VALUE] : null;
+ final InputMethodSubtype subtype = createAdditionalSubtype(
+ localeString, keyboardLayoutSetName, extraValue);
if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
// Skip unknown keyboard layout subtype. This may happen when predefined keyboard
// layout has been removed.
@@ -137,31 +149,36 @@ 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);
+ private static InputMethodSubtype buildInputMethodSubtype(final int nameId,
+ final String localeString, final String layoutExtraValue,
+ final String additionalSubtypeExtraValue) {
+ // To preserve additional subtype settings and user's selection across OS updates, subtype
+ // id shouldn't be changed. New attributes, such as emojiCapable, are carefully excluded
+ // from the calculation of subtype id.
+ final String compatibleExtraValue = StringUtils.joinCommaSplittableText(
+ layoutExtraValue, additionalSubtypeExtraValue);
+ final int compatibleSubtypeId = getInputMethodSubtypeId(localeString, compatibleExtraValue);
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;
+ // Color Emoji is supported from KitKat.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ extraValue = StringUtils.appendToCommaSplittableTextIfNotExists(
+ EMOJI_CAPABLE, compatibleExtraValue);
} else {
- extraValue = layoutExtraValue + "," + additionalSubtypeExtraValue;
+ extraValue = compatibleExtraValue;
}
return InputMethodSubtypeCompatUtils.newInputMethodSubtype(nameId,
R.drawable.ic_ime_switcher_dark, localeString, KEYBOARD_MODE, extraValue,
- false, false, subtypeId);
+ false, false, compatibleSubtypeId);
}
- 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();
+ private static int getInputMethodSubtypeId(final String localeString, final String extraValue) {
+ // From the compatibility point of view, the calculation of subtype id has been copied from
+ // {@link InputMethodSubtype} of JellyBean MR2.
+ return Arrays.hashCode(new Object[] {
+ localeString,
+ KEYBOARD_MODE,
+ extraValue,
+ 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..22b9b77d2 100644
--- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -17,16 +17,11 @@
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;
-
public final class AutoCorrectionUtils {
private static final boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
@@ -36,48 +31,6 @@ 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) {
@@ -87,7 +40,7 @@ public final class AutoCorrectionUtils {
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 +71,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..702688f93 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;
@@ -74,7 +74,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 +83,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.
@@ -139,6 +139,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 +181,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,7 +204,7 @@ 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;
}
@@ -241,7 +254,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,7 +270,7 @@ 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;
diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
index cc25102ce..bbfa0f091 100644
--- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
@@ -102,4 +102,19 @@ public final class CollectionUtils {
public static <E> SparseArray<E> newSparseArray() {
return new SparseArray<E>();
}
+
+ public static <E> ArrayList<E> arrayAsList(final E[] array, final int start, final int end) {
+ if (array == null) {
+ throw new NullPointerException();
+ }
+ if (start < 0 || start > end || end > array.length) {
+ throw new IllegalArgumentException();
+ }
+
+ final ArrayList<E> list = newArrayList(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..c66007537
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java
@@ -0,0 +1,99 @@
+/*
+ * 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 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.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..b18a1d83b 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 {
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..315913e2f 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();
@@ -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..f2a1e524d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -0,0 +1,48 @@
+/*
+ * 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.keyboard.Keyboard;
+import com.android.inputmethod.latin.Suggest;
+
+/**
+ * This class is used to prevent distracters/misspellings being added to personalization
+ * or user history dictionaries
+ */
+public class DistracterFilter {
+ private final Suggest mSuggest;
+ private final Keyboard mKeyboard;
+
+ /**
+ * Create a DistracterFilter instance.
+ *
+ * @param suggest an instance of Suggest which will be used to obtain a list of suggestions
+ * for a potential distracter/misspelling
+ * @param keyboard the keyboard that is currently being used. This information is needed
+ * when calling mSuggest.getSuggestedWords(...) to obtain a list of suggestions.
+ */
+ public DistracterFilter(final Suggest suggest, final Keyboard keyboard) {
+ mSuggest = suggest;
+ mKeyboard = keyboard;
+ }
+
+ public boolean isDistractorToWordsInDictionaries(final String prevWord,
+ final String targetWord) {
+ // TODO: to be implemented
+ return false;
+ }
+}
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..ed502ed3d
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+/**
+ * Utilities to manage executors.
+ */
+public class ExecutorUtils {
+ private static final ConcurrentHashMap<String, PrioritizedSerialExecutor>
+ sExecutorMap = CollectionUtils.newConcurrentHashMap();
+ /**
+ * Gets the executor for the given dictionary name.
+ */
+ public static PrioritizedSerialExecutor getExecutor(final String dictName) {
+ PrioritizedSerialExecutor executor = sExecutorMap.get(dictName);
+ if (executor == null) {
+ synchronized(sExecutorMap) {
+ executor = new PrioritizedSerialExecutor();
+ sExecutorMap.put(dictName, 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 PrioritizedSerialExecutor 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/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
new file mode 100644
index 000000000..7d937a9d2
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java
@@ -0,0 +1,125 @@
+/*
+ * 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.InputAttributes;
+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,
+ final InputAttributes inputAttributes) {
+ if (inputAttributes == null || inputAttributes.mIsPasswordField) {
+ return false;
+ }
+ if (isInSystemSetupWizard(context)) {
+ return false;
+ }
+ if (!hasNewImportantNotice(context)) {
+ return false;
+ }
+ final String importantNoticeTitle = getNextImportantNoticeTitle(context);
+ if (TextUtils.isEmpty(importantNoticeTitle)) {
+ 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..764ef72ce
--- /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 = 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_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..5ce977d5e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -0,0 +1,177 @@
+/*
+ * 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.DictionaryFacilitatorForSuggest;
+import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
+
+import java.util.ArrayList;
+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 ArrayList<String> tokens, final int timestamp,
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator,
+ final SpacingAndPunctuations spacingAndPunctuations) {
+ final ArrayList<LanguageModelParam> languageModelParams =
+ CollectionUtils.newArrayList();
+ final int N = tokens.size();
+ String prevWord = null;
+ 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.
+ prevWord = null;
+ continue;
+ }
+ if (DEBUG_TOKEN) {
+ Log.d(TAG, "--- word: \"" + tempWord + "\"");
+ }
+ final LanguageModelParam languageModelParam =
+ detectWhetherVaildWordOrNotAndGetLanguageModelParam(
+ prevWord, tempWord, timestamp, dictionaryFacilitator);
+ if (languageModelParam == null) {
+ continue;
+ }
+ languageModelParams.add(languageModelParam);
+ prevWord = languageModelParam.mTargetWord;
+ }
+ return languageModelParams;
+ }
+
+ private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam(
+ final String prevWord, final String targetWord, final int timestamp,
+ final DictionaryFacilitatorForSuggest dictionaryFacilitator) {
+ final Locale locale = dictionaryFacilitator.getLocale();
+ if (locale == null) {
+ return null;
+ }
+ if (!dictionaryFacilitator.isValidWord(targetWord, true /* ignoreCase */)) {
+ // OOV word.
+ return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
+ false /* isValidWord */, locale);
+ }
+ if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
+ return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
+ true /* isValidWord */, locale);
+ }
+ final String lowerCaseTargetWord = targetWord.toLowerCase(locale);
+ if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) {
+ // Add the lower-cased word.
+ return createAndGetLanguageModelParamOfWord(prevWord, lowerCaseTargetWord,
+ timestamp, true /* isValidWord */, locale);
+ }
+ // Treat the word as an OOV word.
+ return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
+ false /* isValidWord */, locale);
+ }
+
+ private static LanguageModelParam createAndGetLanguageModelParamOfWord(
+ final String prevWord, final String targetWord, final int timestamp,
+ final boolean isValidWord, final Locale locale) {
+ final String word;
+ if (StringUtils.getCapitalizationType(targetWord) == StringUtils.CAPITALIZE_FIRST
+ && prevWord == null && !isValidWord) {
+ word = targetWord.toLowerCase(locale);
+ } else {
+ word = targetWord;
+ }
+ final int unigramProbability = isValidWord ?
+ UNIGRAM_PROBABILITY_FOR_VALID_WORD : UNIGRAM_PROBABILITY_FOR_OOV_WORD;
+ if (prevWord == null) {
+ 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 = " + prevWord + ", current("
+ + (isValidWord ? "Valid" : "OOV") + ") = " + word);
+ }
+ final int bigramProbability = isValidWord ?
+ BIGRAM_PROBABILITY_FOR_VALID_WORD : BIGRAM_PROBABILITY_FOR_OOV_WORD;
+ return new LanguageModelParam(prevWord, word, unigramProbability,
+ bigramProbability, timestamp);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
index e958a7e71..d14ba508b 100644
--- a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
@@ -35,7 +35,7 @@ public final class LatinImeLoggerUtils {
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);
+ onSeparator(StringUtils.newSingleCodePointString(code), x, y);
}
public static void onSeparator(final String separator, final int x, final int y) {
diff --git a/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java
index 44e5d17b4..8469c87b0 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<T>(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..0c55484b4 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.
}
@@ -168,12 +165,14 @@ public final class LocaleUtils {
* 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/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
index 201a70d42..bf38abc95 100644
--- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
+++ b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin.utils;
+import com.android.inputmethod.annotations.UsedForTesting;
+
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -47,16 +49,6 @@ public class PrioritizedSerialExecutor {
}
/**
- * 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
*/
@@ -84,6 +76,7 @@ public class PrioritizedSerialExecutor {
* Enqueues the given task into the prioritized task queue.
* @param r the enqueued task
*/
+ @UsedForTesting
public void executePrioritized(final Runnable r) {
synchronized(mLock) {
if (!mIsShutdown) {
@@ -120,32 +113,10 @@ public class PrioritizedSerialExecutor {
}
}
- 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;
+ mThreadPoolExecutor.shutdown();
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index 0f5cd80db..4521ec531 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,28 @@ 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 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(), "");
+ initialize(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS);
deactivate();
}
public void initialize(final int cursorStart, final int cursorEnd, final String string,
- final Locale locale, final String separators) {
+ final Locale locale, final int[] sortedSeparators) {
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;
@@ -131,7 +133,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..49f4929b4 100644
--- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -67,7 +67,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 +87,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 +136,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 +182,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 +189,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 +226,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/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..a059f877b
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/StatsUtils.java
@@ -0,0 +1,53 @@
+/*
+ * 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.preference.PreferenceManager;
+import android.util.Log;
+
+import com.android.inputmethod.latin.settings.Settings;
+
+public final class StatsUtils {
+ private static final String TAG = StatsUtils.class.getSimpleName();
+ private static final StatsUtils sInstance = new StatsUtils();
+
+ public static void onCreateCompleted(final Context context) {
+ sInstance.onCreateCompletedInternal(context);
+ }
+
+ private void onCreateCompletedInternal(final Context context) {
+ mContext = context;
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ final Boolean usePersonalizedDict =
+ prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true);
+ Log.d(TAG, "onCreateCompleted. context: " + context.toString() + "usePersonalizedDict: "
+ + usePersonalizedDict);
+ }
+
+ public static void onDestroy() {
+ sInstance.onDestroyInternal();
+ }
+
+ private void onDestroyInternal() {
+ Log.d(TAG, "onDestroy. context: " + mContext.toString());
+ mContext = null;
+ }
+
+ private Context mContext;
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index a36548392..374badc19 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -16,21 +16,15 @@
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 {
@@ -39,6 +33,8 @@ public final class StringUtils {
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 +46,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,6 +76,20 @@ public final class StringUtils {
return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
}
+ public static String joinCommaSplittableText(final String head, final String tail) {
+ if (TextUtils.isEmpty(head) && TextUtils.isEmpty(tail)) {
+ return EMPTY_STRING;
+ }
+ // Here either head or tail is not null.
+ if (TextUtils.isEmpty(head)) {
+ return tail;
+ }
+ if (TextUtils.isEmpty(tail)) {
+ return head;
+ }
+ return head + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + tail;
+ }
+
public static String appendToCommaSplittableTextIfNotExists(final String text,
final String extraValues) {
if (TextUtils.isEmpty(extraValues)) {
@@ -94,7 +104,7 @@ public final class StringUtils {
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)) {
@@ -162,20 +172,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 +316,58 @@ public final class StringUtils {
return true;
}
- @UsedForTesting
- public static boolean looksValidForDictionaryInsertion(final CharSequence text,
- final SettingsValues settings) {
- if (TextUtils.isEmpty(text)) return false;
+ /**
+ * Returns true if all code points in text are whitespace, false otherwise. Empty is true.
+ */
+ // Interestingly enough, U+00A0 NO-BREAK SPACE and U+200B ZERO-WIDTH SPACE are not considered
+ // whitespace, while EN SPACE, EM SPACE and IDEOGRAPHIC SPACES are.
+ public static boolean containsOnlyWhitespace(final String text) {
final int length = text.length();
int i = 0;
- 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;
+ final int codePoint = text.codePointAt(i);
+ if (!Character.isWhitespace(codePoint)) {
+ return false;
}
- if (!settings.isWordCodePoint(codePoint)) return false;
+ i += Character.charCount(codePoint);
}
- // 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;
+ return true;
}
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 +398,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 +437,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 +490,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 +505,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 +522,59 @@ 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;
+ }
+
+ @UsedForTesting
+ public static class Stringizer<E> {
+ public String stringize(final E element) {
+ return element != null ? element.toString() : "null";
+ }
+
+ @UsedForTesting
+ public final String join(final E[] array) {
+ return joinStringArray(toStringArray(array), null /* delimiter */);
+ }
+
+ @UsedForTesting
+ public final String join(final E[] array, final String delimiter) {
+ return joinStringArray(toStringArray(array), delimiter);
+ }
+
+ protected String[] toStringArray(final E[] array) {
+ final String[] stringArray = new String[array.length];
+ for (int index = 0; index < array.length; index++) {
+ stringArray[index] = stringize(array[index]);
}
+ return stringArray;
}
- return Collections.<Object>emptyList();
- }
- 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();
+ protected String joinStringArray(final String[] stringArray, final String delimiter) {
+ if (stringArray == null) {
+ return "null";
}
- writer.endArray();
- return sw.toString();
- } catch (IOException e) {
- } finally {
- try {
- if (writer != null) {
- writer.close();
- }
- } catch (IOException e) {
+ 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..b37779bdc 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,7 +44,8 @@ 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.
@@ -76,9 +78,16 @@ public final class SubtypeLocaleUtils {
}
// 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 +130,6 @@ public final class SubtypeLocaleUtils {
final String keyboardLayoutSet = keyboardLayoutSetMap[i + 1];
sLocaleAndExtraValueToKeyboardLayoutSetMap.put(key, keyboardLayoutSet);
}
-
- sInitialized = true;
}
public static String[] getPredefinedKeyboardLayoutSet() {
@@ -166,8 +173,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 +195,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 +211,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 +305,23 @@ 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));
}
}
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..0b362c48a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java
@@ -0,0 +1,76 @@
+/*
+ * 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 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;
+ 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;
+ }
+
+ @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..42ea3c959 100644
--- a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
+++ b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
@@ -22,6 +22,8 @@ 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
@@ -37,17 +39,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 +63,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..087a7f255 100644
--- a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -22,6 +22,9 @@ 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.
}
@@ -31,7 +34,7 @@ public final class TypefaceUtils {
// 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);
@@ -51,7 +54,7 @@ public final class TypefaceUtils {
// 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/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;
- }
- }
- }
- }
-}