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

This commit is contained in:
Keisuke Kuroyanagi
2016-03-15 05:35:20 +00:00
committed by Android (Google) Code Review
2 changed files with 115 additions and 75 deletions

View File

@@ -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.
// contents. return suggestionSpanInfo.mSuggestionSpan;
final SuggestionSpan[] suggestionSpans = editable.getSpans( }
suggestionInfo.mSuggestionSpanStart, suggestionInfo.mSuggestionSpanEnd, // Suggestion span couldn't be found. Try to find a suggestion span that has the same
SuggestionSpan.class); // contents.
for (final SuggestionSpan suggestionSpan : suggestionSpans) { final SuggestionSpan[] suggestionSpans = editable.getSpans(suggestionSpanInfo.mSpanStart,
final int spanStart = editable.getSpanStart(suggestionSpan); suggestionSpanInfo.mSpanEnd, SuggestionSpan.class);
if (spanStart != suggestionInfo.mSuggestionSpanStart) { for (final SuggestionSpan suggestionSpan : suggestionSpans) {
continue; final int start = editable.getSpanStart(suggestionSpan);
} if (start != suggestionSpanInfo.mSpanStart) {
int spanEnd = editable.getSpanEnd(suggestionSpan); continue;
if (spanEnd != suggestionInfo.mSuggestionSpanEnd) { }
continue; final int end = editable.getSpanEnd(suggestionSpan);
} if (end != suggestionSpanInfo.mSpanEnd) {
if (suggestionSpan.equals(suggestionInfo.mSuggestionSpan)) { continue;
// Found. }
suggestionInfo.mSuggestionSpan = suggestionSpan; if (suggestionSpan.equals(suggestionSpanInfo.mSuggestionSpan)) {
break; return suggestionSpan;
}
} }
} }
final int spanStart = editable.getSpanStart(suggestionInfo.mSuggestionSpan); return null;
final int spanEnd = editable.getSpanEnd(suggestionInfo.mSuggestionSpan); }
private void replaceWithSuggestion(@NonNull final SuggestionInfo suggestionInfo) {
final SuggestionSpan targetSuggestionSpan = findEquivalentSuggestionSpan(
suggestionInfo.mSuggestionSpanInfo);
if (targetSuggestionSpan == null) {
// Span has been removed
return;
}
final Editable editable = (Editable) mTextView.getText();
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,19 +3042,12 @@ 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
@Nullable final SuggestionSpanInfo mSuggestionSpanInfo = new SuggestionSpanInfo();
SuggestionSpan mSuggestionSpan;
// The SuggestionSpan start position
int mSuggestionSpanStart;
// The SuggestionSpan end position
int mSuggestionSpanEnd;
// The index of this suggestion inside suggestionSpan // The index of this suggestion inside suggestionSpan
int mSuggestionIndex; int mSuggestionIndex;
@@ -3049,9 +3055,32 @@ public class Editor {
final SpannableStringBuilder mText = new SpannableStringBuilder(); final SpannableStringBuilder mText = new SpannableStringBuilder();
void clear() { void clear() {
mSuggestionSpan = null; mSuggestionSpanInfo.clear();
mText.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
SuggestionSpan mSuggestionSpan;
// The SuggestionSpan start position
int mSpanStart;
// The SuggestionSpan end position
int mSpanEnd;
void clear() {
mSuggestionSpan = null;
}
} }
private class SuggestionHelper { private class SuggestionHelper {
@@ -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; }
} if (mMisspelledSpanInfo.mSuggestionSpan != null) {
spanUnionStart = Math.min(spanUnionStart, suggestionInfo.mSuggestionSpanStart); spanUnionStart = Math.min(spanUnionStart, mMisspelledSpanInfo.mSpanStart);
spanUnionEnd = Math.max(spanUnionEnd, suggestionInfo.mSuggestionSpanEnd); 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;

View File

@@ -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> {