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

Commit 2346e011 authored by Luca Zanolin's avatar Luca Zanolin
Browse files

Fix "out of bound exception" when the span has prefix.

Change the behavior of the highlight marking the "suggested text" and not the differences.

Bug: 5252699
Change-Id: I4c7e9fc9bac81da8b5f643990b86a336363d7968
parent b6738fc6
Loading
Loading
Loading
Loading
+15 −150
Original line number Original line Diff line number Diff line
@@ -9467,10 +9467,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener


    private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener {
    private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener {
        private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE;
        private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE;
        private static final float AVERAGE_HIGHLIGHTS_PER_SUGGESTION = 1.4f;
        private WordIterator mSuggestionWordIterator;
        private TextAppearanceSpan[] mHighlightSpans = new TextAppearanceSpan
                [(int) (AVERAGE_HIGHLIGHTS_PER_SUGGESTION * MAX_NUMBER_SUGGESTIONS)];
        private SuggestionInfo[] mSuggestionInfos;
        private SuggestionInfo[] mSuggestionInfos;
        private int mNumberOfSuggestions;
        private int mNumberOfSuggestions;
        private boolean mCursorWasVisibleBeforeSuggestions;
        private boolean mCursorWasVisibleBeforeSuggestions;
@@ -9497,10 +9493,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        }
        }


        public SuggestionsPopupWindow() {
        public SuggestionsPopupWindow() {
            for (int i = 0; i < mHighlightSpans.length; i++) {
                mHighlightSpans[i] = new TextAppearanceSpan(mContext,
                        android.R.style.TextAppearance_SuggestionHighlight);
            }
            mCursorWasVisibleBeforeSuggestions = mCursorVisible;
            mCursorWasVisibleBeforeSuggestions = mCursorVisible;
        }
        }


@@ -9534,6 +9526,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
            SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
            int suggestionIndex; // the index of the suggestion inside suggestionSpan
            int suggestionIndex; // the index of the suggestion inside suggestionSpan
            SpannableStringBuilder text = new SpannableStringBuilder();
            SpannableStringBuilder text = new SpannableStringBuilder();
            TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
                    android.R.style.TextAppearance_SuggestionHighlight);


            void removeMisspelledFlag() {
            void removeMisspelledFlag() {
                int suggestionSpanFlags = suggestionSpan.getFlags();
                int suggestionSpanFlags = suggestionSpan.getFlags();
@@ -9746,152 +9740,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            return true;
            return true;
        }
        }


        private long[] getWordLimits(CharSequence text) {
        private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart,
            // TODO locale for mSuggestionWordIterator
                int unionEnd) {
            if (mSuggestionWordIterator == null) mSuggestionWordIterator = new WordIterator();
            mSuggestionWordIterator.setCharSequence(text);

            // First pass will simply count the number of words to be able to create an array
            // Not too expensive since previous break positions are cached by the BreakIterator
            int nbWords = 0;
            int position = mSuggestionWordIterator.following(0);
            while (position != BreakIterator.DONE) {
                nbWords++;
                position = mSuggestionWordIterator.following(position);
            }

            int index = 0;
            long[] result = new long[nbWords];

            position = mSuggestionWordIterator.following(0);
            while (position != BreakIterator.DONE) {
                int wordStart = mSuggestionWordIterator.getBeginning(position);
                result[index++] = packRangeInLong(wordStart, position);
                position = mSuggestionWordIterator.following(position);
            }

            return result;
        }

        private TextAppearanceSpan highlightSpan(int index) {
            final int length = mHighlightSpans.length;
            if (index < length) {
                return mHighlightSpans[index];
            }

            // Assumes indexes are requested in sequence: simply append one more item
            TextAppearanceSpan[] newArray = new TextAppearanceSpan[length + 1];
            System.arraycopy(mHighlightSpans, 0, newArray, 0, length);
            TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
                    android.R.style.TextAppearance_SuggestionHighlight);
            newArray[length] = highlightSpan;
            mHighlightSpans = newArray;
            return highlightSpan;
        }

        private void highlightTextDifferences(SuggestionInfo suggestionInfo,
                int unionStart, int unionEnd) {
            final int spanStart = suggestionInfo.spanStart;
            final int spanStart = suggestionInfo.spanStart;
            final int spanEnd = suggestionInfo.spanEnd;
            final int spanEnd = suggestionInfo.spanEnd;


            // Remove all text formating by converting to Strings
            // Adjust the start/end of the suggestion span
            final String text = suggestionInfo.text.toString();
            suggestionInfo.suggestionStart = spanStart - unionStart;
            final String sourceText = mText.subSequence(spanStart, spanEnd).toString();
            suggestionInfo.suggestionEnd = suggestionInfo.suggestionStart 

                    + suggestionInfo.text.length();
            long[] sourceWordLimits = getWordLimits(sourceText);
            long[] wordLimits = getWordLimits(text);

            SpannableStringBuilder ssb = new SpannableStringBuilder();
            // span [spanStart, spanEnd] is included in union [spanUnionStart, int spanUnionEnd]
            // The final result is made of 3 parts: the text before, between and after the span
            // This is the text before, provided for context
            ssb.append(mText.subSequence(unionStart, spanStart).toString());

            // shift is used to offset spans positions wrt span's beginning
            final int shift = spanStart - unionStart;
            suggestionInfo.suggestionStart = shift;
            suggestionInfo.suggestionEnd = shift + text.length();

            // This is the actual suggestion text, which will be highlighted by the following code
            ssb.append(text);

            String[] words = new String[wordLimits.length];
            for (int i = 0; i < wordLimits.length; i++) {
                int wordStart = extractRangeStartFromLong(wordLimits[i]);
                int wordEnd = extractRangeEndFromLong(wordLimits[i]);
                words[i] = text.substring(wordStart, wordEnd);
            }

            // Highlighted word algorithm is based on word matching between source and text
            // Matching words are found from left to right. TODO: change for RTL languages
            // Characters between matching words are highlighted
            int previousCommonWordIndex = -1;
            int nbHighlightSpans = 0;
            for (int i = 0; i < sourceWordLimits.length; i++) {
                int wordStart = extractRangeStartFromLong(sourceWordLimits[i]);
                int wordEnd = extractRangeEndFromLong(sourceWordLimits[i]);
                String sourceWord = sourceText.substring(wordStart, wordEnd);

                for (int j = previousCommonWordIndex + 1; j < words.length; j++) {
                    if (sourceWord.equals(words[j])) {
                        if (j != previousCommonWordIndex + 1) {
                            int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
                                extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
                            int lastDifferentPosition = extractRangeStartFromLong(wordLimits[j]);
                            ssb.setSpan(highlightSpan(nbHighlightSpans++),
                                    shift + firstDifferentPosition, shift + lastDifferentPosition,
                                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                        } else {
                            // Compare characters between words
                            int previousSourceWordEnd = i == 0 ? 0 :
                                extractRangeEndFromLong(sourceWordLimits[i - 1]);
                            int sourceWordStart = extractRangeStartFromLong(sourceWordLimits[i]);
                            String sourceSpaces = sourceText.substring(previousSourceWordEnd,
                                    sourceWordStart);

                            int previousWordEnd = j == 0 ? 0 :
                                extractRangeEndFromLong(wordLimits[j - 1]);
                            int currentWordStart = extractRangeStartFromLong(wordLimits[j]);
                            String textSpaces = text.substring(previousWordEnd, currentWordStart);

                            if (!sourceSpaces.equals(textSpaces)) {
                                ssb.setSpan(highlightSpan(nbHighlightSpans++),
                                        shift + previousWordEnd, shift + currentWordStart,
                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                            }
                        }
                        previousCommonWordIndex = j;
                        break;
                    }
                }
            }

            // Finally, compare ends of Strings
            if (previousCommonWordIndex < words.length - 1) {
                int firstDifferentPosition = previousCommonWordIndex < 0 ? 0 :
                    extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
                int lastDifferentPosition = text.length();
                ssb.setSpan(highlightSpan(nbHighlightSpans++),
                        shift + firstDifferentPosition, shift + lastDifferentPosition,
                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            } else {
                int lastSourceWordEnd = sourceWordLimits.length == 0 ? 0 :
                    extractRangeEndFromLong(sourceWordLimits[sourceWordLimits.length - 1]);
                String sourceSpaces = sourceText.substring(lastSourceWordEnd, sourceText.length());
            
            
                int lastCommonTextWordEnd = previousCommonWordIndex < 0 ? 0 :
            suggestionInfo.text.clearSpans();
                    extractRangeEndFromLong(wordLimits[previousCommonWordIndex]);
            suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0,
                String textSpaces = text.substring(lastCommonTextWordEnd, text.length());
                    suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

                if (!sourceSpaces.equals(textSpaces) && textSpaces.length() > 0) {
                    ssb.setSpan(highlightSpan(nbHighlightSpans++),
                            shift + lastCommonTextWordEnd, shift + text.length(),
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            }


            // Final part, text after the current suggestion range.
            // Add the text before and after the span.
            ssb.append(mText.subSequence(spanEnd, unionEnd).toString());
            suggestionInfo.text.insert(0, mText.subSequence(unionStart, spanStart).toString());
            suggestionInfo.text.append(mText.subSequence(spanEnd, unionEnd).toString());
        }
        }


        @Override
        @Override