diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/utils')
25 files changed, 362 insertions, 1044 deletions
diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java index db7f2a56c..2aac7c57a 100644 --- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java @@ -16,12 +16,12 @@ package com.android.inputmethod.latin.utils; -import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE; -import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.ASCII_CAPABLE; -import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.EMOJI_CAPABLE; -import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE; -import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; -import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; +import static com.android.inputmethod.latin.common.Constants.Subtype.KEYBOARD_MODE; +import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.ASCII_CAPABLE; +import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.EMOJI_CAPABLE; +import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE; +import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; +import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; import android.os.Build; import android.text.TextUtils; @@ -31,6 +31,7 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.common.StringUtils; import java.util.ArrayList; import java.util.Arrays; diff --git a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java index d12aad639..952ac2a62 100644 --- a/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java +++ b/java/src/com/android/inputmethod/latin/utils/AsyncResultHolder.java @@ -59,11 +59,7 @@ public class AsyncResultHolder<E> { */ public E get(final E defaultValue, final long timeOut) { try { - if (mLatch.await(timeOut, TimeUnit.MILLISECONDS)) { - return mResult; - } else { - return defaultValue; - } + return mLatch.await(timeOut, TimeUnit.MILLISECONDS) ? mResult : defaultValue; } catch (InterruptedException e) { return defaultValue; } diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java index cba769521..120cffbde 100644 --- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java @@ -24,7 +24,6 @@ import com.android.inputmethod.latin.define.DebugFlags; public final class AutoCorrectionUtils { private static final boolean DBG = DebugFlags.DEBUG_ENABLED; private static final String TAG = AutoCorrectionUtils.class.getSimpleName(); - private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4; private AutoCorrectionUtils() { // Purely static class: can't instantiate. diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java index ce25fe6a4..23ffde2a2 100644 --- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin.utils; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.personalization.PersonalizationHelper; diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java index 02f1c5f00..0dbc7c858 100644 --- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java @@ -19,10 +19,12 @@ package com.android.inputmethod.latin.utils; import android.text.InputType; import android.text.TextUtils; -import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.WordComposer; +import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; +import java.util.ArrayList; import java.util.Locale; public final class CapsModeUtils { @@ -325,4 +327,31 @@ public final class CapsModeUtils { // Here we arrived at the start of the line. This should behave exactly like whitespace. return (START == state || LETTER == state) ? noCaps : caps; } + + /** + * Convert capitalize mode flags into human readable text. + * + * @param capsFlags The modes flags to be converted. It may be any combination of + * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and + * {@link TextUtils#CAP_MODE_SENTENCES}. + * @return the text that describe the <code>capsMode</code>. + */ + public static String flagsToString(final int capsFlags) { + final int capsFlagsMask = TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS + | TextUtils.CAP_MODE_SENTENCES; + if ((capsFlags & ~capsFlagsMask) != 0) { + return "unknown<0x" + Integer.toHexString(capsFlags) + ">"; + } + final ArrayList<String> builder = new ArrayList<>(); + if ((capsFlags & android.text.TextUtils.CAP_MODE_CHARACTERS) != 0) { + builder.add("characters"); + } + if ((capsFlags & android.text.TextUtils.CAP_MODE_WORDS) != 0) { + builder.add("words"); + } + if ((capsFlags & android.text.TextUtils.CAP_MODE_SENTENCES) != 0) { + builder.add("sentences"); + } + return builder.isEmpty() ? "none" : TextUtils.join("|", builder); + } } diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java index fb36b7c50..01f5e1079 100644 --- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java @@ -18,21 +18,31 @@ package com.android.inputmethod.latin.utils; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Utility methods for working with collections. + */ public final class CollectionUtils { private CollectionUtils() { // This utility class is not publicly instantiable. } - public static <E> ArrayList<E> arrayAsList(final E[] array, final int start, final int end) { - if (array == null) { - throw new NullPointerException(); - } + /** + * Converts a sub-range of the given array to an ArrayList of the appropriate type. + * @param array Array to be converted. + * @param start First index inclusive to be converted. + * @param end Last index exclusive to be converted. + * @throws IllegalArgumentException if start or end are out of range or start > end. + */ + @Nonnull + public static <E> ArrayList<E> arrayAsList(@Nonnull final E[] array, final int start, + final int end) { if (start < 0 || start > end || end > array.length) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Invalid start: " + start + " end: " + end + + " with array.length: " + array.length); } final ArrayList<E> list = new ArrayList<>(end - start); @@ -47,7 +57,7 @@ public final class CollectionUtils { * @param c Collection to test. * @return Whether c contains no elements. */ - public static boolean isNullOrEmpty(final Collection c) { + public static boolean isNullOrEmpty(@Nullable final Collection<?> c) { return c == null || c.isEmpty(); } } diff --git a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java index 7e8e55990..8699f2ce7 100644 --- a/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CombinedFormatUtils.java @@ -17,6 +17,7 @@ package com.android.inputmethod.latin.utils; import com.android.inputmethod.latin.makedict.DictionaryHeader; +import com.android.inputmethod.latin.makedict.NgramProperty; import com.android.inputmethod.latin.makedict.ProbabilityInfo; import com.android.inputmethod.latin.makedict.WeightedString; import com.android.inputmethod.latin.makedict.WordProperty; @@ -26,6 +27,8 @@ import java.util.HashMap; public class CombinedFormatUtils { public static final String DICTIONARY_TAG = "dictionary"; public static final String BIGRAM_TAG = "bigram"; + public static final String NGRAM_TAG = "ngram"; + public static final String NGRAM_PREV_WORD_TAG = "prev_word"; public static final String SHORTCUT_TAG = "shortcut"; public static final String PROBABILITY_TAG = "f"; public static final String HISTORICAL_INFO_TAG = "historicalInfo"; @@ -33,7 +36,8 @@ public class CombinedFormatUtils { public static final String WORD_TAG = "word"; public static final String BEGINNING_OF_SENTENCE_TAG = "beginning_of_sentence"; public static final String NOT_A_WORD_TAG = "not_a_word"; - public static final String BLACKLISTED_TAG = "blacklisted"; + public static final String POSSIBLY_OFFENSIVE_TAG = "possibly_offensive"; + public static final String TRUE_VALUE = "true"; public static String formatAttributeMap(final HashMap<String, String> attributeMap) { final StringBuilder builder = new StringBuilder(); @@ -58,13 +62,13 @@ public class CombinedFormatUtils { builder.append(","); builder.append(formatProbabilityInfo(wordProperty.mProbabilityInfo)); if (wordProperty.mIsBeginningOfSentence) { - builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=true"); + builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=" + TRUE_VALUE); } if (wordProperty.mIsNotAWord) { - builder.append("," + NOT_A_WORD_TAG + "=true"); + builder.append("," + NOT_A_WORD_TAG + "=" + TRUE_VALUE); } - if (wordProperty.mIsBlacklistEntry) { - builder.append("," + BLACKLISTED_TAG + "=true"); + if (wordProperty.mIsPossiblyOffensive) { + builder.append("," + POSSIBLY_OFFENSIVE_TAG + "=" + TRUE_VALUE); } builder.append("\n"); if (wordProperty.mHasShortcuts) { @@ -76,12 +80,19 @@ public class CombinedFormatUtils { } } if (wordProperty.mHasNgrams) { - // TODO: Support ngram. - for (final WeightedString bigram : wordProperty.getBigrams()) { - builder.append(" " + BIGRAM_TAG + "=" + bigram.mWord); + for (final NgramProperty ngramProperty : wordProperty.mNgrams) { + builder.append(" " + NGRAM_TAG + "=" + ngramProperty.mTargetWord.mWord); builder.append(","); - builder.append(formatProbabilityInfo(bigram.mProbabilityInfo)); + builder.append(formatProbabilityInfo(ngramProperty.mTargetWord.mProbabilityInfo)); builder.append("\n"); + for (int i = 0; i < ngramProperty.mNgramContext.getPrevWordCount(); i++) { + builder.append(" " + NGRAM_PREV_WORD_TAG + "[" + i + "]=" + + ngramProperty.mNgramContext.getNthPrevWord(i + 1)); + if (ngramProperty.mNgramContext.isNthPrevWordBeginningOfSontence(i + 1)) { + builder.append("," + BEGINNING_OF_SENTENCE_TAG + "=true"); + } + builder.append("\n"); + } } } return builder.toString(); @@ -101,4 +112,8 @@ public class CombinedFormatUtils { } return builder.toString(); } + + public static boolean isLiteralTrue(final String value) { + return TRUE_VALUE.equalsIgnoreCase(value); + } } diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java index 87df013a6..3a9705904 100644 --- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java @@ -16,7 +16,7 @@ package com.android.inputmethod.latin.utils; -import java.util.Arrays; +import javax.annotation.Nonnull; public final class CoordinateUtils { private static final int INDEX_X = 0; @@ -27,32 +27,35 @@ public final class CoordinateUtils { // This utility class is not publicly instantiable. } + @Nonnull public static int[] newInstance() { return new int[ELEMENT_SIZE]; } - public static int x(final int[] coords) { + public static int x(@Nonnull final int[] coords) { return coords[INDEX_X]; } - public static int y(final int[] coords) { + public static int y(@Nonnull final int[] coords) { return coords[INDEX_Y]; } - public static void set(final int[] coords, final int x, final int y) { + public static void set(@Nonnull final int[] coords, final int x, final int y) { coords[INDEX_X] = x; coords[INDEX_Y] = y; } - public static void copy(final int[] destination, final int[] source) { + public static void copy(@Nonnull final int[] destination, @Nonnull final int[] source) { destination[INDEX_X] = source[INDEX_X]; destination[INDEX_Y] = source[INDEX_Y]; } + @Nonnull public static int[] newCoordinateArray(final int arraySize) { return new int[ELEMENT_SIZE * arraySize]; } + @Nonnull public static int[] newCoordinateArray(final int arraySize, final int defaultX, final int defaultY) { final int[] result = new int[ELEMENT_SIZE * arraySize]; @@ -62,30 +65,30 @@ public final class CoordinateUtils { return result; } - public static int xFromArray(final int[] coordsArray, final int index) { + public static int xFromArray(@Nonnull final int[] coordsArray, final int index) { return coordsArray[ELEMENT_SIZE * index + INDEX_X]; } - public static int yFromArray(final int[] coordsArray, final int index) { + public static int yFromArray(@Nonnull final int[] coordsArray, final int index) { return coordsArray[ELEMENT_SIZE * index + INDEX_Y]; } - public static int[] coordinateFromArray(final int[] coordsArray, final int index) { - final int baseIndex = ELEMENT_SIZE * index; - return Arrays.copyOfRange(coordsArray, baseIndex, baseIndex + ELEMENT_SIZE); + @Nonnull + public static int[] coordinateFromArray(@Nonnull final int[] coordsArray, final int index) { + final int[] coords = newInstance(); + set(coords, xFromArray(coordsArray, index), yFromArray(coordsArray, index)); + return coords; } - public static void setXYInArray(final int[] coordsArray, final int index, + public static void setXYInArray(@Nonnull final int[] coordsArray, final int index, final int x, final int y) { final int baseIndex = ELEMENT_SIZE * index; coordsArray[baseIndex + INDEX_X] = x; coordsArray[baseIndex + INDEX_Y] = y; } - public static void setCoordinateInArray(final int[] coordsArray, final int index, - final int[] coords) { - final int baseIndex = ELEMENT_SIZE * index; - coordsArray[baseIndex + INDEX_X] = coords[INDEX_X]; - coordsArray[baseIndex + INDEX_Y] = coords[INDEX_Y]; + public static void setCoordinateInArray(@Nonnull final int[] coordsArray, final int index, + @Nonnull final int[] coords) { + setXYInArray(coordsArray, index, x(coords), y(coords)); } } diff --git a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java index e05618901..c90d30c42 100644 --- a/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CursorAnchorInfoUtils.java @@ -24,6 +24,7 @@ import android.inputmethodservice.InputMethodService; import android.os.Build; import android.text.Layout; import android.text.Spannable; +import android.text.Spanned; import android.view.View; import android.view.ViewParent; import android.view.inputmethod.CursorAnchorInfo; @@ -95,7 +96,7 @@ public final class CursorAnchorInfoUtils { @Nullable public static CursorAnchorInfoCompatWrapper extractFromTextView( @Nonnull final TextView textView) { - if (Build.VERSION.SDK_INT < BuildCompatUtils.VERSION_CODES_LXX) { + if (BuildCompatUtils.EFFECTIVE_SDK_INT < Build.VERSION_CODES.LOLLIPOP) { return null; } return CursorAnchorInfoCompatWrapper.wrap(extractFromTextViewInternal(textView)); @@ -107,7 +108,7 @@ public final class CursorAnchorInfoUtils { * @return the {@link CursorAnchorInfo} object based on the current layout. {@code null} if it * is not feasible. */ - @TargetApi(BuildCompatUtils.VERSION_CODES_LXX) + @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Nullable private static CursorAnchorInfo extractFromTextViewInternal(@Nonnull final TextView textView) { final Layout layout = textView.getLayout(); @@ -149,7 +150,7 @@ public final class CursorAnchorInfoUtils { final Object[] spans = spannable.getSpans(0, text.length(), Object.class); for (Object span : spans) { final int spanFlag = spannable.getSpanFlags(span); - if ((spanFlag & Spannable.SPAN_COMPOSING) != 0) { + if ((spanFlag & Spanned.SPAN_COMPOSING) != 0) { composingTextStart = Math.min(composingTextStart, spannable.getSpanStart(span)); composingTextEnd = Math.max(composingTextEnd, spannable.getSpanEnd(span)); diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java index e29aabacd..24025b272 100644 --- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java @@ -26,8 +26,8 @@ import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.AssetFileAddress; import com.android.inputmethod.latin.BinaryDictionaryGetter; -import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.common.Constants; import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java index 355d00dac..525212c96 100644 --- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java @@ -16,14 +16,16 @@ package com.android.inputmethod.latin.utils; -import java.util.List; -import java.util.Locale; - import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.NgramContext; +import java.util.List; +import java.util.Locale; + +import javax.annotation.Nonnull; + public interface DistracterFilter { /** * Determine whether a word is a distracter to words in dictionaries. @@ -68,8 +70,9 @@ public interface DistracterFilter { public static boolean shouldBeHandledAsOov(final int handlingType) { return (handlingType & SHOULD_BE_HANDLED_AS_OOV) != 0; } - }; + } + @Nonnull public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() { @Override public boolean isDistracterToWordsInDictionaries(NgramContext ngramContext, diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java index 8f0f9bb44..9c6a94810 100644 --- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatchesAndSuggestions.java @@ -40,6 +40,7 @@ import com.android.inputmethod.latin.NgramContext; import com.android.inputmethod.latin.RichInputMethodSubtype; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.WordComposer; +import com.android.inputmethod.latin.common.StringUtils; import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; /** @@ -249,8 +250,9 @@ public class DistracterFilterCheckingExactMatchesAndSuggestions implements Distr composer.setComposingWord(codePoints, coordinates); final SuggestionResults suggestionResults; synchronized (mLock) { - suggestionResults = dictionaryFacilitator.getSuggestionResults( - composer, NgramContext.EMPTY_PREV_WORDS_INFO, keyboard.getProximityInfo(), + suggestionResults = dictionaryFacilitator.getSuggestionResults(composer, + NgramContext.EMPTY_PREV_WORDS_INFO, + keyboard.getProximityInfo().getNativeProximityInfo(), settingsValuesForSuggestion, 0 /* sessionId */); } if (suggestionResults.isEmpty()) { diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java index df6e97028..4c99fed9f 100644 --- a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java @@ -41,10 +41,9 @@ public class DistracterFilterCheckingIsInDictionary implements DistracterFilter // This filter treats entries that are already in the dictionary as non-distracters // because they have passed the filtering in the past. return false; - } else { - return mDistracterFilter.isDistracterToWordsInDictionaries( - ngramContext, testedWord, locale); } + return mDistracterFilter.isDistracterToWordsInDictionaries( + ngramContext, testedWord, locale); } @Override diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java index 61da1b789..e77f6fd40 100644 --- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java @@ -27,7 +27,7 @@ import java.util.concurrent.ThreadFactory; * Utilities to manage executors. */ public class ExecutorUtils { - private static final ConcurrentHashMap<String, ExecutorService> sExecutorMap = + static final ConcurrentHashMap<String, ExecutorService> sExecutorMap = new ConcurrentHashMap<>(); private static class ThreadFactoryWithId implements ThreadFactory { @@ -49,7 +49,7 @@ public class ExecutorUtils { public static ExecutorService getExecutor(final String id) { ExecutorService executor = sExecutorMap.get(id); if (executor == null) { - synchronized(sExecutorMap) { + synchronized (sExecutorMap) { executor = sExecutorMap.get(id); if (executor == null) { executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithId(id)); @@ -65,7 +65,7 @@ public class ExecutorUtils { */ @UsedForTesting public static void shutdownAllExecutors() { - synchronized(sExecutorMap) { + synchronized (sExecutorMap) { for (final ExecutorService executor : sExecutorMap.values()) { executor.execute(new Runnable() { @Override diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java deleted file mode 100644 index 73aefb821..000000000 --- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin.utils; - -import android.util.Log; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.DictionaryFacilitator; -import com.android.inputmethod.latin.NgramContext; -import com.android.inputmethod.latin.settings.SpacingAndPunctuations; -import com.android.inputmethod.latin.utils.DistracterFilter.HandlingType; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -// Note: this class is used as a parameter type of a native method. You should be careful when you -// rename this class or field name. See BinaryDictionary#addMultipleDictionaryEntriesNative(). -public final class LanguageModelParam { - private static final String TAG = LanguageModelParam.class.getSimpleName(); - private static final boolean DEBUG = false; - private static final boolean DEBUG_TOKEN = false; - - // For now, these probability values are being referred to only when we add new entries to - // decaying dynamic binary dictionaries. When these are referred to, what matters is 0 or - // non-0. Thus, it's not meaningful to compare 10, 100, and so on. - // TODO: Revise the logic in ForgettingCurveUtils in native code. - private static final int UNIGRAM_PROBABILITY_FOR_VALID_WORD = 100; - private static final int UNIGRAM_PROBABILITY_FOR_OOV_WORD = Dictionary.NOT_A_PROBABILITY; - private static final int BIGRAM_PROBABILITY_FOR_VALID_WORD = 10; - private static final int BIGRAM_PROBABILITY_FOR_OOV_WORD = Dictionary.NOT_A_PROBABILITY; - - public final CharSequence mTargetWord; - public final int[] mWord0; - public final int[] mWord1; - // TODO: this needs to be a list of shortcuts - public final int[] mShortcutTarget; - public final int mUnigramProbability; - public final int mBigramProbability; - public final int mShortcutProbability; - public final boolean mIsNotAWord; - public final boolean mIsBlacklisted; - // Time stamp in seconds. - public final int mTimestamp; - - // Constructor for unigram. TODO: support shortcuts - @UsedForTesting - public LanguageModelParam(final CharSequence word, final int unigramProbability, - final int timestamp) { - this(null /* word0 */, word, unigramProbability, Dictionary.NOT_A_PROBABILITY, timestamp); - } - - // Constructor for unigram and bigram. - @UsedForTesting - public LanguageModelParam(final CharSequence word0, final CharSequence word1, - final int unigramProbability, final int bigramProbability, - final int timestamp) { - mTargetWord = word1; - mWord0 = (word0 == null) ? null : StringUtils.toCodePointArray(word0); - mWord1 = StringUtils.toCodePointArray(word1); - mShortcutTarget = null; - mUnigramProbability = unigramProbability; - mBigramProbability = bigramProbability; - mShortcutProbability = Dictionary.NOT_A_PROBABILITY; - mIsNotAWord = false; - mIsBlacklisted = false; - mTimestamp = timestamp; - } - - // Process a list of words and return a list of {@link LanguageModelParam} objects. - public static ArrayList<LanguageModelParam> createLanguageModelParamsFrom( - final List<String> tokens, final int timestamp, - final SpacingAndPunctuations spacingAndPunctuations, final Locale locale, - final DistracterFilter distracterFilter) { - final ArrayList<LanguageModelParam> languageModelParams = new ArrayList<>(); - final int N = tokens.size(); - NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO; - for (int i = 0; i < N; ++i) { - final String tempWord = tokens.get(i); - if (StringUtils.isEmptyStringOrWhiteSpaces(tempWord)) { - // just skip this token - if (DEBUG_TOKEN) { - Log.d(TAG, "--- isEmptyStringOrWhiteSpaces: \"" + tempWord + "\""); - } - continue; - } - if (!DictionaryInfoUtils.looksValidForDictionaryInsertion( - tempWord, spacingAndPunctuations)) { - if (DEBUG_TOKEN) { - Log.d(TAG, "--- not looksValidForDictionaryInsertion: \"" - + tempWord + "\""); - } - // Sentence terminator found. Split. - ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO; - continue; - } - if (DEBUG_TOKEN) { - Log.d(TAG, "--- word: \"" + tempWord + "\""); - } - final LanguageModelParam languageModelParam = - detectWhetherVaildWordOrNotAndGetLanguageModelParam( - ngramContext, tempWord, timestamp, locale, distracterFilter); - if (languageModelParam == null) { - continue; - } - languageModelParams.add(languageModelParam); - ngramContext = ngramContext.getNextNgramContext( - new NgramContext.WordInfo(tempWord)); - } - return languageModelParams; - } - - private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam( - final NgramContext ngramContext, final String targetWord, final int timestamp, - final Locale locale, final DistracterFilter distracterFilter) { - if (locale == null) { - return null; - } - final int wordHandlingType = distracterFilter.getWordHandlingType(ngramContext, - targetWord, locale); - final String word = HandlingType.shouldBeLowerCased(wordHandlingType) ? - targetWord.toLowerCase(locale) : targetWord; - if (distracterFilter.isDistracterToWordsInDictionaries(ngramContext, targetWord, locale)) { - // The word is a distracter. - return null; - } - return createAndGetLanguageModelParamOfWord(ngramContext, word, timestamp, - !HandlingType.shouldBeHandledAsOov(wordHandlingType)); - } - - private static LanguageModelParam createAndGetLanguageModelParamOfWord( - final NgramContext ngramContext, final String word, final int timestamp, - final boolean isValidWord) { - final int unigramProbability = isValidWord ? - UNIGRAM_PROBABILITY_FOR_VALID_WORD : UNIGRAM_PROBABILITY_FOR_OOV_WORD; - if (!ngramContext.isValid()) { - if (DEBUG) { - Log.d(TAG, "--- add unigram: current(" - + (isValidWord ? "Valid" : "OOV") + ") = " + word); - } - return new LanguageModelParam(word, unigramProbability, timestamp); - } - if (DEBUG) { - Log.d(TAG, "--- add bigram: prev = " + ngramContext + ", current(" - + (isValidWord ? "Valid" : "OOV") + ") = " + word); - } - final int bigramProbability = isValidWord ? - BIGRAM_PROBABILITY_FOR_VALID_WORD : BIGRAM_PROBABILITY_FOR_OOV_WORD; - return new LanguageModelParam(ngramContext.getNthPrevWord(1 /* n */), word, - unigramProbability, bigramProbability, timestamp); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java index 34eeac2c2..7d2ddd268 100644 --- a/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/NgramContextUtils.java @@ -16,14 +16,16 @@ package com.android.inputmethod.latin.utils; -import java.util.Arrays; -import java.util.regex.Pattern; - -import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.NgramContext; import com.android.inputmethod.latin.NgramContext.WordInfo; +import com.android.inputmethod.latin.common.Constants; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; +import java.util.Arrays; +import java.util.regex.Pattern; + +import javax.annotation.Nonnull; + public final class NgramContextUtils { private NgramContextUtils() { // Intentional empty constructor for utility class. @@ -52,6 +54,7 @@ public final class NgramContextUtils { // (n = 2) "abc|" -> beginning-of-sentence // (n = 2) "abc |" -> beginning-of-sentence // (n = 2) "abc. def|" -> beginning-of-sentence + @Nonnull public static NgramContext getNgramContextFromNthPreviousWord(final CharSequence prev, final SpacingAndPunctuations spacingAndPunctuations, final int n) { if (prev == null) return NgramContext.EMPTY_PREV_WORDS_INFO; @@ -74,20 +77,20 @@ public final class NgramContextUtils { } // If we can't find (n + i) words, the context is beginning-of-sentence. if (focusedWordIndex < 0) { - prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE; + prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO; break; } final String focusedWord = w[focusedWordIndex]; // If the word is, the context is beginning-of-sentence. final int length = focusedWord.length(); if (length <= 0) { - prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE; + prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO; break; } // If ends in a sentence separator, the context is beginning-of-sentence. final char lastChar = focusedWord.charAt(length - 1); if (spacingAndPunctuations.isSentenceSeparator(lastChar)) { - prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE; + prevWordsInfo[i] = WordInfo.BEGINNING_OF_SENTENCE_WORD_INFO; break; } // If ends in a word separator or connector, the context is unclear. diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java index e3cac97f0..a381649a4 100644 --- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java +++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin.utils; +import com.android.inputmethod.latin.common.StringUtils; + import java.util.Locale; /** @@ -49,6 +51,17 @@ public class RecapitalizeStatus { } } + public static String modeToString(final int recapitalizeMode) { + switch (recapitalizeMode) { + case NOT_A_RECAPITALIZE_MODE: return "undefined"; + case CAPS_MODE_ORIGINAL_MIXED_CASE: return "mixedCase"; + case CAPS_MODE_ALL_LOWER: return "allLower"; + case CAPS_MODE_FIRST_WORD_UPPER: return "firstWordUpper"; + case CAPS_MODE_ALL_UPPER: return "allUpper"; + default: return "unknown<" + recapitalizeMode + ">"; + } + } + /** * We store the location of the cursor and the string that was there before the recapitalize * action was done, and the location of the cursor and the string that was there after. diff --git a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java deleted file mode 100644 index 64c9e2cff..000000000 --- a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin.utils; - -import java.util.Arrays; - -// TODO: This class is not thread-safe. -public final class ResizableIntArray { - private int[] mArray; - private int mLength; - - public ResizableIntArray(final int capacity) { - reset(capacity); - } - - public int get(final int index) { - if (index < mLength) { - return mArray[index]; - } - throw new ArrayIndexOutOfBoundsException("length=" + mLength + "; index=" + index); - } - - public void addAt(final int index, final int val) { - if (index < mLength) { - mArray[index] = val; - } else { - mLength = index; - add(val); - } - } - - public void add(final int val) { - final int currentLength = mLength; - ensureCapacity(currentLength + 1); - mArray[currentLength] = val; - mLength = currentLength + 1; - } - - /** - * Calculate the new capacity of {@code mArray}. - * @param minimumCapacity the minimum capacity that the {@code mArray} should have. - * @return the new capacity that the {@code mArray} should have. Returns zero when there is no - * need to expand {@code mArray}. - */ - private int calculateCapacity(final int minimumCapacity) { - final int currentCapcity = mArray.length; - if (currentCapcity < minimumCapacity) { - final int nextCapacity = currentCapcity * 2; - // The following is the same as return Math.max(minimumCapacity, nextCapacity); - return minimumCapacity > nextCapacity ? minimumCapacity : nextCapacity; - } - return 0; - } - - private void ensureCapacity(final int minimumCapacity) { - final int newCapacity = calculateCapacity(minimumCapacity); - if (newCapacity > 0) { - // TODO: Implement primitive array pool. - mArray = Arrays.copyOf(mArray, newCapacity); - } - } - - public int getLength() { - return mLength; - } - - public void setLength(final int newLength) { - ensureCapacity(newLength); - mLength = newLength; - } - - public void reset(final int capacity) { - // TODO: Implement primitive array pool. - mArray = new int[capacity]; - mLength = 0; - } - - public int[] getPrimitiveArray() { - return mArray; - } - - public void set(final ResizableIntArray ip) { - // TODO: Implement primitive array pool. - mArray = ip.mArray; - mLength = ip.mLength; - } - - public void copy(final ResizableIntArray ip) { - final int newCapacity = calculateCapacity(ip.mLength); - if (newCapacity > 0) { - // TODO: Implement primitive array pool. - mArray = new int[newCapacity]; - } - System.arraycopy(ip.mArray, 0, mArray, 0, ip.mLength); - mLength = ip.mLength; - } - - public void append(final ResizableIntArray src, final int startPos, final int length) { - if (length == 0) { - return; - } - final int currentLength = mLength; - final int newLength = currentLength + length; - ensureCapacity(newLength); - System.arraycopy(src.mArray, startPos, mArray, currentLength, length); - mLength = newLength; - } - - public void fill(final int value, final int startPos, final int length) { - if (startPos < 0 || length < 0) { - throw new IllegalArgumentException("startPos=" + startPos + "; length=" + length); - } - final int endPos = startPos + length; - ensureCapacity(endPos); - Arrays.fill(mArray, startPos, endPos, value); - if (mLength < endPos) { - mLength = endPos; - } - } - - /** - * Shift to the left by elementCount, discarding elementCount pointers at the start. - * @param elementCount how many elements to shift. - */ - public void shift(final int elementCount) { - System.arraycopy(mArray, elementCount, mArray, 0, mLength - elementCount); - mLength -= elementCount; - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mLength; i++) { - if (i != 0) { - sb.append(","); - } - sb.append(mArray[i]); - } - return "[" + sb + "]"; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java index 093c5a6c1..cc0d470df 100644 --- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java @@ -26,6 +26,7 @@ import android.util.TypedValue; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.settings.SettingsValues; import java.util.ArrayList; import java.util.HashMap; @@ -110,7 +111,6 @@ public final class ResourceUtils { * are true for the specified key value pairs. * * For example, "condition,constant" has the following format. - * (See {@link ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()}) * - HARDWARE=mako,constantForNexus4 * - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4 * - ,defaultConstant @@ -119,6 +119,7 @@ public final class ResourceUtils { * @param conditionConstantArray an array of "condition,constant" elements to be searched. * @return the constant part of the matched "condition,constant" element. Returns null if no * condition matches. + * @see com.android.inputmethod.latin.utils.ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp() */ @UsedForTesting static String findConstantForKeyValuePairs(final HashMap<String, String> keyValuePairs, @@ -186,6 +187,15 @@ public final class ResourceUtils { return dm.widthPixels; } + public static int getKeyboardHeight(final Resources res, final SettingsValues settingsValues) { + final int defaultKeyboardHeight = getDefaultKeyboardHeight(res); + if (settingsValues.mHasKeyboardResize) { + // mKeyboardHeightScale Ranges from [.5,1.2], from xml/prefs_screen_debug.xml + return (int)(defaultKeyboardHeight * settingsValues.mKeyboardHeightScale); + } + return defaultKeyboardHeight; + } + public static int getDefaultKeyboardHeight(final Resources res) { final DisplayMetrics dm = res.getDisplayMetrics(); final String keyboardHeightInDp = getDeviceOverrideValue( diff --git a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java index 38164cb36..c41817fe6 100644 --- a/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/SpannableStringUtils.java @@ -24,6 +24,12 @@ import android.text.TextUtils; import android.text.style.SuggestionSpan; import android.text.style.URLSpan; +import com.android.inputmethod.annotations.UsedForTesting; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public final class SpannableStringUtils { /** * Copies the spans from the region <code>start...end</code> in @@ -51,7 +57,7 @@ public final class SpannableStringUtils { // of a word. But the spans have been split into two by the getText{Before,After}Cursor // methods, so after concatenation they may end in the middle of a word. // Since we don't use them, we can just remove them and avoid crashing. - fl &= ~Spannable.SPAN_PARAGRAPH; + fl &= ~Spanned.SPAN_PARAGRAPH; int st = source.getSpanStart(spans[i]); int en = source.getSpanEnd(spans[i]); @@ -125,4 +131,53 @@ public final class SpannableStringUtils { final URLSpan[] spans = spanned.getSpans(startIndex - 1, endIndex + 1, URLSpan.class); return null != spans && spans.length > 0; } + + /** + * Splits the given {@code charSequence} with at occurrences of the given {@code regex}. + * <p> + * This is equivalent to + * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)} + * except that the spans are preserved in the result array. + * </p> + * @param charSequence the character sequence to be split. + * @param regex the regex pattern to be used as the separator. + * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty + * segments. Otherwise, trailing empty segments will be removed before being returned. + * @return the array which contains the result. All the spans in the <code>charSequence</code> + * is preserved. + */ + @UsedForTesting + public static CharSequence[] split(final CharSequence charSequence, final String regex, + final boolean preserveTrailingEmptySegments) { + // A short-cut for non-spanned strings. + if (!(charSequence instanceof Spanned)) { + // -1 means that trailing empty segments will be preserved. + return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0); + } + + // Hereafter, emulate String.split for CharSequence. + final ArrayList<CharSequence> sequences = new ArrayList<>(); + final Matcher matcher = Pattern.compile(regex).matcher(charSequence); + int nextStart = 0; + boolean matched = false; + while (matcher.find()) { + sequences.add(charSequence.subSequence(nextStart, matcher.start())); + nextStart = matcher.end(); + matched = true; + } + if (!matched) { + // never matched. preserveTrailingEmptySegments is ignored in this case. + return new CharSequence[] { charSequence }; + } + sequences.add(charSequence.subSequence(nextStart, charSequence.length())); + if (!preserveTrailingEmptySegments) { + for (int i = sequences.size() - 1; i >= 0; --i) { + if (!TextUtils.isEmpty(sequences.get(i))) { + break; + } + sequences.remove(i); + } + } + return sequences.toArray(new CharSequence[sequences.size()]); + } } diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java deleted file mode 100644 index bbcef990d..000000000 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ /dev/null @@ -1,631 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin.utils; - -import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; - -import android.text.Spanned; -import android.text.TextUtils; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.Constants; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class StringUtils { - public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case - public static final int CAPITALIZE_FIRST = 1; // First only - public static final int CAPITALIZE_ALL = 2; // All caps - - private static final String EMPTY_STRING = ""; - - private static final char CHAR_LINE_FEED = 0X000A; - private static final char CHAR_VERTICAL_TAB = 0X000B; - private static final char CHAR_FORM_FEED = 0X000C; - private static final char CHAR_CARRIAGE_RETURN = 0X000D; - private static final char CHAR_NEXT_LINE = 0X0085; - private static final char CHAR_LINE_SEPARATOR = 0X2028; - private static final char CHAR_PARAGRAPH_SEPARATOR = 0X2029; - - private StringUtils() { - // This utility class is not publicly instantiable. - } - - public static int codePointCount(final CharSequence text) { - if (TextUtils.isEmpty(text)) return 0; - return Character.codePointCount(text, 0, text.length()); - } - - public static String newSingleCodePointString(int codePoint) { - if (Character.charCount(codePoint) == 1) { - // Optimization: avoid creating a temporary array for characters that are - // represented by a single char value - return String.valueOf((char) codePoint); - } - // For surrogate pair - return new String(Character.toChars(codePoint)); - } - - public static boolean containsInArray(final String text, final String[] array) { - for (final String element : array) { - if (text.equals(element)) return true; - } - return false; - } - - /** - * Comma-Splittable Text is similar to Comma-Separated Values (CSV) but has much simpler syntax. - * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain - * a comma character in it. - */ - private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ","; - - public static boolean containsInCommaSplittableText(final String text, - final String extraValues) { - if (TextUtils.isEmpty(extraValues)) { - return false; - } - return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT)); - } - - public static String removeFromCommaSplittableTextIfExists(final String text, - final String extraValues) { - if (TextUtils.isEmpty(extraValues)) { - return EMPTY_STRING; - } - final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT); - if (!containsInArray(text, elements)) { - return extraValues; - } - final ArrayList<String> result = new ArrayList<>(elements.length - 1); - for (final String element : elements) { - if (!text.equals(element)) { - result.add(element); - } - } - return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result); - } - - /** - * Remove duplicates from an array of strings. - * - * This method will always keep the first occurrence of all strings at their position - * in the array, removing the subsequent ones. - */ - public static void removeDupes(final ArrayList<String> suggestions) { - if (suggestions.size() < 2) return; - int i = 1; - // Don't cache suggestions.size(), since we may be removing items - while (i < suggestions.size()) { - final String cur = suggestions.get(i); - // Compare each suggestion with each previous suggestion - for (int j = 0; j < i; j++) { - final String previous = suggestions.get(j); - if (TextUtils.equals(cur, previous)) { - suggestions.remove(i); - i--; - break; - } - } - i++; - } - } - - public static String capitalizeFirstCodePoint(final String s, final Locale locale) { - if (s.length() <= 1) { - return s.toUpperCase(locale); - } - // Please refer to the comment below in - // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings - final int cutoff = s.offsetByCodePoints(0, 1); - return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff); - } - - public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) { - if (s.length() <= 1) { - return s.toUpperCase(locale); - } - // TODO: fix the bugs below - // - This does not work for Greek, because it returns upper case instead of title case. - // - It does not work for Serbian, because it fails to account for the "lj" character, - // which should be "Lj" in title case and "LJ" in upper case. - // - It does not work for Dutch, because it fails to account for the "ij" digraph when it's - // written as two separate code points. They are two different characters but both should - // be capitalized as "IJ" as if they were a single letter in most words (not all). If the - // unicode char for the ligature is used however, it works. - final int cutoff = s.offsetByCodePoints(0, 1); - return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff).toLowerCase(locale); - } - - private static final int[] EMPTY_CODEPOINTS = {}; - - public static int[] toCodePointArray(final CharSequence charSequence) { - return toCodePointArray(charSequence, 0, charSequence.length()); - } - - /** - * Converts a range of a string to an array of code points. - * @param charSequence the source string. - * @param startIndex the start index inside the string in java chars, inclusive. - * @param endIndex the end index inside the string in java chars, exclusive. - * @return a new array of code points. At most endIndex - startIndex, but possibly less. - */ - public static int[] toCodePointArray(final CharSequence charSequence, - final int startIndex, final int endIndex) { - final int length = charSequence.length(); - if (length <= 0) { - return EMPTY_CODEPOINTS; - } - final int[] codePoints = - new int[Character.codePointCount(charSequence, startIndex, endIndex)]; - copyCodePointsAndReturnCodePointCount(codePoints, charSequence, startIndex, endIndex, - false /* downCase */); - return codePoints; - } - - /** - * Copies the codepoints in a CharSequence to an int array. - * - * This method assumes there is enough space in the array to store the code points. The size - * can be measured with Character#codePointCount(CharSequence, int, int) before passing to this - * method. If the int array is too small, an ArrayIndexOutOfBoundsException will be thrown. - * Also, this method makes no effort to be thread-safe. Do not modify the CharSequence while - * this method is running, or the behavior is undefined. - * This method can optionally downcase code points before copying them, but it pays no attention - * to locale while doing so. - * - * @param destination the int array. - * @param charSequence the CharSequence. - * @param startIndex the start index inside the string in java chars, inclusive. - * @param endIndex the end index inside the string in java chars, exclusive. - * @param downCase if this is true, code points will be downcased before being copied. - * @return the number of copied code points. - */ - public static int copyCodePointsAndReturnCodePointCount(final int[] destination, - final CharSequence charSequence, final int startIndex, final int endIndex, - final boolean downCase) { - int destIndex = 0; - for (int index = startIndex; index < endIndex; - index = Character.offsetByCodePoints(charSequence, index, 1)) { - final int codePoint = Character.codePointAt(charSequence, index); - // TODO: stop using this, as it's not aware of the locale and does not always do - // the right thing. - destination[destIndex] = downCase ? Character.toLowerCase(codePoint) : codePoint; - destIndex++; - } - return destIndex; - } - - public static int[] toSortedCodePointArray(final String string) { - final int[] codePoints = toCodePointArray(string); - Arrays.sort(codePoints); - return codePoints; - } - - /** - * Construct a String from a code point array - * - * @param codePoints a code point array that is null terminated when its logical length is - * shorter than the array length. - * @return a string constructed from the code point array. - */ - public static String getStringFromNullTerminatedCodePointArray(final int[] codePoints) { - int stringLength = codePoints.length; - for (int i = 0; i < codePoints.length; i++) { - if (codePoints[i] == 0) { - stringLength = i; - break; - } - } - return new String(codePoints, 0 /* offset */, stringLength); - } - - // This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE. - public static int getCapitalizationType(final String text) { - // If the first char is not uppercase, then the word is either all lower case or - // camel case, and in either case we return CAPITALIZE_NONE. - final int len = text.length(); - int index = 0; - for (; index < len; index = text.offsetByCodePoints(index, 1)) { - if (Character.isLetter(text.codePointAt(index))) { - break; - } - } - if (index == len) return CAPITALIZE_NONE; - if (!Character.isUpperCase(text.codePointAt(index))) { - return CAPITALIZE_NONE; - } - int capsCount = 1; - int letterCount = 1; - for (index = text.offsetByCodePoints(index, 1); index < len; - index = text.offsetByCodePoints(index, 1)) { - if (1 != capsCount && letterCount != capsCount) break; - final int codePoint = text.codePointAt(index); - if (Character.isUpperCase(codePoint)) { - ++capsCount; - ++letterCount; - } else if (Character.isLetter(codePoint)) { - // We need to discount non-letters since they may not be upper-case, but may - // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME") - ++letterCount; - } - } - // We know the first char is upper case. So we want to test if either every letter other - // than the first is lower case, or if they are all upper case. If the string is exactly - // one char long, then we will arrive here with letterCount 1, and this is correct, too. - if (1 == capsCount) return CAPITALIZE_FIRST; - return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); - } - - public static boolean isIdenticalAfterUpcase(final String text) { - final int length = text.length(); - int i = 0; - while (i < length) { - final int codePoint = text.codePointAt(i); - if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) { - return false; - } - i += Character.charCount(codePoint); - } - return true; - } - - public static boolean isIdenticalAfterDowncase(final String text) { - final int length = text.length(); - int i = 0; - while (i < length) { - final int codePoint = text.codePointAt(i); - if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) { - return false; - } - i += Character.charCount(codePoint); - } - return true; - } - - public static boolean isIdenticalAfterCapitalizeEachWord(final String text, - final int[] sortedSeparators) { - boolean needsCapsNext = true; - final int len = text.length(); - for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { - final int codePoint = text.codePointAt(i); - if (Character.isLetter(codePoint)) { - if ((needsCapsNext && !Character.isUpperCase(codePoint)) - || (!needsCapsNext && !Character.isLowerCase(codePoint))) { - return false; - } - } - // We need a capital letter next if this is a separator. - needsCapsNext = (Arrays.binarySearch(sortedSeparators, codePoint) >= 0); - } - return true; - } - - // TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph - // which should be capitalized together in *some* cases. - public static String capitalizeEachWord(final String text, final int[] sortedSeparators, - final Locale locale) { - final StringBuilder builder = new StringBuilder(); - boolean needsCapsNext = true; - final int len = text.length(); - for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { - final String nextChar = text.substring(i, text.offsetByCodePoints(i, 1)); - if (needsCapsNext) { - builder.append(nextChar.toUpperCase(locale)); - } else { - builder.append(nextChar.toLowerCase(locale)); - } - // We need a capital letter next if this is a separator. - needsCapsNext = (Arrays.binarySearch(sortedSeparators, nextChar.codePointAt(0)) >= 0); - } - return builder.toString(); - } - - /** - * Approximates whether the text before the cursor looks like a URL. - * - * This is not foolproof, but it should work well in the practice. - * Essentially it walks backward from the cursor until it finds something that's not a letter, - * digit, or common URL symbol like underscore. If it hasn't found a period yet, then it - * does not look like a URL. - * If the text: - * - starts with www and contains a period - * - starts with a slash preceded by either a slash, whitespace, or start-of-string - * Then it looks like a URL and we return true. Otherwise, we return false. - * - * Note: this method is called quite often, and should be fast. - * - * TODO: This will return that "abc./def" and ".abc/def" look like URLs to keep down the - * code complexity, but ideally it should not. It's acceptable for now. - */ - public static boolean lastPartLooksLikeURL(final CharSequence text) { - int i = text.length(); - if (0 == i) return false; - int wCount = 0; - int slashCount = 0; - boolean hasSlash = false; - boolean hasPeriod = false; - int codePoint = 0; - while (i > 0) { - codePoint = Character.codePointBefore(text, i); - if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') { - // Handwavy heuristic to see if that's a URL character. Anything between period - // and z. This includes all lower- and upper-case ascii letters, period, - // underscore, arrobase, question mark, equal sign. It excludes spaces, exclamation - // marks, double quotes... - // Anything that's not a URL-like character causes us to break from here and - // evaluate normally. - break; - } - if (Constants.CODE_PERIOD == codePoint) { - hasPeriod = true; - } - if (Constants.CODE_SLASH == codePoint) { - hasSlash = true; - if (2 == ++slashCount) { - return true; - } - } else { - slashCount = 0; - } - if ('w' == codePoint) { - ++wCount; - } else { - wCount = 0; - } - i = Character.offsetByCodePoints(text, i, -1); - } - // End of the text run. - // If it starts with www and includes a period, then it looks like a URL. - if (wCount >= 3 && hasPeriod) return true; - // If it starts with a slash, and the code point before is whitespace, it looks like an URL. - if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) return true; - // If it has both a period and a slash, it looks like an URL. - if (hasPeriod && hasSlash) return true; - // Otherwise, it doesn't look like an URL. - return false; - } - - /** - * Examines the string and returns whether we're inside a double quote. - * - * This is used to decide whether we should put an automatic space before or after a double - * quote character. If we're inside a quotation, then we want to close it, so we want a space - * after and not before. Otherwise, we want to open the quotation, so we want a space before - * and not after. Exception: after a digit, we never want a space because the "inch" or - * "minutes" use cases is dominant after digits. - * In the practice, we determine whether we are in a quotation or not by finding the previous - * double quote character, and looking at whether it's followed by whitespace. If so, that - * was a closing quotation mark, so we're not inside a double quote. If it's not followed - * by whitespace, then it was an opening quotation mark, and we're inside a quotation. - * - * @param text the text to examine. - * @return whether we're inside a double quote. - */ - public static boolean isInsideDoubleQuoteOrAfterDigit(final CharSequence text) { - int i = text.length(); - if (0 == i) return false; - int codePoint = Character.codePointBefore(text, i); - if (Character.isDigit(codePoint)) return true; - int prevCodePoint = 0; - while (i > 0) { - codePoint = Character.codePointBefore(text, i); - if (Constants.CODE_DOUBLE_QUOTE == codePoint) { - // If we see a double quote followed by whitespace, then that - // was a closing quote. - if (Character.isWhitespace(prevCodePoint)) return false; - } - if (Character.isWhitespace(codePoint) && Constants.CODE_DOUBLE_QUOTE == prevCodePoint) { - // If we see a double quote preceded by whitespace, then that - // was an opening quote. No need to continue seeking. - return true; - } - i -= Character.charCount(codePoint); - prevCodePoint = codePoint; - } - // We reached the start of text. If the first char is a double quote, then we're inside - // a double quote. Otherwise we're not. - return Constants.CODE_DOUBLE_QUOTE == codePoint; - } - - public static boolean isEmptyStringOrWhiteSpaces(final String s) { - final int N = codePointCount(s); - for (int i = 0; i < N; ++i) { - if (!Character.isWhitespace(s.codePointAt(i))) { - return false; - } - } - return true; - } - - @UsedForTesting - public static String byteArrayToHexString(final byte[] bytes) { - if (bytes == null || bytes.length == 0) { - return EMPTY_STRING; - } - final StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02x", b & 0xff)); - } - return sb.toString(); - } - - /** - * Convert hex string to byte array. The string length must be an even number. - */ - @UsedForTesting - public static byte[] hexStringToByteArray(final String hexString) { - if (TextUtils.isEmpty(hexString)) { - return null; - } - final int N = hexString.length(); - if (N % 2 != 0) { - throw new NumberFormatException("Input hex string length must be an even number." - + " Length = " + N); - } - final byte[] bytes = new byte[N / 2]; - for (int i = 0; i < N; i += 2) { - bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) - + Character.digit(hexString.charAt(i + 1), 16)); - } - return bytes; - } - - public static String toUpperCaseOfStringForLocale(final String text, - final boolean needsToUpperCase, final Locale locale) { - if (text == null || !needsToUpperCase) return text; - return text.toUpperCase(locale); - } - - public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase, - final Locale locale) { - if (!Constants.isLetterCode(code) || !needsToUpperCase) return code; - final String text = newSingleCodePointString(code); - final String casedText = toUpperCaseOfStringForLocale( - text, needsToUpperCase, locale); - return codePointCount(casedText) == 1 - ? casedText.codePointAt(0) : CODE_UNSPECIFIED; - } - - public static int getTrailingSingleQuotesCount(final CharSequence charSequence) { - final int lastIndex = charSequence.length() - 1; - int i = lastIndex; - while (i >= 0 && charSequence.charAt(i) == Constants.CODE_SINGLE_QUOTE) { - --i; - } - return lastIndex - i; - } - - /** - * Splits the given {@code charSequence} with at occurrences of the given {@code regex}. - * <p> - * This is equivalent to - * {@code charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0)} - * except that the spans are preserved in the result array. - * </p> - * @param input the character sequence to be split. - * @param regex the regex pattern to be used as the separator. - * @param preserveTrailingEmptySegments {@code true} to preserve the trailing empty - * segments. Otherwise, trailing empty segments will be removed before being returned. - * @return the array which contains the result. All the spans in the {@param input} is - * preserved. - */ - @UsedForTesting - public static CharSequence[] split(final CharSequence charSequence, final String regex, - final boolean preserveTrailingEmptySegments) { - // A short-cut for non-spanned strings. - if (!(charSequence instanceof Spanned)) { - // -1 means that trailing empty segments will be preserved. - return charSequence.toString().split(regex, preserveTrailingEmptySegments ? -1 : 0); - } - - // Hereafter, emulate String.split for CharSequence. - final ArrayList<CharSequence> sequences = new ArrayList<>(); - final Matcher matcher = Pattern.compile(regex).matcher(charSequence); - int nextStart = 0; - boolean matched = false; - while (matcher.find()) { - sequences.add(charSequence.subSequence(nextStart, matcher.start())); - nextStart = matcher.end(); - matched = true; - } - if (!matched) { - // never matched. preserveTrailingEmptySegments is ignored in this case. - return new CharSequence[] { charSequence }; - } - sequences.add(charSequence.subSequence(nextStart, charSequence.length())); - if (!preserveTrailingEmptySegments) { - for (int i = sequences.size() - 1; i >= 0; --i) { - if (!TextUtils.isEmpty(sequences.get(i))) { - break; - } - sequences.remove(i); - } - } - return sequences.toArray(new CharSequence[sequences.size()]); - } - - @UsedForTesting - public static class Stringizer<E> { - public String stringize(final E element) { - return element != null ? element.toString() : "null"; - } - - @UsedForTesting - public final String join(final E[] array) { - return joinStringArray(toStringArray(array), null /* delimiter */); - } - - @UsedForTesting - public final String join(final E[] array, final String delimiter) { - return joinStringArray(toStringArray(array), delimiter); - } - - protected String[] toStringArray(final E[] array) { - final String[] stringArray = new String[array.length]; - for (int index = 0; index < array.length; index++) { - stringArray[index] = stringize(array[index]); - } - return stringArray; - } - - protected String joinStringArray(final String[] stringArray, final String delimiter) { - if (stringArray == null) { - return "null"; - } - if (delimiter == null) { - return Arrays.toString(stringArray); - } - final StringBuilder sb = new StringBuilder(); - for (int index = 0; index < stringArray.length; index++) { - sb.append(index == 0 ? "[" : delimiter); - sb.append(stringArray[index]); - } - return sb + "]"; - } - } - - /** - * Returns whether the last composed word contains line-breaking character (e.g. CR or LF). - * @param text the text to be examined. - * @return {@code true} if the last composed word contains line-breaking separator. - */ - @UsedForTesting - public static boolean hasLineBreakCharacter(final String text) { - if (TextUtils.isEmpty(text)) { - return false; - } - for (int i = text.length() - 1; i >= 0; --i) { - final char c = text.charAt(i); - switch (c) { - case CHAR_LINE_FEED: - case CHAR_VERTICAL_TAB: - case CHAR_FORM_FEED: - case CHAR_CARRIAGE_RETURN: - case CHAR_NEXT_LINE: - case CHAR_LINE_SEPARATOR: - case CHAR_PARAGRAPH_SEPARATOR: - return true; - } - } - return false; - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java index 61661cd52..55c1dc9e5 100644 --- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java @@ -16,8 +16,9 @@ package com.android.inputmethod.latin.utils; -import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; -import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; +import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.COMBINING_RULES; +import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; +import static com.android.inputmethod.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; import android.content.Context; import android.content.res.Resources; @@ -25,13 +26,12 @@ import android.os.Build; import android.util.Log; import android.view.inputmethod.InputMethodSubtype; -import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodSubtype; +import com.android.inputmethod.latin.common.StringUtils; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Locale; /** @@ -39,10 +39,10 @@ import java.util.Locale; */ // TODO: consolidate this into RichInputMethodSubtype public final class SubtypeLocaleUtils { - private static final String TAG = SubtypeLocaleUtils.class.getSimpleName(); + static final String TAG = SubtypeLocaleUtils.class.getSimpleName(); - // This reference class {@link Constants} must be located in the same package as LatinIME.java. - private static final String RESOURCE_PACKAGE_NAME = Constants.class.getPackage().getName(); + // This reference class {@link R} must be located in the same package as LatinIME.java. + private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName(); // Special language code to represent "no language". public static final String NO_LANGUAGE = "zz"; @@ -59,7 +59,8 @@ public final class SubtypeLocaleUtils { // Keyboard layout to subtype name resource id map. private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>(); // Exceptional locale whose name should be displayed in Locale.ROOT. - static final HashSet<String> sExceptionalLocaleDisplayedInRootLocale = new HashSet<>(); + private static final HashMap<String, Integer> sExceptionalLocaleDisplayedInRootLocale = + new HashMap<>(); // Exceptional locale to subtype name resource id map. private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>(); // Exceptional locale to subtype name with layout resource id map. @@ -73,6 +74,8 @@ public final class SubtypeLocaleUtils { "string/subtype_with_layout_"; private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX = "string/subtype_no_language_"; + private static final String SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX = + "string/subtype_in_root_locale_"; // Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value. // This is for compatibility to keep the same subtype ids as pre-JellyBean. private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap = @@ -117,7 +120,10 @@ public final class SubtypeLocaleUtils { final String[] exceptionalLocaleInRootLocale = res.getStringArray( R.array.subtype_locale_displayed_in_root_locale); for (int i = 0; i < exceptionalLocaleInRootLocale.length; i++) { - sExceptionalLocaleDisplayedInRootLocale.add(exceptionalLocaleInRootLocale[i]); + final String localeString = exceptionalLocaleInRootLocale[i]; + final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + localeString; + final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); + sExceptionalLocaleDisplayedInRootLocale.put(localeString, resId); } final String[] exceptionalLocales = res.getStringArray( @@ -171,7 +177,7 @@ public final class SubtypeLocaleUtils { if (NO_LANGUAGE.equals(localeString)) { return sResources.getConfiguration().locale; } - if (sExceptionalLocaleDisplayedInRootLocale.contains(localeString)) { + if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { return Locale.ROOT; } return LocaleUtils.constructLocaleFromString(localeString); @@ -190,7 +196,7 @@ public final class SubtypeLocaleUtils { public static String getSubtypeLanguageDisplayName(final String localeString) { final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); final String languageString; - if (sExceptionalLocaleDisplayedInRootLocale.contains(localeString)) { + if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { languageString = localeString; } else { final Locale locale = LocaleUtils.constructLocaleFromString(localeString); @@ -205,7 +211,16 @@ public final class SubtypeLocaleUtils { // No language subtype should be displayed in system locale. return sResources.getString(R.string.subtype_no_language); } - final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString); + final Integer exceptionalNameResId; + if (displayLocale.equals(Locale.ROOT) + && sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) { + exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(localeString); + } else if (sExceptionalLocaleToNameIdsMap.containsKey(localeString)) { + exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString); + } else { + exceptionalNameResId = null; + } + final String displayName; if (exceptionalNameResId != null) { final RunInLocale<String> getExceptionalName = new RunInLocale<String>() { @@ -245,9 +260,8 @@ public final class SubtypeLocaleUtils { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) { return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME); - } else { - return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale); } + return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale); } public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) { @@ -342,6 +356,6 @@ public final class SubtypeLocaleUtils { } public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) { - return subtype.getExtraValueOf(Constants.Subtype.ExtraValue.COMBINING_RULES); + return subtype.getExtraValueOf(COMBINING_RULES); } } diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java index 4e2e396c2..b319aeb8a 100644 --- a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java +++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java @@ -66,8 +66,7 @@ public final class SuggestionResults extends TreeSet<SuggestedWordInfo> { return super.addAll(e); } - private static final class SuggestedWordInfoComparator - implements Comparator<SuggestedWordInfo> { + static final class SuggestedWordInfoComparator implements Comparator<SuggestedWordInfo> { // This comparator ranks the word info with the higher frequency first. That's because // that's the order we want our elements in. @Override diff --git a/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java index dd122b634..0bcba2754 100644 --- a/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java @@ -57,7 +57,7 @@ public final class ViewLayoutUtils { public static void updateLayoutHeightOf(final Window window, final int layoutHeight) { final WindowManager.LayoutParams params = window.getAttributes(); - if (params.height != layoutHeight) { + if (params != null && params.height != layoutHeight) { params.height = layoutHeight; window.setAttributes(params); } @@ -65,7 +65,7 @@ public final class ViewLayoutUtils { public static void updateLayoutHeightOf(final View view, final int layoutHeight) { final ViewGroup.LayoutParams params = view.getLayoutParams(); - if (params.height != layoutHeight) { + if (params != null && params.height != layoutHeight) { params.height = layoutHeight; view.setLayoutParams(params); } diff --git a/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java new file mode 100644 index 000000000..86a5b19ec --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/WordInputEventForPersonalization.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.utils; + +import android.util.Log; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.NgramContext; +import com.android.inputmethod.latin.common.Constants; +import com.android.inputmethod.latin.common.StringUtils; +import com.android.inputmethod.latin.settings.SpacingAndPunctuations; +import com.android.inputmethod.latin.utils.DistracterFilter.HandlingType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +// Note: this class is used as a parameter type of a native method. You should be careful when you +// rename this class or field name. See BinaryDictionary#addMultipleDictionaryEntriesNative(). +public final class WordInputEventForPersonalization { + private static final String TAG = WordInputEventForPersonalization.class.getSimpleName(); + private static final boolean DEBUG_TOKEN = false; + + public final int[] mTargetWord; + public final int mPrevWordsCount; + public final int[][] mPrevWordArray = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; + public final boolean[] mIsPrevWordBeginningOfSentenceArray = + new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + public final boolean mIsValid; + // Time stamp in seconds. + public final int mTimestamp; + + @UsedForTesting + public WordInputEventForPersonalization(final CharSequence targetWord, + final NgramContext ngramContext, final boolean isValid, final int timestamp) { + mTargetWord = StringUtils.toCodePointArray(targetWord); + mPrevWordsCount = ngramContext.getPrevWordCount(); + ngramContext.outputToArray(mPrevWordArray, mIsPrevWordBeginningOfSentenceArray); + mIsValid = isValid; + mTimestamp = timestamp; + } + + // Process a list of words and return a list of {@link WordInputEventForPersonalization} + // objects. + public static ArrayList<WordInputEventForPersonalization> createInputEventFrom( + final List<String> tokens, final int timestamp, + final SpacingAndPunctuations spacingAndPunctuations, final Locale locale, + final DistracterFilter distracterFilter) { + final ArrayList<WordInputEventForPersonalization> inputEvents = new ArrayList<>(); + final int N = tokens.size(); + NgramContext ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO; + for (int i = 0; i < N; ++i) { + final String tempWord = tokens.get(i); + if (StringUtils.isEmptyStringOrWhiteSpaces(tempWord)) { + // just skip this token + if (DEBUG_TOKEN) { + Log.d(TAG, "--- isEmptyStringOrWhiteSpaces: \"" + tempWord + "\""); + } + continue; + } + if (!DictionaryInfoUtils.looksValidForDictionaryInsertion( + tempWord, spacingAndPunctuations)) { + if (DEBUG_TOKEN) { + Log.d(TAG, "--- not looksValidForDictionaryInsertion: \"" + + tempWord + "\""); + } + // Sentence terminator found. Split. + // TODO: Detect whether the context is beginning-of-sentence. + ngramContext = NgramContext.EMPTY_PREV_WORDS_INFO; + continue; + } + if (DEBUG_TOKEN) { + Log.d(TAG, "--- word: \"" + tempWord + "\""); + } + final WordInputEventForPersonalization inputEvent = + detectWhetherVaildWordOrNotAndGetInputEvent( + ngramContext, tempWord, timestamp, locale, distracterFilter); + if (inputEvent == null) { + continue; + } + inputEvents.add(inputEvent); + ngramContext = ngramContext.getNextNgramContext(new NgramContext.WordInfo(tempWord)); + } + return inputEvents; + } + + private static WordInputEventForPersonalization detectWhetherVaildWordOrNotAndGetInputEvent( + final NgramContext ngramContext, final String targetWord, final int timestamp, + final Locale locale, final DistracterFilter distracterFilter) { + if (locale == null) { + return null; + } + final int wordHandlingType = distracterFilter.getWordHandlingType(ngramContext, + targetWord, locale); + final String word = HandlingType.shouldBeLowerCased(wordHandlingType) ? + targetWord.toLowerCase(locale) : targetWord; + if (distracterFilter.isDistracterToWordsInDictionaries(ngramContext, targetWord, locale)) { + // The word is a distracter. + return null; + } + return new WordInputEventForPersonalization(word, ngramContext, + !HandlingType.shouldBeHandledAsOov(wordHandlingType), timestamp); + } +} |