Merge "Introduce a utility method to test IME APIs."
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
index 55b5c75..7c5259a 100644
--- a/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/tests/view/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -35,6 +35,7 @@
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.util.InputConnectionTestUtils;
 import android.widget.EditText;
 
 public class BaseInputConnectionTest extends
@@ -261,14 +262,13 @@
      * an initial text and selection range.
      * @param view the {@link View} to be associated with the {@link BaseInputConnection}.
      * @param source the initial text.
-     * @param selectionStart the initial selection start index.
-     * @param selectionEnd the initial selection end index.
      * @return {@link BaseInputConnection} instantiated in dummy mode with {@code source} and
      * selection range from {@code selectionStart} to {@code selectionEnd}
      */
     private static BaseInputConnection createDummyConnectionWithSelection(
-            final View view, final CharSequence source, final int selectionStart,
-            final int selectionEnd) {
+            final View view, final CharSequence source) {
+        final int selectionStart = Selection.getSelectionStart(source);
+        final int selectionEnd = Selection.getSelectionEnd(source);
         final Editable editable = Editable.Factory.getInstance().newEditable(source);
         Selection.setSelection(editable, selectionStart, selectionEnd);
         return new BaseInputConnection(view, false) {
@@ -279,216 +279,80 @@
         };
     }
 
+    private void testDeleteSurroundingTextMain(final String initialState,
+            final int deleteBefore, final int deleteAfter, final String expectedState) {
+        final CharSequence source = InputConnectionTestUtils.formatString(initialState);
+        final BaseInputConnection ic = createDummyConnectionWithSelection(mView, source);
+        ic.deleteSurroundingText(deleteBefore, deleteAfter);
+
+        final CharSequence expectedString = InputConnectionTestUtils.formatString(expectedState);
+        final int expectedSelectionStart = Selection.getSelectionStart(expectedString);
+        final int expectedSelectionEnd = Selection.getSelectionEnd(expectedString);
+
+        // It is sufficient to check the surrounding text up to source.length() characters, because
+        // InputConnection.deleteSurroundingText() is not supposed to increase the text length.
+        final int retrievalLength = source.length();
+        if (expectedSelectionStart == 0) {
+            assertTrue(TextUtils.isEmpty(ic.getTextBeforeCursor(retrievalLength, 0)));
+        } else {
+            assertEquals(expectedString.subSequence(0, expectedSelectionStart).toString(),
+                    ic.getTextBeforeCursor(retrievalLength, 0).toString());
+        }
+        if (expectedSelectionStart == expectedSelectionEnd) {
+            assertTrue(TextUtils.isEmpty(ic.getSelectedText(0)));  // null is allowed.
+        } else {
+            assertEquals(expectedString.subSequence(expectedSelectionStart,
+                    expectedSelectionEnd).toString(), ic.getSelectedText(0).toString());
+        }
+        if (expectedSelectionEnd == expectedString.length()) {
+            assertTrue(TextUtils.isEmpty(ic.getTextAfterCursor(retrievalLength, 0)));
+        } else {
+            assertEquals(expectedString.subSequence(expectedSelectionEnd,
+                    expectedString.length()).toString(),
+                    ic.getTextAfterCursor(retrievalLength, 0).toString());
+        }
+    }
+
     /**
      * Tests {@link BaseInputConnection#deleteSurroundingText(int, int)} comprehensively.
      */
     public void testDeleteSurroundingText() throws Throwable {
-        // For text "012[]3456789", calling deleteSurroundingText(0, 0) must produce "012[]3456789",
-        // where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 3, 3);
+        testDeleteSurroundingTextMain("012[]3456789", 0, 0, "012[]3456789");
+        testDeleteSurroundingTextMain("012[]3456789", -1, -1, "012[]3456789");
+        testDeleteSurroundingTextMain("012[]3456789", 1, 2, "01[]56789");
+        testDeleteSurroundingTextMain("012[]3456789", 10, 1, "[]456789");
+        testDeleteSurroundingTextMain("012[]3456789", 1, 10, "01[]");
+        testDeleteSurroundingTextMain("[]0123456789", 3, 3, "[]3456789");
+        testDeleteSurroundingTextMain("0123456789[]", 3, 3, "0123456[]");
+        testDeleteSurroundingTextMain("012[345]6789", 0, 0, "012[345]6789");
+        testDeleteSurroundingTextMain("012[345]6789", -1, -1, "012[345]6789");
+        testDeleteSurroundingTextMain("012[345]6789", 1, 2, "01[345]89");
+        testDeleteSurroundingTextMain("012[345]6789", 10, 1, "[345]789");
+        testDeleteSurroundingTextMain("012[345]6789", 1, 10, "01[345]");
+        testDeleteSurroundingTextMain("[012]3456789", 3, 3, "[012]6789");
+        testDeleteSurroundingTextMain("0123456[789]", 3, 3, "0123[789]");
+        testDeleteSurroundingTextMain("[0123456789]", 0, 0, "[0123456789]");
+        testDeleteSurroundingTextMain("[0123456789]", 1, 1, "[0123456789]");
 
-            dummyConnection.deleteSurroundingText(0, 0);
-            assertEquals("012", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertTrue(TextUtils.isEmpty(dummyConnection.getSelectedText(0)));  // null is allowed.
-            assertEquals("3456789", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0123456789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "012[]3456789", calling deleteSurroundingText(-1, -1) must produce
-        // "012[]3456789", where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 3, 3);
-
-            dummyConnection.deleteSurroundingText(-1, -1);
-            assertEquals("012", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertTrue(TextUtils.isEmpty(dummyConnection.getSelectedText(0)));  // null is allowed.
-            assertEquals("3456789", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0123456789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "012[]3456789", calling deleteSurroundingText(1, 2) must produce
-        // "01[]56789", where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 3, 3);
-
-            dummyConnection.deleteSurroundingText(1, 2);
-            assertEquals("01", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertTrue(TextUtils.isEmpty(dummyConnection.getSelectedText(0)));  // null is allowed.
-            assertEquals("56789", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0156789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "012[]3456789", calling deleteSurroundingText(10, 1) must produce "[]456789",
-        // where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 3, 3);
-
-            assertTrue(dummyConnection.deleteSurroundingText(10, 1));
-            assertEquals("", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertTrue(TextUtils.isEmpty(dummyConnection.getSelectedText(0)));  // null is allowed.
-            assertEquals("456789", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("456789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "012[]3456789", calling deleteSurroundingText(1, 10) must produce "01[]",
-        // where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 3, 3);
-
-            assertTrue(dummyConnection.deleteSurroundingText(1, 10));
-            assertEquals("01", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertTrue(TextUtils.isEmpty(dummyConnection.getSelectedText(0)));  // null is allowed.
-            assertEquals("", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("01", dummyConnection.getEditable().toString());
-        }
-
-        // For text "[]0123456789", calling deleteSurroundingText(3, 3) must produce "[]3456789",
-        // where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 0, 0);
-
-            assertTrue(dummyConnection.deleteSurroundingText(3, 3));
-            assertEquals("", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertTrue(TextUtils.isEmpty(dummyConnection.getSelectedText(0)));  // null is allowed.
-            assertEquals("3456789", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("3456789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "0123456789[]", calling deleteSurroundingText(3, 3) must produce "0123456[]",
-        // where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 10, 10);
-
-            assertTrue(dummyConnection.deleteSurroundingText(3, 3));
-            assertEquals("0123456", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertTrue(TextUtils.isEmpty(dummyConnection.getSelectedText(0)));  // null is allowed.
-            assertEquals("", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0123456", dummyConnection.getEditable().toString());
-        }
-
-        // For text "012[345]6789", calling deleteSurroundingText(0, 0) must produce "012[345]6789",
-        // where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 3, 6);
-
-            dummyConnection.deleteSurroundingText(0, 0);
-            assertEquals("012", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertEquals("345", dummyConnection.getSelectedText(0).toString());
-            assertEquals("6789", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0123456789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "012[345]6789", calling deleteSurroundingText(-1, -1) must produce
-        // "012[345]6789", where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 3, 6);
-
-            dummyConnection.deleteSurroundingText(-1, -1);
-            assertEquals("012", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertEquals("345", dummyConnection.getSelectedText(0).toString());
-            assertEquals("6789", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0123456789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "012[345]6789", calling deleteSurroundingText(1, 2) must produce
-        // "01[345]89", where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 3, 6);
-
-            dummyConnection.deleteSurroundingText(1, 2);
-            assertEquals("01", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertEquals("345", dummyConnection.getSelectedText(0).toString());
-            assertEquals("89", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0134589", dummyConnection.getEditable().toString());
-        }
-
-        // For text "012[345]6789", calling deleteSurroundingText(10, 1) must produce
-        // "[345]789", where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 3, 6);
-
-            dummyConnection.deleteSurroundingText(10, 1);
-            assertEquals("", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertEquals("345", dummyConnection.getSelectedText(0).toString());
-            assertEquals("789", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("345789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "012[345]6789", calling deleteSurroundingText(1, 10) must produce
-        // "[345]789", where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 3, 6);
-
-            dummyConnection.deleteSurroundingText(1, 10);
-            assertEquals("01", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertEquals("345", dummyConnection.getSelectedText(0).toString());
-            assertEquals("", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("01345", dummyConnection.getEditable().toString());
-        }
-
-        // For text "[012]3456789", calling deleteSurroundingText(3, 3) must produce "[012]6789",
-        // where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 0, 3);
-
-            assertTrue(dummyConnection.deleteSurroundingText(3, 3));
-            assertEquals("", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertEquals("012", dummyConnection.getSelectedText(0).toString());
-            assertEquals("6789", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0126789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "0123456[789]", calling deleteSurroundingText(3, 3) must produce "0123[789]",
-        // where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 7, 10);
-
-            assertTrue(dummyConnection.deleteSurroundingText(3, 3));
-            assertEquals("0123", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertEquals("789", dummyConnection.getSelectedText(0).toString());
-            assertEquals("", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0123789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "[0123456789]", calling deleteSurroundingText(0, 0) must produce "[0123456789]",
-        // where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 0, 10);
-
-            assertTrue(dummyConnection.deleteSurroundingText(0, 0));
-            assertEquals("", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertEquals("0123456789", dummyConnection.getSelectedText(0).toString());
-            assertEquals("", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0123456789", dummyConnection.getEditable().toString());
-        }
-
-        // For text "[0123456789]", calling deleteSurroundingText(1, 1) must produce "[0123456789]",
-        // where '[' and ']' indicate the text selection range.
-        {
-            final BaseInputConnection dummyConnection = createDummyConnectionWithSelection(
-                    mView, "0123456789", 0, 10);
-
-            assertTrue(dummyConnection.deleteSurroundingText(1, 1));
-            assertEquals("", dummyConnection.getTextBeforeCursor(10, 0).toString());
-            assertEquals("0123456789", dummyConnection.getSelectedText(0).toString());
-            assertEquals("", dummyConnection.getTextAfterCursor(10, 0).toString());
-            assertEquals("0123456789", dummyConnection.getEditable().toString());
-        }
+        // Surrogate characters do not have any special meanings.  Validating the character sequence
+        // is beyond the goal of this API.
+        testDeleteSurroundingTextMain("0<>[]3456789", 1, 0, "0<[]3456789");
+        testDeleteSurroundingTextMain("0<>[]3456789", 2, 0, "0[]3456789");
+        testDeleteSurroundingTextMain("0<>[]3456789", 3, 0, "[]3456789");
+        testDeleteSurroundingTextMain("012[]<>56789", 0, 1, "012[]>56789");
+        testDeleteSurroundingTextMain("012[]<>56789", 0, 2, "012[]56789");
+        testDeleteSurroundingTextMain("012[]<>56789", 0, 3, "012[]6789");
+        testDeleteSurroundingTextMain("0<<[]3456789", 1, 0, "0<[]3456789");
+        testDeleteSurroundingTextMain("0<<[]3456789", 2, 0, "0[]3456789");
+        testDeleteSurroundingTextMain("0<<[]3456789", 3, 0, "[]3456789");
+        testDeleteSurroundingTextMain("012[]<<56789", 0, 1, "012[]<56789");
+        testDeleteSurroundingTextMain("012[]<<56789", 0, 2, "012[]56789");
+        testDeleteSurroundingTextMain("012[]<<56789", 0, 3, "012[]6789");
+        testDeleteSurroundingTextMain("0>>[]3456789", 1, 0, "0>[]3456789");
+        testDeleteSurroundingTextMain("0>>[]3456789", 2, 0, "0[]3456789");
+        testDeleteSurroundingTextMain("0>>[]3456789", 3, 0, "[]3456789");
+        testDeleteSurroundingTextMain("012[]>>56789", 0, 1, "012[]>56789");
+        testDeleteSurroundingTextMain("012[]>>56789", 0, 2, "012[]56789");
+        testDeleteSurroundingTextMain("012[]>>56789", 0, 3, "012[]6789");
     }
 }
diff --git a/tests/tests/view/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java b/tests/tests/view/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java
new file mode 100644
index 0000000..3735c33
--- /dev/null
+++ b/tests/tests/view/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 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.view.inputmethod.cts.util;
+
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+
+public final class InputConnectionTestUtils {
+
+    /**
+     * A utility function to generate test string for input method APIs.  There are several
+     * pre-defined meta characters that are useful for unit tests.
+     *
+     * <p>Pre-defined meta characters:</p>
+     * <dl>
+     *     <dl>{@code [}</dl><dd>The text selection starts from here.</dd>
+     *     <dl>{@code ]}</dl><dd>The text selection ends at here.</dd>
+     *     <dl>{@code <}</dl><dd>Represents a high surrogate character.</dd>
+     *     <dl>{@code >}</dl><dd>Represents a low surrogate character.</dd>
+     * </ul>
+     *
+     * <p>Examples: {@code "012[3<>67]89"} will be converted to {@ode "0123HL6789"}, where
+     * {@code "H"} and {@code "L"} indicate certain high and low surrogate characters, respectively,
+     * with selecting {@code "3HL67"}.</p>
+     *
+     * @param formatString
+     * @return A {@link CharSequence} object with text selection specified by the meta characters.
+     */
+    public static CharSequence formatString(final String formatString) {
+        final String U1F427 = "\uD83D\uDC27";
+        final SpannableStringBuilder builder = new SpannableStringBuilder();
+        int selectionStart = -1;
+        int selectionEnd = -1;
+        for (int i = 0; i < formatString.length(); ++i) {
+            final Character c = formatString.charAt(i);
+            switch (c) {
+                case '[':
+                    selectionStart = builder.length();
+                    break;
+                case ']':
+                    selectionEnd = builder.length();
+                    break;
+                case '<':
+                    builder.append(U1F427.charAt(0));  // High surrogate
+                    break;
+                case '>':
+                    builder.append(U1F427.charAt(1));  // Low surrogate
+                    break;
+                default:
+                    builder.append(c);
+                    break;
+            }
+        }
+        if (selectionStart < 0) {
+            throw new UnsupportedOperationException("Selection marker '[' must be specified.");
+        }
+        if (selectionEnd < 0) {
+            throw new UnsupportedOperationException("Selection marker ']' must be specified.");
+        }
+        Selection.setSelection(builder, selectionStart, selectionEnd);
+        return builder;
+    }
+}