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

Commit 9d8d3f15 authored by Gilles Debunne's avatar Gilles Debunne
Browse files

Spell checher's language synced with keyboard.

Bug 5379440. The spell check is now using the IME's language to
do the spell checking. Changing the input language triggers a
new spell check of the entire text.

Optimizations: ArrowKeyMovementMethod re-uses the TextView's
wordIterator, already set to the correct language.

One wordIterator shared by all SpellParsers in SpellChecker.
Cannot re-use TextView's because of concurrency issues.

With the current implementation, one has to type a new character
to see the new spell checking take place.

Change-Id: I0e460c0a6777548f89d03d6b68f3deea6606c17f
parent b10d396f
Loading
Loading
Loading
Loading
+6 −6
Original line number Diff line number Diff line
@@ -197,16 +197,18 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
    @Override
    protected boolean leftWord(TextView widget, Spannable buffer) {
        final int selectionEnd = widget.getSelectionEnd();
        mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
        return Selection.moveToPreceding(buffer, mWordIterator, isSelecting(buffer));
        final WordIterator wordIterator = widget.getWordIterator();
        wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
        return Selection.moveToPreceding(buffer, wordIterator, isSelecting(buffer));
    }

    /** {@hide} */
    @Override
    protected boolean rightWord(TextView widget, Spannable buffer) {
        final int selectionEnd = widget.getSelectionEnd();
        mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
        return Selection.moveToFollowing(buffer, mWordIterator, isSelecting(buffer));
        final WordIterator wordIterator = widget.getWordIterator();
        wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
        return Selection.moveToFollowing(buffer, wordIterator, isSelecting(buffer));
    }

    @Override
@@ -322,8 +324,6 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
        return sInstance;
    }

    private WordIterator mWordIterator = new WordIterator();

    private static final Object LAST_TAP_DOWN = new Object();
    private static ArrowKeyMovementMethod sInstance;
}
+56 −17
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import android.view.textservice.TextServicesManager;
import com.android.internal.util.ArrayUtils;

import java.text.BreakIterator;
import java.util.Locale;


/**
@@ -45,7 +46,7 @@ public class SpellChecker implements SpellCheckerSessionListener {

    private final TextView mTextView;

    final SpellCheckerSession mSpellCheckerSession;
    SpellCheckerSession mSpellCheckerSession;
    final int mCookie;

    // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
@@ -61,23 +62,54 @@ public class SpellChecker implements SpellCheckerSessionListener {

    private int mSpanSequenceCounter = 0;

    private Locale mCurrentLocale;

    // Shared by all SpellParsers. Cannot be shared with TextView since it may be used
    // concurrently due to the asynchronous nature of onGetSuggestions.
    private WordIterator mWordIterator;

    public SpellChecker(TextView textView) {
        mTextView = textView;

        final TextServicesManager textServicesManager = (TextServicesManager) textView.getContext().
                getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
        mSpellCheckerSession = textServicesManager.newSpellCheckerSession(
                null /* not currently used by the textServicesManager */,
                null /* null locale means use the languages defined in Settings
                        if referToSpellCheckerLanguageSettings is true */,
                        this, true /* means use the languages defined in Settings */);
        mCookie = hashCode();

        // Arbitrary: 4 simultaneous spell check spans. Will automatically double size on demand
        // Arbitrary: these arrays will automatically double their sizes on demand
        final int size = ArrayUtils.idealObjectArraySize(1);
        mIds = new int[size];
        mSpellCheckSpans = new SpellCheckSpan[size];

        setLocale(mTextView.getLocale());

        mCookie = hashCode();
    }

    private void setLocale(Locale locale) {
        final TextServicesManager textServicesManager = (TextServicesManager)
                mTextView.getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
        mSpellCheckerSession = textServicesManager.newSpellCheckerSession(
                null /* Bundle not currently used by the textServicesManager */,
                locale, this, false /* means any available languages from current spell checker */);
        mCurrentLocale = locale;

        // Restore SpellCheckSpans in pool
        for (int i = 0; i < mLength; i++) {
            mSpellCheckSpans[i].setSpellCheckInProgress(false);
            mIds[i] = -1;
        }
        mLength = 0;

        // Change SpellParsers' wordIterator locale
        mWordIterator = new WordIterator(locale);

        // Stop all SpellParsers
        final int length = mSpellParsers.length;
        for (int i = 0; i < length; i++) {
            mSpellParsers[i].finish();
        }

        // Remove existing misspelled SuggestionSpans
        mTextView.removeMisspelledSpans((Editable) mTextView.getText());

        // This class is the listener for locale change: warn other locale-aware objects
        mTextView.onLocaleChanged();
    }

    /**
@@ -95,7 +127,7 @@ public class SpellChecker implements SpellCheckerSessionListener {

        final int length = mSpellParsers.length;
        for (int i = 0; i < length; i++) {
            mSpellParsers[i].close();
            mSpellParsers[i].finish();
        }
    }

@@ -140,12 +172,20 @@ public class SpellChecker implements SpellCheckerSessionListener {
    }

    public void spellCheck(int start, int end) {
        final Locale locale = mTextView.getLocale();
        if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) {
            setLocale(locale);
            // Re-check the entire text
            start = 0;
            end = mTextView.getText().length();
        }

        if (!isSessionActive()) return;

        final int length = mSpellParsers.length;
        for (int i = 0; i < length; i++) {
            final SpellParser spellParser = mSpellParsers[i];
            if (spellParser.isDone()) {
            if (spellParser.isFinished()) {
                spellParser.init(start, end);
                spellParser.parse();
                return;
@@ -229,7 +269,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
        final int length = mSpellParsers.length;
        for (int i = 0; i < length; i++) {
            final SpellParser spellParser = mSpellParsers[i];
            if (!spellParser.isDone()) {
            if (!spellParser.isFinished()) {
                spellParser.parse();
            }
        }
@@ -301,7 +341,6 @@ public class SpellChecker implements SpellCheckerSessionListener {
    }

    private class SpellParser {
        private WordIterator mWordIterator = new WordIterator(/*TODO Locale*/);
        private Object mRange = new Object();

        public void init(int start, int end) {
@@ -309,11 +348,11 @@ public class SpellChecker implements SpellCheckerSessionListener {
                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }

        public void close() {
        public void finish() {
            ((Editable) mTextView.getText()).removeSpan(mRange);
        }

        public boolean isDone() {
        public boolean isFinished() {
            return ((Editable) mTextView.getText()).getSpanStart(mRange) < 0;
        }

+56 −15
Original line number Diff line number Diff line
@@ -132,6 +132,7 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.RemoteViews.RemoteView;

@@ -147,6 +148,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;

/**
 * Displays text to the user and optionally allows them to edit it.  A TextView
@@ -2941,15 +2943,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                    sp.removeSpan(cw);
                }

                SuggestionSpan[] suggestionSpans = sp.getSpans(0, sp.length(), SuggestionSpan.class);
                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) {
                        sp.removeSpan(suggestionSpans[i]);
                    }
                }

                removeMisspelledSpans(sp);
                sp.removeSpan(mSuggestionRangeSpan);

                ss.text = sp;
@@ -2969,6 +2963,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return superState;
    }

    void removeMisspelledSpans(Spannable spannable) {
        SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
                SuggestionSpan.class);
        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) {
                spannable.removeSpan(suggestionSpans[i]);
            }
        }
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
@@ -8838,15 +8844,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            selectionStart = ((Spanned) mText).getSpanStart(urlSpan);
            selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan);
        } else {
            if (mWordIterator == null) {
                mWordIterator = new WordIterator();
            }
            mWordIterator.setCharSequence(mText, minOffset, maxOffset);
            final WordIterator wordIterator = getWordIterator();
            wordIterator.setCharSequence(mText, minOffset, maxOffset);

            selectionStart = mWordIterator.getBeginning(minOffset);
            selectionStart = wordIterator.getBeginning(minOffset);
            if (selectionStart == BreakIterator.DONE) return false;

            selectionEnd = mWordIterator.getEnd(maxOffset);
            selectionEnd = wordIterator.getEnd(maxOffset);
            if (selectionEnd == BreakIterator.DONE) return false;

            if (selectionStart == selectionEnd) {
@@ -8861,6 +8865,43 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return selectionEnd > selectionStart;
    }

    /**
     * This is a temporary method. Future versions may support multi-locale text.
     *
     * @return The current locale used in this TextView, based on the current IME's locale,
     * or the system default locale if this is not defined.
     * @hide
     */
    public Locale getLocale() {
        Locale locale = Locale.getDefault();
        final InputMethodManager imm = InputMethodManager.peekInstance();
        if (imm != null) {
            final InputMethodSubtype currentInputMethodSubtype = imm.getCurrentInputMethodSubtype();
            if (currentInputMethodSubtype != null) {
                String localeString = currentInputMethodSubtype.getLocale();
                if (!TextUtils.isEmpty(localeString)) {
                    locale = new Locale(localeString);
                }
            }
        }
        return locale;
    }

    void onLocaleChanged() {
        // Will be re-created on demand in getWordIterator with the proper new locale
        mWordIterator = null;
    }

    /**
     * @hide
     */
    public WordIterator getWordIterator() {
        if (mWordIterator == null) {
            mWordIterator = new WordIterator(getLocale());
        }
        return mWordIterator;
    }

    private long getCharRange(int offset) {
        final int textLength = mText.length();
        if (offset + 1 < textLength) {