Loading core/java/android/widget/Editor.java +112 −74 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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(); } } } } Loading Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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); Loading @@ -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(); Loading Loading @@ -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++) { Loading @@ -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); Loading @@ -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; Loading core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java +2 −0 Original line number Original line Diff line number Diff line Loading @@ -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> { Loading Loading
core/java/android/widget/Editor.java +112 −74 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) Loading Loading @@ -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; Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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(); } } } } Loading Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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); Loading @@ -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(); Loading Loading @@ -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++) { Loading @@ -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); Loading @@ -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; Loading
core/tests/coretests/src/android/widget/SuggestionsPopupWindowTest.java +2 −0 Original line number Original line Diff line number Diff line Loading @@ -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> { Loading