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

Commit 8898358b authored by satok's avatar satok
Browse files

Handle sentence level spell checking hidden APIs

Change-Id:  Ia91f1771c0b8ef458dd5b023f4c372cc36a15657
parent 1b3d01de
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -81,6 +81,13 @@ public final class SentenceSuggestionsInfo implements Parcelable {
        return 0;
    }

    /**
     * @hide
     */
    public int getSuggestionsCount() {
        return mSuggestionsInfos.length;
    }

    /**
     * @hide
     */
+2 −2
Original line number Diff line number Diff line
@@ -180,9 +180,9 @@ public class SpellCheckerSession {
    /**
     * @hide
     */
    public void getSentenceSuggestions(TextInfo textInfo, int suggestionsLimit) {
    public void getSentenceSuggestions(TextInfo[] textInfo, int suggestionsLimit) {
        mSpellCheckerSessionListenerImpl.getSentenceSuggestionsMultiple(
                new TextInfo[] {textInfo}, suggestionsLimit);
                textInfo, suggestionsLimit);
    }

    /**
+165 −84
Original line number Diff line number Diff line
@@ -57,9 +57,14 @@ public class SpellChecker implements SpellCheckerSessionListener {
    // Pause between each spell check to keep the UI smooth
    private final static int SPELL_PAUSE_DURATION = 400; // milliseconds

    private static final int USE_SPAN_RANGE = -1;

    private final TextView mTextView;

    SpellCheckerSession mSpellCheckerSession;
    // We assume that the sentence level spell check will always provide better results than words.
    // Although word SC has a sequential option.
    private boolean mIsSentenceSpellCheckSupported;
    final int mCookie;

    // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
@@ -111,6 +116,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
                    null /* Bundle not currently used by the textServicesManager */,
                    mCurrentLocale, this,
                    false /* means any available languages from current spell checker */);
            mIsSentenceSpellCheckSupported = mSpellCheckerSession.isSentenceSpellCheckSupported();
        }

        // Restore SpellCheckSpans in pool
@@ -272,46 +278,80 @@ public class SpellChecker implements SpellCheckerSessionListener {
                textInfos = textInfosCopy;
            }

            if (mIsSentenceSpellCheckSupported) {
                mSpellCheckerSession.getSentenceSuggestions(
                        textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE);
            } else {
                mSpellCheckerSession.getSuggestions(textInfos, SuggestionSpan.SUGGESTIONS_MAX_SIZE,
                        false /* TODO Set sequentialWords to true for initial spell check */);
            }
        }

    @Override
    public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
        // TODO: Handle the position and length for each suggestion
        // do nothing for now
    }

    @Override
    public void onGetSuggestions(SuggestionsInfo[] results) {
        Editable editable = (Editable) mTextView.getText();

        for (int i = 0; i < results.length; i++) {
            SuggestionsInfo suggestionsInfo = results[i];
            if (suggestionsInfo.getCookie() != mCookie) continue;
    private SpellCheckSpan onGetSuggestionsInternal(
            SuggestionsInfo suggestionsInfo, int offset, int length) {
        if (suggestionsInfo.getCookie() != mCookie) {
            return null;
        }
        final Editable editable = (Editable) mTextView.getText();
        final int sequenceNumber = suggestionsInfo.getSequence();

            for (int j = 0; j < mLength; j++) {
                if (sequenceNumber == mIds[j]) {
        for (int k = 0; k < mLength; ++k) {
            if (sequenceNumber == mIds[k]) {
                final int attributes = suggestionsInfo.getSuggestionsAttributes();
                    boolean isInDictionary =
                final boolean isInDictionary =
                        ((attributes & SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY) > 0);
                    boolean looksLikeTypo =
                final boolean looksLikeTypo =
                        ((attributes & SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO) > 0);

                    SpellCheckSpan spellCheckSpan = mSpellCheckSpans[j];

                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) {
                        createMisspelledSuggestionSpan(editable, suggestionsInfo, spellCheckSpan);
                    createMisspelledSuggestionSpan(
                            editable, suggestionsInfo, spellCheckSpan, offset, length);
                }
                return spellCheckSpan;
            }
        }
        return null;
    }

    @Override
    public void onGetSuggestions(SuggestionsInfo[] results) {
        final Editable editable = (Editable) mTextView.getText();
        for (int i = 0; i < results.length; ++i) {
            final SpellCheckSpan spellCheckSpan =
                    onGetSuggestionsInternal(results[i], USE_SPAN_RANGE, USE_SPAN_RANGE);
            if (spellCheckSpan != null) {
                editable.removeSpan(spellCheckSpan);
                    break;
            }
        }
        scheduleNewSpellCheck();
    }

    @Override
    public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
        final Editable editable = (Editable) mTextView.getText();

        for (int i = 0; i < results.length; ++i) {
            final SentenceSuggestionsInfo ssi = results[i];
            SpellCheckSpan spellCheckSpan = null;
            for (int j = 0; j < ssi.getSuggestionsCount(); ++j) {
                final SuggestionsInfo suggestionsInfo = ssi.getSuggestionsInfoAt(j);
                final int offset = ssi.getOffsetAt(j);
                final int length = ssi.getLengthAt(j);
                final SpellCheckSpan scs = onGetSuggestionsInternal(
                        suggestionsInfo, offset, length);
                if (spellCheckSpan == null && scs != null) {
                    // the spellCheckSpan is shared by all the "SuggestionsInfo"s in the same
                    // SentenceSuggestionsInfo
                    spellCheckSpan = scs;
                }
            }
            if (spellCheckSpan != null) {
                editable.removeSpan(spellCheckSpan);
            }
        }
        scheduleNewSpellCheck();
    }

@@ -338,10 +378,11 @@ public class SpellChecker implements SpellCheckerSessionListener {
    }

    private void createMisspelledSuggestionSpan(Editable editable, SuggestionsInfo suggestionsInfo,
            SpellCheckSpan spellCheckSpan) {
        final int start = editable.getSpanStart(spellCheckSpan);
        final int end = editable.getSpanEnd(spellCheckSpan);
        if (start < 0 || end <= start) return; // span was removed in the meantime
            SpellCheckSpan spellCheckSpan, int offset, int length) {
        final int spellCheckSpanStart = editable.getSpanStart(spellCheckSpan);
        final int spellCheckSpanEnd = editable.getSpanEnd(spellCheckSpan);
        if (spellCheckSpanStart < 0 || spellCheckSpanEnd <= spellCheckSpanStart)
            return; // span was removed in the meantime

        final int suggestionsCount = suggestionsInfo.getSuggestionsCount();
        if (suggestionsCount <= 0) {
@@ -349,6 +390,16 @@ public class SpellChecker implements SpellCheckerSessionListener {
            return;
        }

        final int start;
        final int end;
        if (offset != USE_SPAN_RANGE && length != USE_SPAN_RANGE) {
            start = spellCheckSpanStart + offset;
            end = start + length;
        } else {
            start = spellCheckSpanStart;
            end = spellCheckSpanEnd;
        }

        String[] suggestions = new String[suggestionsCount];
        for (int i = 0; i < suggestionsCount; i++) {
            suggestions[i] = suggestionsInfo.getSuggestionAt(i);
@@ -419,16 +470,43 @@ public class SpellChecker implements SpellCheckerSessionListener {
            int wordCount = 0;
            boolean scheduleOtherSpellCheck = false;

            if (mIsSentenceSpellCheckSupported) {
                int regionEnd;
                if (wordIteratorWindowEnd < end) {
                    // Several batches needed on that region. Cut after last previous word
                    regionEnd = mWordIterator.preceding(wordIteratorWindowEnd);
                    scheduleOtherSpellCheck = true;
                } else {
                    regionEnd = mWordIterator.preceding(end);
                }
                boolean correct = regionEnd != BreakIterator.DONE;
                if (correct) {
                    regionEnd = mWordIterator.getEnd(regionEnd);
                    correct = regionEnd != BreakIterator.DONE;
                }
                if (!correct) {
                    editable.removeSpan(mRange);
                    return;
                }
                wordStart = regionEnd;
                // TODO: Find the start position of the sentence.
                // Set span with the context
                final int spellCheckStart =  Math.min(
                        start, Math.max(wordStart, regionEnd - WORD_ITERATOR_INTERVAL));
                if (regionEnd <= spellCheckStart) {
                    return;
                }
                addSpellCheckSpan(editable, spellCheckStart, regionEnd);
            } else {
                while (wordStart <= end) {
                    if (wordEnd >= start && wordEnd > wordStart) {
                        if (wordCount >= MAX_NUMBER_OF_WORDS) {
                            scheduleOtherSpellCheck = true;
                            break;
                        }

                    // A new word has been created across the interval boundaries with this edit.
                    // The previous spans (that ended on start / started on end) are not valid
                    // anymore and must be removed.
                        // A new word has been created across the interval boundaries with this
                        // edit. The previous spans (that ended on start / started on end) are
                        // not valid anymore and must be removed.
                        if (wordStart < start && wordEnd > start) {
                            removeSpansAt(editable, start, spellCheckSpans);
                            removeSpansAt(editable, start, suggestionSpans);
@@ -472,8 +550,10 @@ public class SpellChecker implements SpellCheckerSessionListener {
                    wordEnd = mWordIterator.following(wordEnd);
                    if ((wordIteratorWindowEnd < end) &&
                            (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) {
                    wordIteratorWindowEnd = Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL);
                    mWordIterator.setCharSequence(editable, originalWordEnd, wordIteratorWindowEnd);
                        wordIteratorWindowEnd =
                                Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL);
                        mWordIterator.setCharSequence(
                                editable, originalWordEnd, wordIteratorWindowEnd);
                        wordEnd = mWordIterator.following(originalWordEnd);
                    }
                    if (wordEnd == BreakIterator.DONE) break;
@@ -482,6 +562,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
                        break;
                    }
                }
            }

            if (scheduleOtherSpellCheck) {
                // Update range span: start new spell check from last wordStart