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

Commit 287d6c6e authored by Gilles Debunne's avatar Gilles Debunne
Browse files

Bug 5250788: LatinIME slows down as amount of Text increases

Removed unnecessary CharSequenceIterator and made the WordIterator
work on String instead of CharSequence

Submit words to the spell checker by batches.

Refactored WordIterator to make it intrinsically local.

Change-Id: Ie9e30691985a130fa55cd052005ddb22a21761cb
parent e0f2515b
Loading
Loading
Loading
Loading
+0 −100
Original line number Diff line number Diff line
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.text;

import java.text.CharacterIterator;

/** {@hide} */
public class CharSequenceIterator implements CharacterIterator {
    private final CharSequence mValue;

    private final int mLength;
    private int mIndex;

    public CharSequenceIterator(CharSequence value) {
        mValue = value;
        mLength = value.length();
        mIndex = 0;
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }

    /** {@inheritDoc} */
    public char current() {
        if (mIndex == mLength) {
            return DONE;
        }
        return mValue.charAt(mIndex);
    }

    /** {@inheritDoc} */
    public int getBeginIndex() {
        return 0;
    }

    /** {@inheritDoc} */
    public int getEndIndex() {
        return mLength;
    }

    /** {@inheritDoc} */
    public int getIndex() {
        return mIndex;
    }

    /** {@inheritDoc} */
    public char first() {
        return setIndex(0);
    }

    /** {@inheritDoc} */
    public char last() {
        return setIndex(mLength - 1);
    }

    /** {@inheritDoc} */
    public char next() {
        if (mIndex == mLength) {
            return DONE;
        }
        return setIndex(mIndex + 1);
    }

    /** {@inheritDoc} */
    public char previous() {
        if (mIndex == 0) {
            return DONE;
        }
        return setIndex(mIndex - 1);
    }

    /** {@inheritDoc} */
    public char setIndex(int index) {
        if ((index < 0) || (index > mLength)) {
            throw new IllegalArgumentException("Valid range is [" + 0 + "..." + mLength + "]");
        }
        mIndex = index;
        return current();
    }
}
+6 −4
Original line number Diff line number Diff line
@@ -35,11 +35,11 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
                (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0));
    }

    private int getCurrentLineTop(Spannable buffer, Layout layout) {
    private static int getCurrentLineTop(Spannable buffer, Layout layout) {
        return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer)));
    }

    private int getPageHeight(TextView widget) {
    private static int getPageHeight(TextView widget) {
        // This calculation does not take into account the view transformations that
        // may have been applied to the child or its containers.  In case of scaling or
        // rotation, the calculated page height may be incorrect.
@@ -196,14 +196,16 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
    /** {@hide} */
    @Override
    protected boolean leftWord(TextView widget, Spannable buffer) {
        mWordIterator.setCharSequence(buffer);
        final int selectionEnd = widget.getSelectionEnd();
        mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
        return Selection.moveToPreceding(buffer, mWordIterator, isSelecting(buffer));
    }

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

+53 −98
Original line number Diff line number Diff line
@@ -17,14 +17,9 @@

package android.text.method;

import android.text.CharSequenceIterator;
import android.text.Editable;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextWatcher;

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

/**
@@ -36,8 +31,11 @@ import java.util.Locale;
 * {@hide}
 */
public class WordIterator implements Selection.PositionIterator {
    private CharSequence mCurrent;
    private boolean mCurrentDirty = false;
    // Size of the window for the word iterator, should be greater than the longest word's length
    private static final int WINDOW_WIDTH = 50;

    private String mString;
    private int mOffsetShift;

    private BreakIterator mIterator;

@@ -56,70 +54,40 @@ public class WordIterator implements Selection.PositionIterator {
        mIterator = BreakIterator.getWordInstance(locale);
    }

    private final TextWatcher mWatcher = new TextWatcher() {
        /** {@inheritDoc} */
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // ignored
        }
    public void setCharSequence(CharSequence charSequence, int start, int end) {
        mOffsetShift = Math.max(0, start - WINDOW_WIDTH);
        final int windowEnd = Math.min(charSequence.length(), end + WINDOW_WIDTH);

        /** {@inheritDoc} */
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            mCurrentDirty = true;
        }

        /** {@inheritDoc} */
        public void afterTextChanged(Editable s) {
            // ignored
        }
    };

    public void setCharSequence(CharSequence incoming) {
        // When incoming is different object, move listeners to new sequence
        // and mark as dirty so we reload contents.
        if (mCurrent != incoming) {
            if (mCurrent instanceof Editable) {
                ((Editable) mCurrent).removeSpan(mWatcher);
            }

            if (incoming instanceof Editable) {
                ((Editable) incoming).setSpan(
                        mWatcher, 0, incoming.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
            }

            mCurrent = incoming;
            mCurrentDirty = true;
        }

        if (mCurrentDirty) {
            final CharacterIterator charIterator = new CharSequenceIterator(mCurrent);
            mIterator.setText(charIterator);

            mCurrentDirty = false;
        }
        mString = charSequence.toString().substring(mOffsetShift, windowEnd);
        mIterator.setText(mString);
    }

    /** {@inheritDoc} */
    public int preceding(int offset) {
        int shiftedOffset = offset - mOffsetShift;
        do {
            offset = mIterator.preceding(offset);
            if (offset == BreakIterator.DONE || isOnLetterOrDigit(offset)) {
                break;
            shiftedOffset = mIterator.preceding(shiftedOffset);
            if (shiftedOffset == BreakIterator.DONE) {
                return BreakIterator.DONE;
            }
            if (isOnLetterOrDigit(shiftedOffset)) {
                return shiftedOffset + mOffsetShift;
            }
        } while (true);

        return offset;
    }

    /** {@inheritDoc} */
    public int following(int offset) {
        int shiftedOffset = offset - mOffsetShift;
        do {
            offset = mIterator.following(offset);
            if (offset == BreakIterator.DONE || isAfterLetterOrDigit(offset)) {
                break;
            shiftedOffset = mIterator.following(shiftedOffset);
            if (shiftedOffset == BreakIterator.DONE) {
                return BreakIterator.DONE;
            }
            if (isAfterLetterOrDigit(shiftedOffset)) {
                return shiftedOffset + mOffsetShift;
            }
        } while (true);

        return offset;
    }

    /** If <code>offset</code> is within a word, returns the index of the first character of that
@@ -135,17 +103,18 @@ public class WordIterator implements Selection.PositionIterator {
     * @throws IllegalArgumentException is offset is not valid.
     */
    public int getBeginning(int offset) {
        checkOffsetIsValid(offset);
        final int shiftedOffset = offset - mOffsetShift;
        checkOffsetIsValid(shiftedOffset);

        if (isOnLetterOrDigit(offset)) {
            if (mIterator.isBoundary(offset)) {
                return offset;
        if (isOnLetterOrDigit(shiftedOffset)) {
            if (mIterator.isBoundary(shiftedOffset)) {
                return shiftedOffset + mOffsetShift;
            } else {
                return mIterator.preceding(offset);
                return mIterator.preceding(shiftedOffset) + mOffsetShift;
            }
        } else {
            if (isAfterLetterOrDigit(offset)) {
                return mIterator.preceding(offset);
            if (isAfterLetterOrDigit(shiftedOffset)) {
                return mIterator.preceding(shiftedOffset) + mOffsetShift;
            }
        }
        return BreakIterator.DONE;
@@ -164,58 +133,44 @@ public class WordIterator implements Selection.PositionIterator {
     * @throws IllegalArgumentException is offset is not valid.
     */
    public int getEnd(int offset) {
        checkOffsetIsValid(offset);
        final int shiftedOffset = offset - mOffsetShift;
        checkOffsetIsValid(shiftedOffset);

        if (isAfterLetterOrDigit(offset)) {
            if (mIterator.isBoundary(offset)) {
                return offset;
        if (isAfterLetterOrDigit(shiftedOffset)) {
            if (mIterator.isBoundary(shiftedOffset)) {
                return shiftedOffset + mOffsetShift;
            } else {
                return mIterator.following(offset);
                return mIterator.following(shiftedOffset) + mOffsetShift;
            }
        } else {
            if (isOnLetterOrDigit(offset)) {
                return mIterator.following(offset);
            if (isOnLetterOrDigit(shiftedOffset)) {
                return mIterator.following(shiftedOffset) + mOffsetShift;
            }
        }
        return BreakIterator.DONE;
    }

    private boolean isAfterLetterOrDigit(int offset) {
        if (offset - 1 >= 0) {
            final char previousChar = mCurrent.charAt(offset - 1);
            if (Character.isLetterOrDigit(previousChar)) return true;
            if (offset - 2 >= 0) {
                final char previousPreviousChar = mCurrent.charAt(offset - 2);
                if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
                    final int codePoint = Character.toCodePoint(previousPreviousChar, previousChar);
                    return Character.isLetterOrDigit(codePoint);
                }
            }
    private boolean isAfterLetterOrDigit(int shiftedOffset) {
        if (shiftedOffset >= 1 && shiftedOffset <= mString.length()) {
            final int codePoint = mString.codePointBefore(shiftedOffset);
            if (Character.isLetterOrDigit(codePoint)) return true;
        }
        return false;
    }

    private boolean isOnLetterOrDigit(int offset) {
        final int length = mCurrent.length();
        if (offset < length) {
            final char currentChar = mCurrent.charAt(offset);
            if (Character.isLetterOrDigit(currentChar)) return true;
            if (offset + 1 < length) {
                final char nextChar = mCurrent.charAt(offset + 1);
                if (Character.isSurrogatePair(currentChar, nextChar)) {
                    final int codePoint = Character.toCodePoint(currentChar, nextChar);
                    return Character.isLetterOrDigit(codePoint);
                }
            }
    private boolean isOnLetterOrDigit(int shiftedOffset) {
        if (shiftedOffset >= 0 && shiftedOffset < mString.length()) {
            final int codePoint = mString.codePointAt(shiftedOffset);
            if (Character.isLetterOrDigit(codePoint)) return true;
        }
        return false;
    }

    private void checkOffsetIsValid(int offset) {
        if (offset < 0 || offset > mCurrent.length()) {
            final String message = "Invalid offset: " + offset +
                    ". Valid range is [0, " + mCurrent.length() + "]";
            throw new IllegalArgumentException(message);
    private void checkOffsetIsValid(int shiftedOffset) {
        if (shiftedOffset < 0 || shiftedOffset > mString.length()) {
            throw new IllegalArgumentException("Invalid offset: " + (shiftedOffset + mOffsetShift) +
                    ". Valid range is [" + mOffsetShift + ", " + (mString.length() + mOffsetShift) +
                    "]");
        }
    }
}
+191 −24

File changed.

Preview size limit exceeded, changes collapsed.

+26 −129
Original line number Diff line number Diff line
@@ -353,8 +353,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    // Set when this TextView gained focus with some text selected. Will start selection mode.
    private boolean mCreatedWithASelection = false;

    // Size of the window for the word iterator, should be greater than the longest word's length
    private static final int WORD_ITERATOR_WINDOW_WIDTH = 50;
    private WordIterator mWordIterator;

    private SpellChecker mSpellChecker;
@@ -6124,7 +6122,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     * not the full view width with padding.
     * {@hide}
     */
    protected void makeNewLayout(int w, int hintWidth,
    protected void makeNewLayout(int wantWidth, int hintWidth,
                                 BoringLayout.Metrics boring,
                                 BoringLayout.Metrics hintBoring,
                                 int ellipsisWidth, boolean bringIntoView) {
@@ -6136,8 +6134,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

        mHighlightPathBogus = true;

        if (w < 0) {
            w = 0;
        if (wantWidth < 0) {
            wantWidth = 0;
        }
        if (hintWidth < 0) {
            hintWidth = 0;
@@ -6157,12 +6155,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            resolveTextDirection();
        }

        mLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment, shouldEllipsize,
        mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
                effectiveEllipsize, effectiveEllipsize == mEllipsize);
        if (switchEllipsize) {
            TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
                    TruncateAt.END : TruncateAt.MARQUEE;
            mSavedMarqueeModeLayout = makeSingleLayout(w, boring, ellipsisWidth, alignment,
            mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
                    shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
        }

@@ -6170,7 +6168,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        mHintLayout = null;

        if (mHint != null) {
            if (shouldEllipsize) hintWidth = w;
            if (shouldEllipsize) hintWidth = wantWidth;

            if (hintBoring == UNKNOWN_BORING) {
                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
@@ -6254,12 +6252,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        prepareCursorControllers();
    }

    private Layout makeSingleLayout(int w, BoringLayout.Metrics boring, int ellipsisWidth,
    private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
            Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
            boolean useSaved) {
        Layout result = null;
        if (mText instanceof Spannable) {
            result = new DynamicLayout(mText, mTransformed, mTextPaint, w,
            result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
                    alignment, mTextDir, mSpacingMult,
                    mSpacingAdd, mIncludePad, mInput == null ? effectiveEllipsize : null,
                            ellipsisWidth);
@@ -6272,53 +6270,53 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            }

            if (boring != null) {
                if (boring.width <= w &&
                if (boring.width <= wantWidth &&
                        (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
                    if (useSaved && mSavedLayout != null) {
                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
                                w, alignment, mSpacingMult, mSpacingAdd,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad);
                    } else {
                        result = BoringLayout.make(mTransformed, mTextPaint,
                                w, alignment, mSpacingMult, mSpacingAdd,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad);
                    }

                    if (useSaved) {
                        mSavedLayout = (BoringLayout) result;
                    }
                } else if (shouldEllipsize && boring.width <= w) {
                } else if (shouldEllipsize && boring.width <= wantWidth) {
                    if (useSaved && mSavedLayout != null) {
                        result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
                                w, alignment, mSpacingMult, mSpacingAdd,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad, effectiveEllipsize,
                                ellipsisWidth);
                    } else {
                        result = BoringLayout.make(mTransformed, mTextPaint,
                                w, alignment, mSpacingMult, mSpacingAdd,
                                wantWidth, alignment, mSpacingMult, mSpacingAdd,
                                boring, mIncludePad, effectiveEllipsize,
                                ellipsisWidth);
                    }
                } else if (shouldEllipsize) {
                    result = new StaticLayout(mTransformed,
                            0, mTransformed.length(),
                            mTextPaint, w, alignment, mTextDir, mSpacingMult,
                            mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
                            mSpacingAdd, mIncludePad, effectiveEllipsize,
                            ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
                } else {
                    result = new StaticLayout(mTransformed, mTextPaint,
                            w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
                            wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
                            mIncludePad);
                }
            } else if (shouldEllipsize) {
                result = new StaticLayout(mTransformed,
                        0, mTransformed.length(),
                        mTextPaint, w, alignment, mTextDir, mSpacingMult,
                        mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
                        mSpacingAdd, mIncludePad, effectiveEllipsize,
                        ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
            } else {
                result = new StaticLayout(mTransformed, mTextPaint,
                        w, alignment, mTextDir, mSpacingMult, mSpacingAdd,
                        wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
                        mIncludePad);
            }
        }
@@ -7749,98 +7747,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     * Create new SpellCheckSpans on the modified region.
     */
    private void updateSpellCheckSpans(int start, int end) {
        if (!isTextEditable() || !isSuggestionsEnabled() || !getSpellChecker().isSessionActive())
            return;
        Editable text = (Editable) mText;

        final int shift = prepareWordIterator(start, end);
        final int shiftedStart = start - shift;
        final int shiftedEnd = end - shift;

        // Move back to the beginning of the current word, if any
        int wordStart = mWordIterator.preceding(shiftedStart);
        int wordEnd;
        if (wordStart == BreakIterator.DONE) {
            wordEnd = mWordIterator.following(shiftedStart);
            if (wordEnd != BreakIterator.DONE) {
                wordStart = mWordIterator.getBeginning(wordEnd);
            }
        } else {
            wordEnd = mWordIterator.getEnd(wordStart);
        }
        if (wordEnd == BreakIterator.DONE) {
            return;
        }

        // We need to expand by one character because we want to include the spans that end/start
        // at position start/end respectively.
        SpellCheckSpan[] spellCheckSpans = text.getSpans(start - 1, end + 1, SpellCheckSpan.class);
        SuggestionSpan[] suggestionSpans = text.getSpans(start - 1, end + 1, SuggestionSpan.class);
        final int numberOfSpellCheckSpans = spellCheckSpans.length;

        // Iterate over the newly added text and schedule new SpellCheckSpans
        while (wordStart <= shiftedEnd) {
            if (wordEnd >= shiftedStart && wordEnd > wordStart) {
                // A new word has been created across the interval boundaries. Remove previous spans
                if (wordStart < shiftedStart && wordEnd > shiftedStart) {
                    removeSpansAt(start, spellCheckSpans, text);
                    removeSpansAt(start, suggestionSpans, text);
                }

                if (wordStart < shiftedEnd && wordEnd > shiftedEnd) {
                    removeSpansAt(end, spellCheckSpans, text);
                    removeSpansAt(end, suggestionSpans, text);
                }

                // Do not create new boundary spans if they already exist
                boolean createSpellCheckSpan = true;
                if (wordEnd == shiftedStart) {
                    for (int i = 0; i < numberOfSpellCheckSpans; i++) {
                        final int spanEnd = text.getSpanEnd(spellCheckSpans[i]);
                        if (spanEnd == start) {
                            createSpellCheckSpan = false;
                            break;
                        }
                    }
                }

                if (wordStart == shiftedEnd) {
                    for (int i = 0; i < numberOfSpellCheckSpans; i++) {
                        final int spanStart = text.getSpanStart(spellCheckSpans[i]);
                        if (spanStart == end) {
                            createSpellCheckSpan = false;
                            break;
                        }
                    }
                }

                if (createSpellCheckSpan) {
                    mSpellChecker.addSpellCheckSpan(wordStart + shift, wordEnd + shift);
                }
            }

            // iterate word by word
            wordEnd = mWordIterator.following(wordEnd);
            if (wordEnd == BreakIterator.DONE) break;
            wordStart = mWordIterator.getBeginning(wordEnd);
            if (wordStart == BreakIterator.DONE) {
                Log.e(LOG_TAG, "No word beginning from " + (wordEnd + shift) + "in " + mText);
                break;
            }
        }

        mSpellChecker.spellCheck();
    }

    private static <T> void removeSpansAt(int offset, T[] spans, Editable text) {
        final int length = spans.length;
        for (int i = 0; i < length; i++) {
            final T span = spans[i];
            final int start = text.getSpanStart(span);
            if (start > offset) continue;
            final int end = text.getSpanEnd(span);
            if (end < offset) continue;
            text.removeSpan(span);
        if (isTextEditable() && isSuggestionsEnabled()) {
            getSpellChecker().spellCheck(start, end);
        }
    }

@@ -8930,15 +8838,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            selectionStart = ((Spanned) mText).getSpanStart(urlSpan);
            selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan);
        } else {
            final int shift = prepareWordIterator(minOffset, maxOffset);
            if (mWordIterator == null) {
                mWordIterator = new WordIterator();
            }
            mWordIterator.setCharSequence(mText, minOffset, maxOffset);

            selectionStart = mWordIterator.getBeginning(minOffset - shift);
            selectionStart = mWordIterator.getBeginning(minOffset);
            if (selectionStart == BreakIterator.DONE) return false;
            selectionStart += shift;

            selectionEnd = mWordIterator.getEnd(maxOffset - shift);
            selectionEnd = mWordIterator.getEnd(maxOffset);
            if (selectionEnd == BreakIterator.DONE) return false;
            selectionEnd += shift;

            if (selectionStart == selectionEnd) {
                // Possible when the word iterator does not properly handle the text's language
@@ -8977,18 +8886,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return packRangeInLong(offset,  offset);
    }

    int prepareWordIterator(int start, int end) {
        if (mWordIterator == null) {
            mWordIterator = new WordIterator();
        }

        final int windowStart = Math.max(0, start - WORD_ITERATOR_WINDOW_WIDTH);
        final int windowEnd = Math.min(mText.length(), end + WORD_ITERATOR_WINDOW_WIDTH);
        mWordIterator.setCharSequence(mText.subSequence(windowStart, windowEnd));

        return windowStart;
    }

    private SpellChecker getSpellChecker() {
        if (mSpellChecker == null) {
            mSpellChecker = new SpellChecker(this);