Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e675665a authored by Keisuke Kuroyanagi's avatar Keisuke Kuroyanagi Committed by Android (Google) Code Review
Browse files

Merge "Show SuggestionsPopup when there is a misspelled span." into nyc-dev

parents 7400f826 f8e0da26
Loading
Loading
Loading
Loading
+112 −74
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@ package android.widget;


import android.R;
import android.R;
import android.annotation.IntDef;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.app.PendingIntent.CanceledException;
@@ -2460,7 +2461,7 @@ public class Editor {
            }
            }
            final SubMenu subMenu = menu.addSubMenu(Menu.NONE, Menu.NONE, MENU_ITEM_ORDER_REPLACE,
            final SubMenu subMenu = menu.addSubMenu(Menu.NONE, Menu.NONE, MENU_ITEM_ORDER_REPLACE,
                    com.android.internal.R.string.replace);
                    com.android.internal.R.string.replace);
            final int numItems = mSuggestionHelper.getSuggestionInfo(suggestionInfoArray);
            final int numItems = mSuggestionHelper.getSuggestionInfo(suggestionInfoArray, null);
            for (int i = 0; i < numItems; i++) {
            for (int i = 0; i < numItems; i++) {
                final SuggestionInfo info = suggestionInfoArray[i];
                final SuggestionInfo info = suggestionInfoArray[i];
                subMenu.add(Menu.NONE, Menu.NONE, i, info.mText)
                subMenu.add(Menu.NONE, Menu.NONE, i, info.mText)
@@ -2516,32 +2517,44 @@ public class Editor {
        mPreserveSelection = true;
        mPreserveSelection = true;
    }
    }


    private void replaceWithSuggestion(final SuggestionInfo suggestionInfo) {
    @Nullable
    private SuggestionSpan findEquivalentSuggestionSpan(
            @NonNull SuggestionSpanInfo suggestionSpanInfo) {
        final Editable editable = (Editable) mTextView.getText();
        final Editable editable = (Editable) mTextView.getText();
        if (editable.getSpanStart(suggestionInfo.mSuggestionSpan) < 0) {
        if (editable.getSpanStart(suggestionSpanInfo.mSuggestionSpan) >= 0) {
            // Suggestion span coundn't be found. Try to find a suggestion span that has the same
            // Exactly same span is found.
            return suggestionSpanInfo.mSuggestionSpan;
        }
        // Suggestion span couldn't be found. Try to find a suggestion span that has the same
        // contents.
        // contents.
            final SuggestionSpan[] suggestionSpans = editable.getSpans(
        final SuggestionSpan[] suggestionSpans = editable.getSpans(suggestionSpanInfo.mSpanStart,
                    suggestionInfo.mSuggestionSpanStart, suggestionInfo.mSuggestionSpanEnd,
                suggestionSpanInfo.mSpanEnd, SuggestionSpan.class);
                    SuggestionSpan.class);
        for (final SuggestionSpan suggestionSpan : suggestionSpans) {
        for (final SuggestionSpan suggestionSpan : suggestionSpans) {
                final int spanStart = editable.getSpanStart(suggestionSpan);
            final int start = editable.getSpanStart(suggestionSpan);
                if (spanStart != suggestionInfo.mSuggestionSpanStart) {
            if (start != suggestionSpanInfo.mSpanStart) {
                continue;
                continue;
            }
            }
                int spanEnd = editable.getSpanEnd(suggestionSpan);
            final int end = editable.getSpanEnd(suggestionSpan);
                if (spanEnd != suggestionInfo.mSuggestionSpanEnd) {
            if (end != suggestionSpanInfo.mSpanEnd) {
                continue;
                continue;
            }
            }
                if (suggestionSpan.equals(suggestionInfo.mSuggestionSpan)) {
            if (suggestionSpan.equals(suggestionSpanInfo.mSuggestionSpan)) {
                    // Found.
                return suggestionSpan;
                    suggestionInfo.mSuggestionSpan = suggestionSpan;
            }
                    break;
        }
        }
        return null;
    }
    }

    private void replaceWithSuggestion(@NonNull final SuggestionInfo suggestionInfo) {
        final SuggestionSpan targetSuggestionSpan = findEquivalentSuggestionSpan(
                suggestionInfo.mSuggestionSpanInfo);
        if (targetSuggestionSpan == null) {
            // Span has been removed
            return;
        }
        }
        final int spanStart = editable.getSpanStart(suggestionInfo.mSuggestionSpan);
        final Editable editable = (Editable) mTextView.getText();
        final int spanEnd = editable.getSpanEnd(suggestionInfo.mSuggestionSpan);
        final int spanStart = editable.getSpanStart(targetSuggestionSpan);
        final int spanEnd = editable.getSpanEnd(targetSuggestionSpan);
        if (spanStart < 0 || spanEnd <= spanStart) {
        if (spanStart < 0 || spanEnd <= spanStart) {
            // Span has been removed
            // Span has been removed
            return;
            return;
@@ -2571,7 +2584,7 @@ public class Editor {
        }
        }


        // Notify source IME of the suggestion pick. Do this before swapping texts.
        // Notify source IME of the suggestion pick. Do this before swapping texts.
        suggestionInfo.mSuggestionSpan.notifySelection(
        targetSuggestionSpan.notifySelection(
                mTextView.getContext(), originalText, suggestionInfo.mSuggestionIndex);
                mTextView.getContext(), originalText, suggestionInfo.mSuggestionIndex);


        // Swap text content between actual text and Suggestion span
        // Swap text content between actual text and Suggestion span
@@ -2581,7 +2594,7 @@ public class Editor {
                suggestionStart, suggestionEnd).toString();
                suggestionStart, suggestionEnd).toString();
        mTextView.replaceText_internal(spanStart, spanEnd, suggestion);
        mTextView.replaceText_internal(spanStart, spanEnd, suggestion);


        String[] suggestions = suggestionInfo.mSuggestionSpan.getSuggestions();
        String[] suggestions = targetSuggestionSpan.getSuggestions();
        suggestions[suggestionInfo.mSuggestionIndex] = originalText;
        suggestions[suggestionInfo.mSuggestionIndex] = originalText;


        // Restore previous SuggestionSpans
        // Restore previous SuggestionSpans
@@ -3029,28 +3042,44 @@ public class Editor {
        }
        }
    }
    }


    private static class SuggestionInfo {
    private static final class SuggestionInfo {
        // Range of actual suggestion within mText
        // Range of actual suggestion within mText
        int mSuggestionStart, mSuggestionEnd;
        int mSuggestionStart, mSuggestionEnd;


        // The SuggestionSpan that this TextView represents
        // The SuggestionSpan that this TextView represents
        final SuggestionSpanInfo mSuggestionSpanInfo = new SuggestionSpanInfo();

        // The index of this suggestion inside suggestionSpan
        int mSuggestionIndex;

        final SpannableStringBuilder mText = new SpannableStringBuilder();

        void clear() {
            mSuggestionSpanInfo.clear();
            mText.clear();
        }

        // Utility method to set attributes about a SuggestionSpan.
        void setSpanInfo(SuggestionSpan span, int spanStart, int spanEnd) {
            mSuggestionSpanInfo.mSuggestionSpan = span;
            mSuggestionSpanInfo.mSpanStart = spanStart;
            mSuggestionSpanInfo.mSpanEnd = spanEnd;
        }
    }

    private static final class SuggestionSpanInfo {
        // The SuggestionSpan;
        @Nullable
        @Nullable
        SuggestionSpan mSuggestionSpan;
        SuggestionSpan mSuggestionSpan;


        // The SuggestionSpan start position
        // The SuggestionSpan start position
        int mSuggestionSpanStart;
        int mSpanStart;


        // The SuggestionSpan end position
        // The SuggestionSpan end position
        int mSuggestionSpanEnd;
        int mSpanEnd;

        // The index of this suggestion inside suggestionSpan
        int mSuggestionIndex;

        final SpannableStringBuilder mText = new SpannableStringBuilder();


        void clear() {
        void clear() {
            mSuggestionSpan = null;
            mSuggestionSpan = null;
            mText.clear();
        }
        }
    }
    }


@@ -3109,48 +3138,51 @@ public class Editor {
         * position.
         * position.
         *
         *
         * @param suggestionInfos SuggestionInfo array the results will be set.
         * @param suggestionInfos SuggestionInfo array the results will be set.
         * @param misspelledSpanInfo a struct the misspelled SuggestionSpan info will be set.
         * @return the number of suggestions actually fetched.
         * @return the number of suggestions actually fetched.
         */
         */
        public int getSuggestionInfo(SuggestionInfo[] suggestionInfos) {
        public int getSuggestionInfo(SuggestionInfo[] suggestionInfos,
                @Nullable SuggestionSpanInfo misspelledSpanInfo) {
            final Spannable spannable = (Spannable) mTextView.getText();
            final Spannable spannable = (Spannable) mTextView.getText();
            final SuggestionSpan[] suggestionSpans = getSortedSuggestionSpans();
            final SuggestionSpan[] suggestionSpans = getSortedSuggestionSpans();
            final int nbSpans = suggestionSpans.length;
            final int nbSpans = suggestionSpans.length;
            if (nbSpans == 0) return 0;
            if (nbSpans == 0) return 0;


            int numberOfSuggestions = 0;
            int numberOfSuggestions = 0;
            for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) {
            for (final SuggestionSpan suggestionSpan : suggestionSpans) {
                final SuggestionSpan suggestionSpan = suggestionSpans[spanIndex];
                final int spanStart = spannable.getSpanStart(suggestionSpan);
                final int spanStart = spannable.getSpanStart(suggestionSpan);
                final int spanEnd = spannable.getSpanEnd(suggestionSpan);
                final int spanEnd = spannable.getSpanEnd(suggestionSpan);


                if (misspelledSpanInfo != null
                        && (suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) {
                    misspelledSpanInfo.mSuggestionSpan = suggestionSpan;
                    misspelledSpanInfo.mSpanStart = spanStart;
                    misspelledSpanInfo.mSpanEnd = spanEnd;
                }

                final String[] suggestions = suggestionSpan.getSuggestions();
                final String[] suggestions = suggestionSpan.getSuggestions();
                final int nbSuggestions = suggestions.length;
                final int nbSuggestions = suggestions.length;
                suggestionLoop:
                for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
                for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) {
                    final String suggestion = suggestions[suggestionIndex];
                    final String suggestion = suggestions[suggestionIndex];
                    boolean suggestionIsDuplicate = false;
                    for (int i = 0; i < numberOfSuggestions; i++) {
                    for (int i = 0; i < numberOfSuggestions; i++) {
                        if (suggestionInfos[i].mText.toString().equals(suggestion)) {
                        final SuggestionInfo otherSuggestionInfo = suggestionInfos[i];
                            final SuggestionSpan otherSuggestionSpan =
                        if (otherSuggestionInfo.mText.toString().equals(suggestion)) {
                                    suggestionInfos[i].mSuggestionSpan;
                            final int otherSpanStart =
                            final int otherSpanStart = spannable.getSpanStart(otherSuggestionSpan);
                                    otherSuggestionInfo.mSuggestionSpanInfo.mSpanStart;
                            final int otherSpanEnd = spannable.getSpanEnd(otherSuggestionSpan);
                            final int otherSpanEnd =
                                    otherSuggestionInfo.mSuggestionSpanInfo.mSpanEnd;
                            if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) {
                            if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) {
                                suggestionIsDuplicate = true;
                                continue suggestionLoop;
                                break;
                            }
                            }
                        }
                        }
                    }
                    }


                    if (suggestionIsDuplicate) {
                        continue;
                    }
                    SuggestionInfo suggestionInfo = suggestionInfos[numberOfSuggestions];
                    SuggestionInfo suggestionInfo = suggestionInfos[numberOfSuggestions];
                    suggestionInfo.mSuggestionSpan = suggestionSpan;
                    suggestionInfo.setSpanInfo(suggestionSpan, spanStart, spanEnd);
                    suggestionInfo.mSuggestionIndex = suggestionIndex;
                    suggestionInfo.mSuggestionIndex = suggestionIndex;
                    suggestionInfo.mSuggestionStart = 0;
                    suggestionInfo.mSuggestionStart = 0;
                    suggestionInfo.mSuggestionEnd = suggestion.length();
                    suggestionInfo.mSuggestionEnd = suggestion.length();
                    suggestionInfo.mSuggestionSpanStart = spanStart;
                    suggestionInfo.mSuggestionSpanEnd = spanEnd;
                    suggestionInfo.mText.replace(0, suggestionInfo.mText.length(), suggestion);
                    suggestionInfo.mText.replace(0, suggestionInfo.mText.length(), suggestion);
                    numberOfSuggestions++;
                    numberOfSuggestions++;
                    if (numberOfSuggestions >= suggestionInfos.length) {
                    if (numberOfSuggestions >= suggestionInfos.length) {
@@ -3180,7 +3212,7 @@ public class Editor {
        private TextView mAddToDictionaryButton;
        private TextView mAddToDictionaryButton;
        private TextView mDeleteButton;
        private TextView mDeleteButton;
        private ListView mSuggestionListView;
        private ListView mSuggestionListView;
        private SuggestionSpan mMisspelledSpan;
        private final SuggestionSpanInfo mMisspelledSpanInfo = new SuggestionSpanInfo();
        private int mContainerMarginWidth;
        private int mContainerMarginWidth;
        private int mContainerMarginTop;
        private int mContainerMarginTop;


@@ -3253,8 +3285,9 @@ public class Editor {
            mAddToDictionaryButton.setOnClickListener(new View.OnClickListener() {
            mAddToDictionaryButton.setOnClickListener(new View.OnClickListener() {
                public void onClick(View v) {
                public void onClick(View v) {
                    final Editable editable = (Editable) mTextView.getText();
                    final Editable editable = (Editable) mTextView.getText();
                    final int spanStart = editable.getSpanStart(mMisspelledSpan);
                    final int spanStart = editable.getSpanStart(
                    final int spanEnd = editable.getSpanEnd(mMisspelledSpan);
                            mMisspelledSpanInfo.mSuggestionSpan);
                    final int spanEnd = editable.getSpanEnd(mMisspelledSpanInfo.mSuggestionSpan);
                    final String originalText = TextUtils.substring(editable, spanStart, spanEnd);
                    final String originalText = TextUtils.substring(editable, spanStart, spanEnd);


                    final Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT);
                    final Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT);
@@ -3265,7 +3298,7 @@ public class Editor {
                    mTextView.getContext().startActivity(intent);
                    mTextView.getContext().startActivity(intent);
                    // There is no way to know if the word was indeed added. Re-check.
                    // There is no way to know if the word was indeed added. Re-check.
                    // TODO The ExtractEditText should remove the span in the original text instead
                    // TODO The ExtractEditText should remove the span in the original text instead
                    editable.removeSpan(mMisspelledSpan);
                    editable.removeSpan(mMisspelledSpanInfo.mSuggestionSpan);
                    Selection.setSelection(editable, spanEnd);
                    Selection.setSelection(editable, spanEnd);
                    updateSpellCheckSpans(spanStart, spanEnd, false);
                    updateSpellCheckSpans(spanStart, spanEnd, false);
                    hideWithCleanUp();
                    hideWithCleanUp();
@@ -3422,30 +3455,29 @@ public class Editor {
            for (final SuggestionInfo info : mSuggestionInfos) {
            for (final SuggestionInfo info : mSuggestionInfos) {
                info.clear();
                info.clear();
            }
            }
            mMisspelledSpan = null;
            mMisspelledSpanInfo.clear();
            hide();
            hide();
        }
        }


        private boolean updateSuggestions() {
        private boolean updateSuggestions() {
            Spannable spannable = (Spannable) mTextView.getText();
            Spannable spannable = (Spannable) mTextView.getText();
            mNumberOfSuggestions =
            mNumberOfSuggestions =
                    mSuggestionHelper.getSuggestionInfo(mSuggestionInfos);
                    mSuggestionHelper.getSuggestionInfo(mSuggestionInfos, mMisspelledSpanInfo);
            if (mNumberOfSuggestions == 0) {
            if (mNumberOfSuggestions == 0 && mMisspelledSpanInfo.mSuggestionSpan == null) {
                return false;
                return false;
            }
            }


            int spanUnionStart = mTextView.getText().length();
            int spanUnionStart = mTextView.getText().length();
            int spanUnionEnd = 0;
            int spanUnionEnd = 0;


            mMisspelledSpan = null;
            for (int i = 0; i < mNumberOfSuggestions; i++) {
            for (int i = 0; i < mNumberOfSuggestions; i++) {
                final SuggestionInfo suggestionInfo = mSuggestionInfos[i];
                final SuggestionSpanInfo spanInfo = mSuggestionInfos[i].mSuggestionSpanInfo;
                final SuggestionSpan suggestionSpan = suggestionInfo.mSuggestionSpan;
                spanUnionStart = Math.min(spanUnionStart, spanInfo.mSpanStart);
                if ((suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) {
                spanUnionEnd = Math.max(spanUnionEnd, spanInfo.mSpanEnd);
                    mMisspelledSpan = suggestionSpan;
            }
            }
                spanUnionStart = Math.min(spanUnionStart, suggestionInfo.mSuggestionSpanStart);
            if (mMisspelledSpanInfo.mSuggestionSpan != null) {
                spanUnionEnd = Math.max(spanUnionEnd, suggestionInfo.mSuggestionSpanEnd);
                spanUnionStart = Math.min(spanUnionStart, mMisspelledSpanInfo.mSpanStart);
                spanUnionEnd = Math.max(spanUnionEnd, mMisspelledSpanInfo.mSpanEnd);
            }
            }


            for (int i = 0; i < mNumberOfSuggestions; i++) {
            for (int i = 0; i < mNumberOfSuggestions; i++) {
@@ -3454,17 +3486,23 @@ public class Editor {


            // Make "Add to dictionary" item visible if there is a span with the misspelled flag
            // Make "Add to dictionary" item visible if there is a span with the misspelled flag
            int addToDictionaryButtonVisibility = View.GONE;
            int addToDictionaryButtonVisibility = View.GONE;
            if (mMisspelledSpan != null) {
            if (mMisspelledSpanInfo.mSuggestionSpan != null) {
                final int misspelledStart = spannable.getSpanStart(mMisspelledSpan);
                if (mMisspelledSpanInfo.mSpanStart >= 0
                final int misspelledEnd = spannable.getSpanEnd(mMisspelledSpan);
                        && mMisspelledSpanInfo.mSpanEnd > mMisspelledSpanInfo.mSpanStart) {
                if (misspelledStart >= 0 && misspelledEnd > misspelledStart) {
                    addToDictionaryButtonVisibility = View.VISIBLE;
                    addToDictionaryButtonVisibility = View.VISIBLE;
                }
                }
            }
            }
            mAddToDictionaryButton.setVisibility(addToDictionaryButtonVisibility);
            mAddToDictionaryButton.setVisibility(addToDictionaryButtonVisibility);


            if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
            if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
            final int underlineColor = mSuggestionInfos[0].mSuggestionSpan.getUnderlineColor();
            final int underlineColor;
            if (mNumberOfSuggestions != 0) {
                underlineColor =
                        mSuggestionInfos[0].mSuggestionSpanInfo.mSuggestionSpan.getUnderlineColor();
            } else {
                underlineColor = mMisspelledSpanInfo.mSuggestionSpan.getUnderlineColor();
            }

            if (underlineColor == 0) {
            if (underlineColor == 0) {
                // Fallback on the default highlight color when the first span does not provide one
                // Fallback on the default highlight color when the first span does not provide one
                mSuggestionRangeSpan.setBackgroundColor(mTextView.mHighlightColor);
                mSuggestionRangeSpan.setBackgroundColor(mTextView.mHighlightColor);
@@ -3484,8 +3522,8 @@ public class Editor {
        private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart,
        private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart,
                int unionEnd) {
                int unionEnd) {
            final Spannable text = (Spannable) mTextView.getText();
            final Spannable text = (Spannable) mTextView.getText();
            final int spanStart = suggestionInfo.mSuggestionSpanStart;
            final int spanStart = suggestionInfo.mSuggestionSpanInfo.mSpanStart;
            final int spanEnd = suggestionInfo.mSuggestionSpanEnd;
            final int spanEnd = suggestionInfo.mSuggestionSpanInfo.mSpanEnd;


            // Adjust the start/end of the suggestion span
            // Adjust the start/end of the suggestion span
            suggestionInfo.mSuggestionStart = spanStart - unionStart;
            suggestionInfo.mSuggestionStart = spanStart - unionStart;
+2 −0
Original line number Original line Diff line number Diff line
@@ -32,6 +32,8 @@ import com.android.frameworks.coretests.R;


/**
/**
 * SuggestionsPopupWindowTest tests.
 * SuggestionsPopupWindowTest tests.
 *
 * TODO: Add tests for when there are no suggestions
 */
 */
public class SuggestionsPopupWindowTest extends ActivityInstrumentationTestCase2<TextViewActivity> {
public class SuggestionsPopupWindowTest extends ActivityInstrumentationTestCase2<TextViewActivity> {