Merge "Move Range out of RichInputConnection and rename it."
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f3fc317..243928f 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -73,7 +73,6 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.latin.RichInputConnection.Range;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
@@ -87,6 +86,7 @@
 import com.android.inputmethod.latin.utils.RecapitalizeStatus;
 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
+import com.android.inputmethod.latin.utils.TextRange;
 import com.android.inputmethod.latin.utils.Utils;
 import com.android.inputmethod.latin.utils.Utils.Stats;
 import com.android.inputmethod.research.ResearchLogger;
@@ -2518,7 +2518,7 @@
         // If we don't know the cursor location, return.
         if (mLastSelectionStart < 0) return;
         if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
-        final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
+        final TextRange range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
                 0 /* additionalPrecedingWordsCount */);
         if (null == range) return; // Happens if we don't have an input connection at all
         // If for some strange reason (editor bug or so) we measure the text before the cursor as
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 6b22cb1..39170cf 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -17,9 +17,7 @@
 package com.android.inputmethod.latin;
 
 import android.inputmethodservice.InputMethodService;
-import android.text.Spanned;
 import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
@@ -32,9 +30,9 @@
 import com.android.inputmethod.latin.utils.CapsModeUtils;
 import com.android.inputmethod.latin.utils.DebugLogUtils;
 import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.TextRange;
 import com.android.inputmethod.research.ResearchLogger;
 
-import java.util.Arrays;
 import java.util.Locale;
 import java.util.regex.Pattern;
 
@@ -441,100 +439,6 @@
         return getNthPreviousWord(prev, sentenceSeperators, n);
     }
 
-    /**
-     * Represents a range of text, relative to the current cursor position.
-     */
-    public static final class Range {
-        private final CharSequence mTextAtCursor;
-        private final int mWordAtCursorStartIndex;
-        private final int mWordAtCursorEndIndex;
-        private final int mCursorIndex;
-
-        public final CharSequence mWord;
-
-        public int getNumberOfCharsInWordBeforeCursor() {
-            return mCursorIndex - mWordAtCursorStartIndex;
-        }
-
-        public int getNumberOfCharsInWordAfterCursor() {
-            return mWordAtCursorEndIndex - mCursorIndex;
-        }
-
-        /**
-         * Gets the suggestion spans that are put squarely on the word, with the exact start
-         * and end of the span matching the boundaries of the word.
-         * @return the list of spans.
-         */
-        public SuggestionSpan[] getSuggestionSpansAtWord() {
-            if (!(mTextAtCursor instanceof Spanned && mWord instanceof Spanned)) {
-                return new SuggestionSpan[0];
-            }
-            final Spanned text = (Spanned)mTextAtCursor;
-            // Note: it's fine to pass indices negative or greater than the length of the string
-            // to the #getSpans() method. The reason we need to get from -1 to +1 is that, the
-            // spans were cut at the cursor position, and #getSpans(start, end) does not return
-            // spans that end at `start' or begin at `end'. Consider the following case:
-            //              this| is          (The | symbolizes the cursor position
-            //              ---- ---
-            // In this case, the cursor is in position 4, so the 0~7 span has been split into
-            // a 0~4 part and a 4~7 part.
-            // If we called #getSpans(0, 4) in this case, we would only get the part from 0 to 4
-            // of the span, and not the part from 4 to 7, so we would not realize the span actually
-            // extends from 0 to 7. But if we call #getSpans(-1, 5) we'll get both the 0~4 and
-            // the 4~7 spans and we can merge them accordingly.
-            // Any span starting more than 1 char away from the word boundaries in any direction
-            // does not touch the word, so we don't need to consider it. That's why requesting
-            // -1 ~ +1 is enough.
-            // Of course this is only relevant if the cursor is at one end of the word. If it's
-            // in the middle, the -1 and +1 are not necessary, but they are harmless.
-            final SuggestionSpan[] spans = text.getSpans(mWordAtCursorStartIndex - 1,
-                    mWordAtCursorEndIndex + 1, SuggestionSpan.class);
-            int readIndex = 0;
-            int writeIndex = 0;
-            for (; readIndex < spans.length; ++readIndex) {
-                final SuggestionSpan span = spans[readIndex];
-                // The span may be null, as we null them when we find duplicates. Cf a few lines
-                // down.
-                if (null == span) continue;
-                // Tentative span start and end. This may be modified later if we realize the
-                // same span is also applied to other parts of the string.
-                int spanStart = text.getSpanStart(span);
-                int spanEnd = text.getSpanEnd(span);
-                for (int i = readIndex + 1; i < spans.length; ++i) {
-                    if (span.equals(spans[i])) {
-                        // We found the same span somewhere else. Read the new extent of this
-                        // span, and adjust our values accordingly.
-                        spanStart = Math.min(spanStart, text.getSpanStart(spans[i]));
-                        spanEnd = Math.max(spanEnd, text.getSpanEnd(spans[i]));
-                        // ...and mark the span as processed.
-                        spans[i] = null;
-                    }
-                }
-                if (spanStart == mWordAtCursorStartIndex && spanEnd == mWordAtCursorEndIndex) {
-                    // If the span does not start and stop here, we ignore it. It probably extends
-                    // past the start or end of the word, as happens in missing space correction
-                    // or EasyEditSpans put by voice input.
-                    spans[writeIndex++] = spans[readIndex];
-                }
-            }
-            return writeIndex == readIndex ? spans : Arrays.copyOfRange(spans, 0, writeIndex);
-        }
-
-        public Range(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
-                final int wordAtCursorEndIndex, final int cursorIndex) {
-            if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
-                    || cursorIndex > wordAtCursorEndIndex
-                    || wordAtCursorEndIndex > textAtCursor.length()) {
-                throw new IndexOutOfBoundsException();
-            }
-            mTextAtCursor = textAtCursor;
-            mWordAtCursorStartIndex = wordAtCursorStartIndex;
-            mWordAtCursorEndIndex = wordAtCursorEndIndex;
-            mCursorIndex = cursorIndex;
-            mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex);
-        }
-    }
-
     private static boolean isSeparator(int code, String sep) {
         return sep.indexOf(code) != -1;
     }
@@ -581,7 +485,7 @@
      */
     public CharSequence getWordAtCursor(String separators) {
         // getWordRangeAtCursor returns null if the connection is null
-        Range r = getWordRangeAtCursor(separators, 0);
+        TextRange r = getWordRangeAtCursor(separators, 0);
         return (r == null) ? null : r.mWord;
     }
 
@@ -593,7 +497,8 @@
      *   be included in the returned range
      * @return a range containing the text surrounding the cursor
      */
-    public Range getWordRangeAtCursor(final String sep, final int additionalPrecedingWordsCount) {
+    public TextRange getWordRangeAtCursor(final String sep,
+            final int additionalPrecedingWordsCount) {
         mIC = mParent.getCurrentInputConnection();
         if (mIC == null || sep == null) {
             return null;
@@ -643,7 +548,7 @@
             }
         }
 
-        return new Range(TextUtils.concat(before, after), startIndexInBefore,
+        return new TextRange(TextUtils.concat(before, after), startIndexInBefore,
                 before.length() + endIndexInAfter, before.length());
     }
 
diff --git a/java/src/com/android/inputmethod/latin/utils/TextRange.java b/java/src/com/android/inputmethod/latin/utils/TextRange.java
new file mode 100644
index 0000000..5793e41
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/TextRange.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2013 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 com.android.inputmethod.latin.utils;
+
+import android.text.Spanned;
+import android.text.style.SuggestionSpan;
+
+import java.util.Arrays;
+
+/**
+ * Represents a range of text, relative to the current cursor position.
+ */
+public final class TextRange {
+    private final CharSequence mTextAtCursor;
+    private final int mWordAtCursorStartIndex;
+    private final int mWordAtCursorEndIndex;
+    private final int mCursorIndex;
+
+    public final CharSequence mWord;
+
+    public int getNumberOfCharsInWordBeforeCursor() {
+        return mCursorIndex - mWordAtCursorStartIndex;
+    }
+
+    public int getNumberOfCharsInWordAfterCursor() {
+        return mWordAtCursorEndIndex - mCursorIndex;
+    }
+
+    /**
+     * Gets the suggestion spans that are put squarely on the word, with the exact start
+     * and end of the span matching the boundaries of the word.
+     * @return the list of spans.
+     */
+    public SuggestionSpan[] getSuggestionSpansAtWord() {
+        if (!(mTextAtCursor instanceof Spanned && mWord instanceof Spanned)) {
+            return new SuggestionSpan[0];
+        }
+        final Spanned text = (Spanned)mTextAtCursor;
+        // Note: it's fine to pass indices negative or greater than the length of the string
+        // to the #getSpans() method. The reason we need to get from -1 to +1 is that, the
+        // spans were cut at the cursor position, and #getSpans(start, end) does not return
+        // spans that end at `start' or begin at `end'. Consider the following case:
+        //              this| is          (The | symbolizes the cursor position
+        //              ---- ---
+        // In this case, the cursor is in position 4, so the 0~7 span has been split into
+        // a 0~4 part and a 4~7 part.
+        // If we called #getSpans(0, 4) in this case, we would only get the part from 0 to 4
+        // of the span, and not the part from 4 to 7, so we would not realize the span actually
+        // extends from 0 to 7. But if we call #getSpans(-1, 5) we'll get both the 0~4 and
+        // the 4~7 spans and we can merge them accordingly.
+        // Any span starting more than 1 char away from the word boundaries in any direction
+        // does not touch the word, so we don't need to consider it. That's why requesting
+        // -1 ~ +1 is enough.
+        // Of course this is only relevant if the cursor is at one end of the word. If it's
+        // in the middle, the -1 and +1 are not necessary, but they are harmless.
+        final SuggestionSpan[] spans = text.getSpans(mWordAtCursorStartIndex - 1,
+                mWordAtCursorEndIndex + 1, SuggestionSpan.class);
+        int readIndex = 0;
+        int writeIndex = 0;
+        for (; readIndex < spans.length; ++readIndex) {
+            final SuggestionSpan span = spans[readIndex];
+            // The span may be null, as we null them when we find duplicates. Cf a few lines
+            // down.
+            if (null == span) continue;
+            // Tentative span start and end. This may be modified later if we realize the
+            // same span is also applied to other parts of the string.
+            int spanStart = text.getSpanStart(span);
+            int spanEnd = text.getSpanEnd(span);
+            for (int i = readIndex + 1; i < spans.length; ++i) {
+                if (span.equals(spans[i])) {
+                    // We found the same span somewhere else. Read the new extent of this
+                    // span, and adjust our values accordingly.
+                    spanStart = Math.min(spanStart, text.getSpanStart(spans[i]));
+                    spanEnd = Math.max(spanEnd, text.getSpanEnd(spans[i]));
+                    // ...and mark the span as processed.
+                    spans[i] = null;
+                }
+            }
+            if (spanStart == mWordAtCursorStartIndex && spanEnd == mWordAtCursorEndIndex) {
+                // If the span does not start and stop here, we ignore it. It probably extends
+                // past the start or end of the word, as happens in missing space correction
+                // or EasyEditSpans put by voice input.
+                spans[writeIndex++] = spans[readIndex];
+            }
+        }
+        return writeIndex == readIndex ? spans : Arrays.copyOfRange(spans, 0, writeIndex);
+    }
+
+    public TextRange(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
+            final int wordAtCursorEndIndex, final int cursorIndex) {
+        if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
+                || cursorIndex > wordAtCursorEndIndex
+                || wordAtCursorEndIndex > textAtCursor.length()) {
+            throw new IndexOutOfBoundsException();
+        }
+        mTextAtCursor = textAtCursor;
+        mWordAtCursorStartIndex = wordAtCursorStartIndex;
+        mWordAtCursorEndIndex = wordAtCursorEndIndex;
+        mCursorIndex = cursorIndex;
+        mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex);
+    }
+}
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index f073308..06a21bc 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -57,11 +57,11 @@
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputConnection;
-import com.android.inputmethod.latin.RichInputConnection.Range;
 import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.TextRange;
 import com.android.inputmethod.research.MotionEventReader.ReplayData;
 import com.android.inputmethod.research.ui.SplashScreen;
 
@@ -1220,7 +1220,7 @@
             final RichInputConnection connection) {
         String word = "";
         if (connection != null) {
-            Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
+            TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
             if (range != null) {
                 word = range.mWord.toString();
             }
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
index 0e077bb..c0dd993 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.utils.TextRange;
+
 import android.inputmethodservice.InputMethodService;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
@@ -30,8 +32,6 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionWrapper;
 
-import com.android.inputmethod.latin.RichInputConnection.Range;
-
 import java.util.Locale;
 
 @SmallTest
@@ -169,7 +169,7 @@
         mockInputMethodService.setInputConnection(new MockConnection("word wo", "rd", et));
         et.startOffset = 0;
         et.selectionStart = 7;
-        Range r;
+        TextRange r;
 
         ic.beginBatchEdit();
         // basic case
@@ -241,7 +241,7 @@
         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
                 10 /* start */, 16 /* end */, 0 /* flags */);
         mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
-        Range r;
+        TextRange r;
         SuggestionSpan[] suggestions;
 
         r = ic.getWordRangeAtCursor(" ", 0);