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

Commit 291a0f96 authored by Kohsuke Yatoh's avatar Kohsuke Yatoh
Browse files

Add API to differentiate grammar suggestions.

The following attributes are added for grammar suggestions.
- SuggestionSpan#FLAG_GRAMMAR_ERROR
- SuggestionsInfo#RESULT_ATTR_LOOKS_LIKE_GRAMMAR_ERROR

Bug: 166304720
Test: atest CtsInputMethodTestCases:SpellCheckerTest
Change-Id: I0e5da3125d28bb881e513d0d69e0a2d336a9f286
parent f2c9466e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -50867,6 +50867,7 @@ package android.text.style {
    field @NonNull public static final android.os.Parcelable.Creator<android.text.style.SuggestionSpan> 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";
@@ -58241,6 +58242,7 @@ package android.view.textservice {
    field @NonNull public static final android.os.Parcelable.Creator<android.view.textservice.SuggestionsInfo> 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
  }
+37 −5
Original line number Diff line number Diff line
@@ -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,14 +385,20 @@ 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
                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;
+8 −0
Original line number Diff line number Diff line
@@ -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;
+29 −14
Original line number Diff line number Diff line
@@ -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;
+13 −3
Original line number Diff line number Diff line
@@ -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) {
Loading