Merge "Update Material icons and key pressed state background"
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 72b5031..90398de 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -155,6 +155,10 @@
         onSharedPreferenceChanged(prefs, PREF_USE_CONTACTS_KEY);
     }
 
+    public float getRecommendedThreshold() {
+        return mRecommendedThreshold;
+    }
+
     private static String getKeyboardLayoutNameForScript(final int script) {
         switch (script) {
         case ScriptUtils.SCRIPT_LATIN:
@@ -214,118 +218,6 @@
                 EMPTY_STRING_ARRAY);
     }
 
-    public SuggestionsGatherer newSuggestionsGatherer(final String text, int maxLength) {
-        return new SuggestionsGatherer(text, mRecommendedThreshold, maxLength);
-    }
-
-    // TODO: remove this class and replace it by storage local to the session.
-    public static final class SuggestionsGatherer {
-        public static final class Result {
-            public final String[] mSuggestions;
-            public final boolean mHasRecommendedSuggestions;
-            public Result(final String[] gatheredSuggestions,
-                    final boolean hasRecommendedSuggestions) {
-                mSuggestions = gatheredSuggestions;
-                mHasRecommendedSuggestions = hasRecommendedSuggestions;
-            }
-        }
-
-        private final ArrayList<String> mSuggestions;
-        private final int[] mScores;
-        private final String mOriginalText;
-        private final float mRecommendedThreshold;
-        private final int mMaxLength;
-        private int mLength = 0;
-
-        SuggestionsGatherer(final String originalText, final float recommendedThreshold,
-                final int maxLength) {
-            mOriginalText = originalText;
-            mRecommendedThreshold = recommendedThreshold;
-            mMaxLength = maxLength;
-            mSuggestions = new ArrayList<>(maxLength + 1);
-            mScores = new int[mMaxLength];
-        }
-
-        synchronized public boolean addWord(char[] word, int[] spaceIndices, int wordOffset,
-                int wordLength, int score) {
-            final int positionIndex = Arrays.binarySearch(mScores, 0, mLength, score);
-            // binarySearch returns the index if the element exists, and -<insertion index> - 1
-            // if it doesn't. See documentation for binarySearch.
-            final int insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
-
-            // Weak <- insertIndex == 0, ..., insertIndex == mLength -> Strong
-            if (insertIndex == 0 && mLength >= mMaxLength) {
-                return true;
-            }
-
-            final String wordString = new String(word, wordOffset, wordLength);
-            if (mLength < mMaxLength) {
-                final int copyLen = mLength - insertIndex;
-                ++mLength;
-                System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen);
-                mSuggestions.add(insertIndex, wordString);
-                mScores[insertIndex] = score;
-            } else {
-                System.arraycopy(mScores, 1, mScores, 0, insertIndex - 1);
-                mSuggestions.add(insertIndex, wordString);
-                mSuggestions.remove(0);
-                mScores[insertIndex - 1] = score;
-            }
-
-            return true;
-        }
-
-        public Result getResults(final int capitalizeType, final Locale locale) {
-            final String[] gatheredSuggestions;
-            final boolean hasRecommendedSuggestions;
-            if (0 == mLength) {
-                gatheredSuggestions = null;
-                hasRecommendedSuggestions = false;
-            } else {
-                if (DBG) {
-                    if (mLength != mSuggestions.size()) {
-                        Log.e(TAG, "Suggestion size is not the same as stored mLength");
-                    }
-                    for (int i = mLength - 1; i >= 0; --i) {
-                        Log.i(TAG, "" + mScores[i] + " " + mSuggestions.get(i));
-                    }
-                }
-                Collections.reverse(mSuggestions);
-                StringUtils.removeDupes(mSuggestions);
-                if (StringUtils.CAPITALIZE_ALL == capitalizeType) {
-                    for (int i = 0; i < mSuggestions.size(); ++i) {
-                        // get(i) returns a CharSequence which is actually a String so .toString()
-                        // should return the same object.
-                        mSuggestions.set(i, mSuggestions.get(i).toString().toUpperCase(locale));
-                    }
-                } else if (StringUtils.CAPITALIZE_FIRST == capitalizeType) {
-                    for (int i = 0; i < mSuggestions.size(); ++i) {
-                        // Likewise
-                        mSuggestions.set(i, StringUtils.capitalizeFirstCodePoint(
-                                mSuggestions.get(i).toString(), locale));
-                    }
-                }
-                // This returns a String[], while toArray() returns an Object[] which cannot be cast
-                // into a String[].
-                gatheredSuggestions = mSuggestions.toArray(EMPTY_STRING_ARRAY);
-
-                final int bestScore = mScores[mLength - 1];
-                final String bestSuggestion = mSuggestions.get(0);
-                final float normalizedScore =
-                        BinaryDictionaryUtils.calcNormalizedScore(
-                                mOriginalText, bestSuggestion.toString(), bestScore);
-                hasRecommendedSuggestions = (normalizedScore > mRecommendedThreshold);
-                if (DBG) {
-                    Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
-                    Log.i(TAG, "Normalized score = " + normalizedScore
-                            + " (threshold " + mRecommendedThreshold
-                            + ") => hasRecommendedSuggestions = " + hasRecommendedSuggestions);
-                }
-            }
-            return new Result(gatheredSuggestions, hasRecommendedSuggestions);
-        }
-    }
-
     public boolean isValidWord(final Locale locale, final String word) {
         mSemaphore.acquireUninterruptibly();
         try {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 6bfd354..38d7206 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -32,7 +32,6 @@
 public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession {
     private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName();
     private static final boolean DBG = false;
-    private final static String[] EMPTY_STRING_ARRAY = new String[0];
     private final Resources mResources;
     private SentenceLevelAdapter mSentenceLevelAdapter;
 
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 19c1dd0..d668672 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -34,20 +34,22 @@
 import com.android.inputmethod.latin.PrevWordsInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
-import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion;
-import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer;
+import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.LocaleUtils;
 import com.android.inputmethod.latin.utils.ScriptUtils;
 import com.android.inputmethod.latin.utils.StringUtils;
 import com.android.inputmethod.latin.utils.SuggestionResults;
 
+import java.util.ArrayList;
 import java.util.Locale;
 
 public abstract class AndroidWordLevelSpellCheckerSession extends Session {
     private static final String TAG = AndroidWordLevelSpellCheckerSession.class.getSimpleName();
     private static final boolean DBG = false;
 
+    public final static String[] EMPTY_STRING_ARRAY = new String[0];
+
     // Immutable, but not available in the constructor.
     private Locale mLocale;
     // Cache this for performance
@@ -259,14 +261,6 @@
             }
             final String text = inText.replaceAll(
                     AndroidSpellCheckerService.APOSTROPHE, AndroidSpellCheckerService.SINGLE_QUOTE);
-
-            // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
-            //final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(text,
-            //mService.mSuggestionThreshold, mService.mRecommendedThreshold,
-            //suggestionsLimit);
-            final SuggestionsGatherer suggestionsGatherer = mService.newSuggestionsGatherer(
-                    text, suggestionsLimit);
-
             final int capitalizeType = StringUtils.getCapitalizationType(text);
             boolean isInDict = true;
             if (!mService.hasMainDictionaryForLocale(mLocale)) {
@@ -287,20 +281,12 @@
                 proximityInfo = keyboard.getProximityInfo();
             }
             composer.setComposingWord(codePoints, coordinates);
+            // TODO: Don't gather suggestions if the limit is <= 0 unless necessary
             final SuggestionResults suggestionResults = mService.getSuggestionResults(
                     mLocale, composer, prevWordsInfo, proximityInfo);
-            if (suggestionResults != null) {
-                for (final SuggestedWordInfo suggestion : suggestionResults) {
-                    final String suggestionStr = suggestion.mWord;
-                    suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0,
-                            suggestionStr.length(), suggestion.mScore);
-                }
-            }
+            final Result result = getResult(capitalizeType, mLocale, suggestionsLimit,
+                    mService.getRecommendedThreshold(), text, suggestionResults);
             isInDict = isInDictForAnyCapitalization(text, capitalizeType);
-
-            final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(
-                    capitalizeType, mLocale);
-
             if (DBG) {
                 Log.i(TAG, "Spell checking results for " + text + " with suggestion limit "
                         + suggestionsLimit);
@@ -337,6 +323,62 @@
         }
     }
 
+    private static final class Result {
+        public final String[] mSuggestions;
+        public final boolean mHasRecommendedSuggestions;
+        public Result(final String[] gatheredSuggestions,
+                final boolean hasRecommendedSuggestions) {
+            mSuggestions = gatheredSuggestions;
+            mHasRecommendedSuggestions = hasRecommendedSuggestions;
+        }
+    }
+
+    private static Result getResult(final int capitalizeType, final Locale locale,
+            final int suggestionsLimit, final float recommendedThreshold, final String originalText,
+            final SuggestionResults suggestionResults) {
+        if (suggestionResults.isEmpty() || suggestionsLimit <= 0) {
+            return new Result(null /* gatheredSuggestions */,
+                    false /* hasRecommendedSuggestions */);
+        }
+        if (DBG) {
+            for (final SuggestedWordInfo suggestedWordInfo : suggestionResults) {
+                Log.i(TAG, "" + suggestedWordInfo.mScore + " " + suggestedWordInfo.mWord);
+            }
+        }
+        final ArrayList<String> suggestions = new ArrayList<>();
+        for (final SuggestedWordInfo suggestedWordInfo : suggestionResults) {
+            final String suggestion;
+            if (StringUtils.CAPITALIZE_ALL == capitalizeType) {
+                suggestion = suggestedWordInfo.mWord.toUpperCase(locale);
+            } else if (StringUtils.CAPITALIZE_FIRST == capitalizeType) {
+                suggestion = StringUtils.capitalizeFirstCodePoint(
+                        suggestedWordInfo.mWord, locale);
+            } else {
+                suggestion = suggestedWordInfo.mWord;
+            }
+            suggestions.add(suggestion);
+        }
+        StringUtils.removeDupes(suggestions);
+        // This returns a String[], while toArray() returns an Object[] which cannot be cast
+        // into a String[].
+        final String[] gatheredSuggestions =
+                suggestions.subList(0, Math.min(suggestions.size(), suggestionsLimit))
+                        .toArray(EMPTY_STRING_ARRAY);
+
+        final int bestScore = suggestionResults.first().mScore;
+        final String bestSuggestion = suggestions.get(0);
+        final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
+                originalText, bestSuggestion.toString(), bestScore);
+        final boolean hasRecommendedSuggestions = (normalizedScore > recommendedThreshold);
+        if (DBG) {
+            Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore);
+            Log.i(TAG, "Normalized score = " + normalizedScore
+                    + " (threshold " + recommendedThreshold
+                    + ") => hasRecommendedSuggestions = " + hasRecommendedSuggestions);
+        }
+        return new Result(gatheredSuggestions, hasRecommendedSuggestions);
+    }
+
     /*
      * The spell checker acts on its own behalf. That is needed, in particular, to be able to
      * access the dictionary files, which the provider restricts to the identity of Latin IME.