resolved conflicts for merge of 1742f20c to master

Change-Id: I02d84edfe8cc0b71881884ecec21f831404aa555
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 31da5b5..6700829 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -45,14 +45,15 @@
 
     // No more than this number of words will be parsed on each iteration to ensure a minimum
     // lock of the UI thread
-    public static final int MAX_NUMBER_OF_WORDS = 10;
+    public static final int MAX_NUMBER_OF_WORDS = 50;
 
-    // Safe estimate, will ensure that the interval below usually does not have to be updated
-    public static final int AVERAGE_WORD_LENGTH = 10;
+    // Rough estimate, such that the word iterator interval usually does not need to be shifted
+    public static final int AVERAGE_WORD_LENGTH = 7;
 
     // When parsing, use a character window of that size. Will be shifted if needed
     public static final int WORD_ITERATOR_INTERVAL = AVERAGE_WORD_LENGTH * MAX_NUMBER_OF_WORDS;
 
+    // Pause between each spell check to keep the UI smooth
     private final static int SPELL_PAUSE_DURATION = 400; // milliseconds
 
     private final TextView mTextView;
@@ -75,6 +76,14 @@
 
     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;
+
+    private TextServicesManager mTextServicesManager;
+
+    private Runnable mSpellRunnable;
+
     public SpellChecker(TextView textView) {
         mTextView = textView;
 
@@ -136,6 +145,10 @@
         for (int i = 0; i < length; i++) {
             mSpellParsers[i].stop();
         }
+
+        if (mSpellRunnable != null) {
+            mTextView.removeCallbacks(mSpellRunnable);
+        }
     }
 
     private int nextSpellCheckSpanIndex() {
@@ -285,18 +298,29 @@
             }
         }
 
-        mTextView.postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                final int length = mSpellParsers.length;
-                for (int i = 0; i < length; i++) {
-                    final SpellParser spellParser = mSpellParsers[i];
-                    if (spellParser.isParsing()) {
-                        spellParser.parse();
+        scheduleNewSpellCheck();
+    }
+
+    private void scheduleNewSpellCheck() {
+        if (mSpellRunnable == null) {
+            mSpellRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    final int length = mSpellParsers.length;
+                    for (int i = 0; i < length; i++) {
+                        final SpellParser spellParser = mSpellParsers[i];
+                        if (!spellParser.isFinished()) {
+                            spellParser.parse();
+                            break; // run one spell parser at a time to bound running time
+                        }
                     }
                 }
-            }
-        }, SPELL_PAUSE_DURATION);
+            };
+        } else {
+            mTextView.removeCallbacks(mSpellRunnable);
+        }
+
+        mTextView.postDelayed(mSpellRunnable, SPELL_PAUSE_DURATION);
     }
 
     private void createMisspelledSuggestionSpan(Editable editable, SuggestionsInfo suggestionsInfo,
@@ -393,20 +417,19 @@
             final int start = editable.getSpanStart(mRange);
             final int end = editable.getSpanEnd(mRange);
 
-            final WordIterator wordIterator = mTextView.getWordIterator();
             int wordIteratorWindowEnd = Math.min(end, start + WORD_ITERATOR_INTERVAL);
-            wordIterator.setCharSequence(editable, start, wordIteratorWindowEnd);
+            mWordIterator.setCharSequence(editable, start, wordIteratorWindowEnd);
 
             // Move back to the beginning of the current word, if any
-            int wordStart = wordIterator.preceding(start);
+            int wordStart = mWordIterator.preceding(start);
             int wordEnd;
             if (wordStart == BreakIterator.DONE) {
-                wordEnd = wordIterator.following(start);
+                wordEnd = mWordIterator.following(start);
                 if (wordEnd != BreakIterator.DONE) {
-                    wordStart = wordIterator.getBeginning(wordEnd);
+                    wordStart = mWordIterator.getBeginning(wordEnd);
                 }
             } else {
-                wordEnd = wordIterator.getEnd(wordStart);
+                wordEnd = mWordIterator.getEnd(wordStart);
             }
             if (wordEnd == BreakIterator.DONE) {
                 removeRangeSpan(editable);
@@ -472,15 +495,15 @@
 
                 // iterate word by word
                 int originalWordEnd = wordEnd;
-                wordEnd = wordIterator.following(wordEnd);
+                wordEnd = mWordIterator.following(wordEnd);
                 if ((wordIteratorWindowEnd < end) &&
                         (wordEnd == BreakIterator.DONE || wordEnd >= wordIteratorWindowEnd)) {
                     wordIteratorWindowEnd = Math.min(end, originalWordEnd + WORD_ITERATOR_INTERVAL);
-                    wordIterator.setCharSequence(editable, originalWordEnd, wordIteratorWindowEnd);
-                    wordEnd = wordIterator.following(originalWordEnd);
+                    mWordIterator.setCharSequence(editable, originalWordEnd, wordIteratorWindowEnd);
+                    wordEnd = mWordIterator.following(originalWordEnd);
                 }
                 if (wordEnd == BreakIterator.DONE) break;
-                wordStart = wordIterator.getBeginning(wordEnd);
+                wordStart = mWordIterator.getBeginning(wordEnd);
                 if (wordStart == BreakIterator.DONE) {
                     break;
                 }