Modified algorithm to remove duplicated suggestions

Bug 5916225

Duplicates were removed when received from SpellChecker, in a way
that could move the top candidates lower in the list.

Moved that code to the part that creates the actual suggestion list,
to make it more generic. The order of the first SuggestionSpan is
guaranteed to be respected.

Also mentionned non null suggestions and fixed a problem in SuggestionSpan
constructor.

Change-Id: Iaa3b1b84ae512451e439e5c5e63448c2a19145b5
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 0f26a34..5dc206f 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -25,6 +25,7 @@
 import android.text.ParcelableSpan;
 import android.text.TextPaint;
 import android.text.TextUtils;
+import android.util.Log;
 import android.widget.TextView;
 
 import java.util.Arrays;
@@ -114,7 +115,7 @@
      * @param context Context for the application
      * @param locale locale Locale of the suggestions
      * @param suggestions Suggestions for the string under the span. Only the first up to
-     * {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered.
+     * {@link SuggestionSpan#SUGGESTIONS_MAX_SIZE} will be considered. Null values not permitted.
      * @param flags Additional flags indicating how this span is handled in TextView
      * @param notificationTargetClass if not null, this class will get notified when the user
      * selects one of the suggestions.
@@ -124,10 +125,13 @@
         final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length);
         mSuggestions = Arrays.copyOf(suggestions, N);
         mFlags = flags;
-        if (context != null && locale == null) {
+        if (locale != null) {
+            mLocaleString = locale.toString();
+        } else if (context != null) {
             mLocaleString = context.getResources().getConfiguration().locale.toString();
         } else {
-            mLocaleString = locale.toString();
+            Log.e("SuggestionSpan", "No locale or context specified in SuggestionSpan constructor");
+            mLocaleString = "";
         }
 
         if (notificationTargetClass != null) {
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index a9aec82..570f0f9 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -342,56 +342,15 @@
         final int end = editable.getSpanEnd(spellCheckSpan);
         if (start < 0 || end <= start) return; // span was removed in the meantime
 
-        // Other suggestion spans may exist on that region, with identical suggestions, filter
-        // them out to avoid duplicates.
-        SuggestionSpan[] suggestionSpans = editable.getSpans(start, end, SuggestionSpan.class);
-        final int length = suggestionSpans.length;
-        for (int i = 0; i < length; i++) {
-            final int spanStart = editable.getSpanStart(suggestionSpans[i]);
-            final int spanEnd = editable.getSpanEnd(suggestionSpans[i]);
-            if (spanStart != start || spanEnd != end) {
-                // Nulled (to avoid new array allocation) if not on that exact same region
-                suggestionSpans[i] = null;
-            }
-        }
-
         final int suggestionsCount = suggestionsInfo.getSuggestionsCount();
-        String[] suggestions;
         if (suggestionsCount <= 0) {
             // A negative suggestion count is possible
-            suggestions = ArrayUtils.emptyArray(String.class);
-        } else {
-            int numberOfSuggestions = 0;
-            suggestions = new String[suggestionsCount];
+            return;
+        }
 
-            for (int i = 0; i < suggestionsCount; i++) {
-                final String spellSuggestion = suggestionsInfo.getSuggestionAt(i);
-                if (spellSuggestion == null) break;
-                boolean suggestionFound = false;
-
-                for (int j = 0; j < length && !suggestionFound; j++) {
-                    if (suggestionSpans[j] == null) break;
-
-                    String[] suggests = suggestionSpans[j].getSuggestions();
-                    for (int k = 0; k < suggests.length; k++) {
-                        if (spellSuggestion.equals(suggests[k])) {
-                            // The suggestion is already provided by an other SuggestionSpan
-                            suggestionFound = true;
-                            break;
-                        }
-                    }
-                }
-
-                if (!suggestionFound) {
-                    suggestions[numberOfSuggestions++] = spellSuggestion;
-                }
-            }
-
-            if (numberOfSuggestions != suggestionsCount) {
-                String[] newSuggestions = new String[numberOfSuggestions];
-                System.arraycopy(suggestions, 0, newSuggestions, 0, numberOfSuggestions);
-                suggestions = newSuggestions;
-            }
+        String[] suggestions = new String[suggestionsCount];
+        for (int i = 0; i < suggestionsCount; i++) {
+            suggestions[i] = suggestionsInfo.getSuggestionAt(i);
         }
 
         SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions,
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9fb26ae..4caed56 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9838,17 +9838,34 @@
                 String[] suggestions = suggestionSpan.getSuggestions();
                 int nbSuggestions = suggestions.length;
                 for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
-                    SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
-                    suggestionInfo.suggestionSpan = suggestionSpan;
-                    suggestionInfo.suggestionIndex = suggestionIndex;
-                    suggestionInfo.text.replace(0, suggestionInfo.text.length(),
-                            suggestions[suggestionIndex]);
+                    String suggestion = suggestions[suggestionIndex];
 
-                    mNumberOfSuggestions++;
-                    if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) {
-                        // Also end outer for loop
-                        spanIndex = nbSpans;
-                        break;
+                    boolean suggestionIsDuplicate = false;
+                    for (int i = 0; i < mNumberOfSuggestions; i++) {
+                        if (mSuggestionInfos[i].text.toString().equals(suggestion)) {
+                            SuggestionSpan otherSuggestionSpan = mSuggestionInfos[i].suggestionSpan;
+                            final int otherSpanStart = spannable.getSpanStart(otherSuggestionSpan);
+                            final int otherSpanEnd = spannable.getSpanEnd(otherSuggestionSpan);
+                            if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) {
+                                suggestionIsDuplicate = true;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (!suggestionIsDuplicate) {
+                        SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions];
+                        suggestionInfo.suggestionSpan = suggestionSpan;
+                        suggestionInfo.suggestionIndex = suggestionIndex;
+                        suggestionInfo.text.replace(0, suggestionInfo.text.length(), suggestion);
+
+                        mNumberOfSuggestions++;
+
+                        if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) {
+                            // Also end outer for loop
+                            spanIndex = nbSpans;
+                            break;
+                        }
                     }
                 }
             }
@@ -9857,7 +9874,7 @@
                 highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd);
             }
 
-            // Add to dictionary item if there is a span with the misspelled flag
+            // Add "Add to dictionary" item if there is a span with the misspelled flag
             if (misspelledSpan != null) {
                 final int misspelledStart = spannable.getSpanStart(misspelledSpan);
                 final int misspelledEnd = spannable.getSpanEnd(misspelledSpan);
@@ -9915,8 +9932,9 @@
                     suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
 
             // Add the text before and after the span.
-            suggestionInfo.text.insert(0, mText.toString().substring(unionStart, spanStart));
-            suggestionInfo.text.append(mText.toString().substring(spanEnd, unionEnd));
+            final String textAsString = text.toString();
+            suggestionInfo.text.insert(0, textAsString.substring(unionStart, spanStart));
+            suggestionInfo.text.append(textAsString.substring(spanEnd, unionEnd));
         }
 
         @Override