diff --git a/api/current.txt b/api/current.txt index b22e84c2c6c06..240d42573abd7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -50874,6 +50874,7 @@ package android.text.style { field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int FLAG_AUTO_CORRECTION = 4; // 0x4 field public static final int FLAG_EASY_CORRECT = 1; // 0x1 + field public static final int FLAG_GRAMMAR_ERROR = 8; // 0x8 field public static final int FLAG_MISSPELLED = 2; // 0x2 field public static final int SUGGESTIONS_MAX_SIZE = 5; // 0x5 field @Deprecated public static final String SUGGESTION_SPAN_PICKED_AFTER = "after"; @@ -58248,6 +58249,7 @@ package android.view.textservice { field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = 4; // 0x4 field public static final int RESULT_ATTR_IN_THE_DICTIONARY = 1; // 0x1 + field public static final int RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR = 8; // 0x8 field public static final int RESULT_ATTR_LOOKS_LIKE_TYPO = 2; // 0x2 } diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index be01bfb94f213..ac8005b39469e 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -70,6 +70,12 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { */ public static final int FLAG_AUTO_CORRECTION = 0x0004; + /** + * Sets this flag if the suggestions apply to a grammar error. This type of suggestion is + * rendered differently to highlight the error. + */ + public static final int FLAG_GRAMMAR_ERROR = 0x0008; + /** * This action is deprecated in {@link android.os.Build.VERSION_CODES#Q}. * @@ -136,6 +142,9 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { private float mAutoCorrectionUnderlineThickness; private int mAutoCorrectionUnderlineColor; + private float mGrammarErrorUnderlineThickness; + private int mGrammarErrorUnderlineColor; + /** * @param context Context for the application * @param suggestions Suggestions for the string under the span @@ -190,9 +199,11 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { private void initStyle(Context context) { if (context == null) { mMisspelledUnderlineThickness = 0; + mGrammarErrorUnderlineThickness = 0; mEasyCorrectUnderlineThickness = 0; mAutoCorrectionUnderlineThickness = 0; mMisspelledUnderlineColor = Color.BLACK; + mGrammarErrorUnderlineColor = Color.BLACK; mEasyCorrectUnderlineColor = Color.BLACK; mAutoCorrectionUnderlineColor = Color.BLACK; return; @@ -206,6 +217,14 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { mMisspelledUnderlineColor = typedArray.getColor( com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); + defStyleAttr = com.android.internal.R.attr.textAppearanceGrammarErrorSuggestion; + typedArray = context.obtainStyledAttributes( + null, com.android.internal.R.styleable.SuggestionSpan, defStyleAttr, 0); + mGrammarErrorUnderlineThickness = typedArray.getDimension( + com.android.internal.R.styleable.SuggestionSpan_textUnderlineThickness, 0); + mGrammarErrorUnderlineColor = typedArray.getColor( + com.android.internal.R.styleable.SuggestionSpan_textUnderlineColor, Color.BLACK); + defStyleAttr = com.android.internal.R.attr.textAppearanceEasyCorrectSuggestion; typedArray = context.obtainStyledAttributes( null, com.android.internal.R.styleable.SuggestionSpan, defStyleAttr, 0); @@ -235,6 +254,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { mMisspelledUnderlineThickness = src.readFloat(); mAutoCorrectionUnderlineColor = src.readInt(); mAutoCorrectionUnderlineThickness = src.readFloat(); + mGrammarErrorUnderlineColor = src.readInt(); + mGrammarErrorUnderlineThickness = src.readFloat(); } /** @@ -313,6 +334,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { dest.writeFloat(mMisspelledUnderlineThickness); dest.writeInt(mAutoCorrectionUnderlineColor); dest.writeFloat(mAutoCorrectionUnderlineThickness); + dest.writeInt(mGrammarErrorUnderlineColor); + dest.writeFloat(mGrammarErrorUnderlineThickness); } @Override @@ -362,13 +385,19 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0; final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0; final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0; + final boolean grammarError = (mFlags & FLAG_GRAMMAR_ERROR) != 0; if (easy) { - if (!misspelled) { + if (!misspelled && !grammarError) { tp.setUnderlineText(mEasyCorrectUnderlineColor, mEasyCorrectUnderlineThickness); } else if (tp.underlineColor == 0) { // Spans are rendered in an arbitrary order. Since misspelled is less prioritary // than just easy, do not apply misspelled if an easy (or a mispelled) has been set - tp.setUnderlineText(mMisspelledUnderlineColor, mMisspelledUnderlineThickness); + if (grammarError) { + tp.setUnderlineText( + mGrammarErrorUnderlineColor, mGrammarErrorUnderlineThickness); + } else { + tp.setUnderlineText(mMisspelledUnderlineColor, mMisspelledUnderlineThickness); + } } } else if (autoCorrection) { tp.setUnderlineText(mAutoCorrectionUnderlineColor, mAutoCorrectionUnderlineThickness); @@ -384,11 +413,14 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { final boolean misspelled = (mFlags & FLAG_MISSPELLED) != 0; final boolean easy = (mFlags & FLAG_EASY_CORRECT) != 0; final boolean autoCorrection = (mFlags & FLAG_AUTO_CORRECTION) != 0; + final boolean grammarError = (mFlags & FLAG_GRAMMAR_ERROR) != 0; if (easy) { - if (!misspelled) { - return mEasyCorrectUnderlineColor; - } else { + if (grammarError) { + return mGrammarErrorUnderlineColor; + } else if (misspelled) { return mMisspelledUnderlineColor; + } else { + return mEasyCorrectUnderlineColor; } } else if (autoCorrection) { return mAutoCorrectionUnderlineColor; diff --git a/core/java/android/view/textservice/SuggestionsInfo.java b/core/java/android/view/textservice/SuggestionsInfo.java index e69b6e7277a86..ca529f626cfe0 100644 --- a/core/java/android/view/textservice/SuggestionsInfo.java +++ b/core/java/android/view/textservice/SuggestionsInfo.java @@ -45,6 +45,14 @@ public final class SuggestionsInfo implements Parcelable { * the result suggestions include highly recommended ones. */ public static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = 0x0004; + + /** + * Flag of the attributes of the suggestions that can be obtained by + * {@link #getSuggestionsAttributes}: this tells that the text service thinks the requested + * sentence contains a grammar error. + */ + public static final int RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR = 0x0008; + private final int mSuggestionsAttributes; private final String[] mSuggestions; private final boolean mSuggestionsAvailable; diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index fa195211fb544..eaa738d595775 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -186,6 +186,9 @@ public class Editor { private static final int MENU_ITEM_ORDER_SECONDARY_ASSIST_ACTIONS_START = 50; private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100; + private static final int FLAG_MISSPELLED_OR_GRAMMAR_ERROR = + SuggestionSpan.FLAG_MISSPELLED | SuggestionSpan.FLAG_GRAMMAR_ERROR; + @IntDef({MagnifierHandleTrigger.SELECTION_START, MagnifierHandleTrigger.SELECTION_END, MagnifierHandleTrigger.INSERTION}) @@ -1552,7 +1555,7 @@ public class Editor { for (int i = 0; i < suggestionSpans.length; i++) { int flags = suggestionSpans[i].getFlags(); if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 - && (flags & SuggestionSpan.FLAG_MISSPELLED) == 0) { + && (flags & FLAG_MISSPELLED_OR_GRAMMAR_ERROR) == 0) { flags &= ~SuggestionSpan.FLAG_EASY_CORRECT; suggestionSpans[i].setFlags(flags); } @@ -3064,8 +3067,9 @@ public class Editor { // Remove potential misspelled flags int suggestionSpanFlags = suggestionSpan.getFlags(); - if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) != 0) { + if ((suggestionSpanFlags & FLAG_MISSPELLED_OR_GRAMMAR_ERROR) != 0) { suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED; + suggestionSpanFlags &= ~SuggestionSpan.FLAG_GRAMMAR_ERROR; suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT; suggestionSpan.setFlags(suggestionSpanFlags); } @@ -3601,19 +3605,29 @@ public class Editor { final int flag1 = span1.getFlags(); final int flag2 = span2.getFlags(); if (flag1 != flag2) { - // The order here should match what is used in updateDrawState - final boolean easy1 = (flag1 & SuggestionSpan.FLAG_EASY_CORRECT) != 0; - final boolean easy2 = (flag2 & SuggestionSpan.FLAG_EASY_CORRECT) != 0; - final boolean misspelled1 = (flag1 & SuggestionSpan.FLAG_MISSPELLED) != 0; - final boolean misspelled2 = (flag2 & SuggestionSpan.FLAG_MISSPELLED) != 0; - if (easy1 && !misspelled1) return -1; - if (easy2 && !misspelled2) return 1; - if (misspelled1) return -1; - if (misspelled2) return 1; + // Compare so that the order will be: easy -> misspelled -> grammarError + int easy = compareFlag(SuggestionSpan.FLAG_EASY_CORRECT, flag1, flag2); + if (easy != 0) return easy; + int misspelled = compareFlag(SuggestionSpan.FLAG_MISSPELLED, flag1, flag2); + if (misspelled != 0) return misspelled; + int grammarError = compareFlag(SuggestionSpan.FLAG_GRAMMAR_ERROR, flag1, flag2); + if (grammarError != 0) return grammarError; } return mSpansLengths.get(span1).intValue() - mSpansLengths.get(span2).intValue(); } + + /* + * Returns -1 if flags1 has flagToCompare but flags2 does not. + * Returns 1 if flags2 has flagToCompare but flags1 does not. + * Otherwise, returns 0. + */ + private int compareFlag(int flagToCompare, int flags1, int flags2) { + boolean hasFlag1 = (flags1 & flagToCompare) != 0; + boolean hasFlag2 = (flags2 & flagToCompare) != 0; + if (hasFlag1 == hasFlag2) return 0; + return hasFlag1 ? -1 : 1; + } } /** @@ -3632,8 +3646,9 @@ public class Editor { mSpansLengths.put(suggestionSpan, Integer.valueOf(end - start)); } - // The suggestions are sorted according to their types (easy correction first, then - // misspelled) and to the length of the text that they cover (shorter first). + // The suggestions are sorted according to their types (easy correction first, + // misspelled second, then grammar error) and to the length of the text that they cover + // (shorter first). Arrays.sort(suggestionSpans, mSuggestionSpanComparator); mSpansLengths.clear(); @@ -3661,7 +3676,7 @@ public class Editor { final int spanEnd = spannable.getSpanEnd(suggestionSpan); if (misspelledSpanInfo != null - && (suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) { + && (suggestionSpan.getFlags() & FLAG_MISSPELLED_OR_GRAMMAR_ERROR) != 0) { misspelledSpanInfo.mSuggestionSpan = suggestionSpan; misspelledSpanInfo.mSpanStart = spanStart; misspelledSpanInfo.mSpanEnd = spanEnd; diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index d4aad752daa00..0611bea7978de 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -338,11 +338,13 @@ public class SpellChecker implements SpellCheckerSessionListener { ((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0); final boolean looksLikeTypo = ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0); + final boolean looksLikeGrammarError = + ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR) > 0); final SpellCheckSpan spellCheckSpan = mSpellCheckSpans[k]; //TODO: we need to change that rule for results from a sentence-level spell // checker that will probably be in dictionary. - if (!isInDictionary && looksLikeTypo) { + if (!isInDictionary && (looksLikeTypo || looksLikeGrammarError)) { createMisspelledSuggestionSpan( editable, suggestionsInfo, spellCheckSpan, offset, length); } else { @@ -482,8 +484,16 @@ public class SpellChecker implements SpellCheckerSessionListener { suggestions = ArrayUtils.emptyArray(String.class); } - SuggestionSpan suggestionSpan = new SuggestionSpan(mTextView.getContext(), suggestions, - SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED); + final int suggestionsAttrs = suggestionsInfo.getSuggestionsAttributes(); + int flags = SuggestionSpan.FLAG_EASY_CORRECT; + if ((suggestionsAttrs & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) != 0) { + flags |= SuggestionSpan.FLAG_MISSPELLED; + } + if ((suggestionsAttrs & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR) != 0) { + flags |= SuggestionSpan.FLAG_GRAMMAR_ERROR; + } + SuggestionSpan suggestionSpan = + new SuggestionSpan(mTextView.getContext(), suggestions, flags); // TODO: Remove mIsSentenceSpellCheckSupported by extracting an interface // to share the logic of word level spell checker and sentence level spell checker if (mIsSentenceSpellCheckSupported) { diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 644ed3a6ee132..64de32c9ad09e 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -199,6 +199,9 @@ + + + diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index f920083f5cb30..24afe07b57cfd 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -306,6 +306,10 @@ please see styles_device_defaults.xml. @color/holo_blue_light + +