Create new module CtsInputMethodTestCases
This CL copies CTS tests related to Input Method and Input Method
Framework out from CtsViewTestCases module into its own module. After
a continuous CTS test for this new module is set up and running, the
duplicated test in CtsViewTestCases should be removed (Bug 34648531).
Bug: 7542467
Test: Manually "run cts --module CtsInputMethodTestCases" and verify
all tests are passed.
Change-Id: I86881c0145f510b2aa9424e2b90823ca517d2989
diff --git a/tests/inputmethod/Android.mk b/tests/inputmethod/Android.mk
new file mode 100644
index 0000000..7de22d4
--- /dev/null
+++ b/tests/inputmethod/Android.mk
@@ -0,0 +1,42 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_MULTILIB := both
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ compatibility-device-util \
+ ctstestrunner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsInputMethodTestCases
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
new file mode 100644
index 0000000..11f008d
--- /dev/null
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.view.inputmethod.cts">
+
+ <application
+ android:label="CtsInputMethodTestCases"
+ android:multiArch="true"
+ android:supportsRtl="true">
+
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name="android.view.inputmethod.cts.InputMethodCtsActivity"
+ android:label="InputMethodCtsActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests of android.view.inputmethod"
+ android:targetPackage="android.view.inputmethod.cts">
+ <meta-data
+ android:name="listener"
+ android:value="com.android.cts.runner.CtsTestRunListener" />
+ </instrumentation>
+
+</manifest>
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
new file mode 100644
index 0000000..ed1e6be
--- /dev/null
+++ b/tests/inputmethod/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+
+<configuration description="Config for CTS InputMethod test cases">
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsInputMethodTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.view.inputmethod.cts" />
+ <option name="runtime-hint" value="1m0s" />
+ </test>
+</configuration>
diff --git a/tests/inputmethod/res/layout/inputmethod_edittext.xml b/tests/inputmethod/res/layout/inputmethod_edittext.xml
new file mode 100644
index 0000000..a8f442e
--- /dev/null
+++ b/tests/inputmethod/res/layout/inputmethod_edittext.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/blue"
+ android:padding="10px">
+
+ <EditText
+ android:id="@+id/entry"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:drawable/editbox_background"/>
+
+</RelativeLayout>
diff --git a/tests/inputmethod/res/values/colors.xml b/tests/inputmethod/res/values/colors.xml
new file mode 100644
index 0000000..1d87ea8
--- /dev/null
+++ b/tests/inputmethod/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 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.
+ -->
+
+<resources>
+ <drawable name="blue">#770000ff</drawable>
+</resources>
diff --git a/tests/inputmethod/res/xml/keyboard.xml b/tests/inputmethod/res/xml/keyboard.xml
new file mode 100644
index 0000000..af8b23b
--- /dev/null
+++ b/tests/inputmethod/res/xml/keyboard.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017, 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.
+-->
+
+<Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
+ android:keyWidth="10%p"
+ android:horizontalGap="0px"
+ android:verticalGap="0px"
+ android:keyHeight="10px"
+ >
+
+ <Row>
+ <Key android:codes="-1" android:keyLabel="Sticky!"
+ android:isModifier="true" android:isSticky="true" />
+ <Key android:codes="120" android:keyLabel="x" />
+ </Row>
+</Keyboard>
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
new file mode 100644
index 0000000..8089739
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2008 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.Editable;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.R;
+import android.view.inputmethod.cts.util.InputConnectionTestUtils;
+import android.widget.EditText;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BaseInputConnectionTest {
+ private Instrumentation mInstrumentation;
+ private InputMethodCtsActivity mActivity;
+ private Window mWindow;
+ private EditText mView;
+ private BaseInputConnection mConnection;
+
+ @Rule
+ public ActivityTestRule<InputMethodCtsActivity> mActivityRule =
+ new ActivityTestRule<>(InputMethodCtsActivity.class);
+
+ @Before
+ public void setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+ PollingCheck.waitFor(mActivity::hasWindowFocus);
+ mWindow = mActivity.getWindow();
+ mView = (EditText) mWindow.findViewById(R.id.entry);
+ mConnection = new BaseInputConnection(mView, true);
+ }
+
+ @Test
+ public void testDefaultMethods() {
+ // These methods are default to return fixed result.
+
+ assertFalse(mConnection.beginBatchEdit());
+ assertFalse(mConnection.endBatchEdit());
+
+ // only fit for test default implementation of commitCompletion.
+ int completionId = 1;
+ String completionString = "commitCompletion test";
+ assertFalse(mConnection.commitCompletion(new CompletionInfo(completionId,
+ 0, completionString)));
+
+ assertNull(mConnection.getExtractedText(new ExtractedTextRequest(), 0));
+
+ // only fit for test default implementation of performEditorAction.
+ int actionCode = 1;
+ int actionId = 2;
+ String action = "android.intent.action.MAIN";
+ assertTrue(mConnection.performEditorAction(actionCode));
+ assertFalse(mConnection.performContextMenuAction(actionId));
+ assertFalse(mConnection.performPrivateCommand(action, new Bundle()));
+ }
+
+ @Test
+ public void testOpComposingSpans() {
+ Spannable text = new SpannableString("Test ComposingSpans");
+ BaseInputConnection.setComposingSpans(text);
+ assertTrue(BaseInputConnection.getComposingSpanStart(text) > -1);
+ assertTrue(BaseInputConnection.getComposingSpanEnd(text) > -1);
+ BaseInputConnection.removeComposingSpans(text);
+ assertTrue(BaseInputConnection.getComposingSpanStart(text) == -1);
+ assertTrue(BaseInputConnection.getComposingSpanEnd(text) == -1);
+ }
+
+ /**
+ * getEditable: Return the target of edit operations. The default implementation
+ * returns its own fake editable that is just used for composing text.
+ * clearMetaKeyStates: Default implementation uses
+ * MetaKeyKeyListener#clearMetaKeyState(long, int) to clear the state.
+ * BugId:1738511
+ * commitText: 1. Default implementation replaces any existing composing text with the given
+ * text.
+ * 2. In addition, only if dummy mode, a key event is sent for the new text and the
+ * current editable buffer cleared.
+ * deleteSurroundingText: The default implementation performs the deletion around the current
+ * selection position of the editable text.
+ * getCursorCapsMode: 1. The default implementation uses TextUtils.getCapsMode to get the
+ * cursor caps mode for the current selection position in the editable text.
+ * TextUtils.getCapsMode is tested fully in TextUtilsTest#testGetCapsMode.
+ * 2. In dummy mode in which case 0 is always returned.
+ * getTextBeforeCursor, getTextAfterCursor: The default implementation performs the deletion
+ * around the current selection position of the editable text.
+ * setSelection: changes the selection position in the current editable text.
+ */
+ @Test
+ public void testOpTextMethods() throws Throwable {
+ // return is an default Editable instance with empty source
+ final Editable text = mConnection.getEditable();
+ assertNotNull(text);
+ assertEquals(0, text.length());
+
+ // Test commitText, not dummy mode
+ CharSequence str = "TestCommit ";
+ Editable inputText = Editable.Factory.getInstance().newEditable(str);
+ mConnection.commitText(inputText, inputText.length());
+ final Editable text2 = mConnection.getEditable();
+ int strLength = str.length();
+ assertEquals(strLength, text2.length());
+ assertEquals(str.toString(), text2.toString());
+ assertEquals(TextUtils.CAP_MODE_WORDS,
+ mConnection.getCursorCapsMode(TextUtils.CAP_MODE_WORDS));
+ int offLength = 3;
+ CharSequence expected = str.subSequence(strLength - offLength, strLength);
+ assertEquals(expected.toString(), mConnection.getTextBeforeCursor(offLength,
+ BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
+ mConnection.setSelection(0, 0);
+ expected = str.subSequence(0, offLength);
+ assertEquals(expected.toString(), mConnection.getTextAfterCursor(offLength,
+ BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
+
+ mActivityRule.runOnUiThread(() -> {
+ assertTrue(mView.requestFocus());
+ assertTrue(mView.isFocused());
+ });
+
+ // dummy mode
+ BaseInputConnection dummyConnection = new BaseInputConnection(mView, false);
+ dummyConnection.commitText(inputText, inputText.length());
+ PollingCheck.waitFor(() -> text2.toString().equals(mView.getText().toString()));
+ assertEquals(0, dummyConnection.getCursorCapsMode(TextUtils.CAP_MODE_WORDS));
+
+ // Test deleteSurroundingText
+ int end = text2.length();
+ mConnection.setSelection(end, end);
+ // Delete the ending space
+ assertTrue(mConnection.deleteSurroundingText(1, 2));
+ Editable text3 = mConnection.getEditable();
+ assertEquals(strLength - 1, text3.length());
+ String expectedDelString = "TestCommit";
+ assertEquals(expectedDelString, text3.toString());
+ }
+
+ /**
+ * finishComposingText: 1. The default implementation removes the composing state from the
+ * current editable text.
+ * 2. In addition, only if dummy mode, a key event is sent for the new
+ * text and the current editable buffer cleared.
+ * setComposingText: The default implementation places the given text into the editable,
+ * replacing any existing composing text
+ */
+ @Test
+ public void testFinishComposingText() throws Throwable {
+ CharSequence str = "TestFinish";
+ Editable inputText = Editable.Factory.getInstance().newEditable(str);
+ mConnection.commitText(inputText, inputText.length());
+ final Editable text = mConnection.getEditable();
+ // Test finishComposingText, not dummy mode
+ BaseInputConnection.setComposingSpans(text);
+ assertTrue(BaseInputConnection.getComposingSpanStart(text) > -1);
+ assertTrue(BaseInputConnection.getComposingSpanEnd(text) > -1);
+ mConnection.finishComposingText();
+ assertTrue(BaseInputConnection.getComposingSpanStart(text) == -1);
+ assertTrue(BaseInputConnection.getComposingSpanEnd(text) == -1);
+
+ mActivityRule.runOnUiThread(() -> {
+ assertTrue(mView.requestFocus());
+ assertTrue(mView.isFocused());
+ });
+
+ // dummy mode
+ BaseInputConnection dummyConnection = new BaseInputConnection(mView, false);
+ dummyConnection.setComposingText(str, str.length());
+ dummyConnection.finishComposingText();
+ PollingCheck.waitFor(() -> text.toString().equals(mView.getText().toString()));
+ }
+
+ /**
+ * Provides standard implementation for sending a key event to the window
+ * attached to the input connection's view
+ */
+ @Test
+ public void testSendKeyEvent() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ assertTrue(mView.requestFocus());
+ assertTrue(mView.isFocused());
+ });
+
+ // 12-key support
+ KeyCharacterMap keymap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ if (keymap.getKeyboardType() == KeyCharacterMap.NUMERIC) {
+ // 'Q' in case of 12-key(NUMERIC) keyboard
+ mConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_7));
+ mConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_7));
+ } else {
+ mInstrumentation.sendStringSync("q");
+ mInstrumentation.waitForIdleSync();
+ }
+ PollingCheck.waitFor(() -> "q".equals(mView.getText().toString()));
+ }
+
+ /**
+ * Updates InputMethodManager with the current fullscreen mode.
+ */
+ @Test
+ public void testReportFullscreenMode() {
+ InputMethodManager imManager = (InputMethodManager) mInstrumentation.getTargetContext()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ mConnection.reportFullscreenMode(false);
+ assertFalse(imManager.isFullscreenMode());
+ mConnection.reportFullscreenMode(true);
+ // Only IMEs are allowed to report full-screen mode. Calling this method from the
+ // application should have no effect.
+ assertFalse(imManager.isFullscreenMode());
+ }
+
+ /**
+ * An utility method to create an instance of {@link BaseInputConnection} in dummy mode with
+ * an initial text and selection range.
+ * @param view the {@link View} to be associated with the {@link BaseInputConnection}.
+ * @param source the initial text.
+ * @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 = 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) {
+ @Override
+ public Editable getEditable() {
+ return editable;
+ }
+ };
+ }
+
+ private void verifyDeleteSurroundingTextMain(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.
+ */
+ @Test
+ public void testDeleteSurroundingText() throws Throwable {
+ verifyDeleteSurroundingTextMain("012[]3456789", 0, 0, "012[]3456789");
+ verifyDeleteSurroundingTextMain("012[]3456789", -1, -1, "012[]3456789");
+ verifyDeleteSurroundingTextMain("012[]3456789", 1, 2, "01[]56789");
+ verifyDeleteSurroundingTextMain("012[]3456789", 10, 1, "[]456789");
+ verifyDeleteSurroundingTextMain("012[]3456789", 1, 10, "01[]");
+ verifyDeleteSurroundingTextMain("[]0123456789", 3, 3, "[]3456789");
+ verifyDeleteSurroundingTextMain("0123456789[]", 3, 3, "0123456[]");
+ verifyDeleteSurroundingTextMain("012[345]6789", 0, 0, "012[345]6789");
+ verifyDeleteSurroundingTextMain("012[345]6789", -1, -1, "012[345]6789");
+ verifyDeleteSurroundingTextMain("012[345]6789", 1, 2, "01[345]89");
+ verifyDeleteSurroundingTextMain("012[345]6789", 10, 1, "[345]789");
+ verifyDeleteSurroundingTextMain("012[345]6789", 1, 10, "01[345]");
+ verifyDeleteSurroundingTextMain("[012]3456789", 3, 3, "[012]6789");
+ verifyDeleteSurroundingTextMain("0123456[789]", 3, 3, "0123[789]");
+ verifyDeleteSurroundingTextMain("[0123456789]", 0, 0, "[0123456789]");
+ verifyDeleteSurroundingTextMain("[0123456789]", 1, 1, "[0123456789]");
+
+ // Surrogate characters do not have any special meanings. Validating the character sequence
+ // is beyond the goal of this API.
+ verifyDeleteSurroundingTextMain("0<>[]3456789", 1, 0, "0<[]3456789");
+ verifyDeleteSurroundingTextMain("0<>[]3456789", 2, 0, "0[]3456789");
+ verifyDeleteSurroundingTextMain("0<>[]3456789", 3, 0, "[]3456789");
+ verifyDeleteSurroundingTextMain("012[]<>56789", 0, 1, "012[]>56789");
+ verifyDeleteSurroundingTextMain("012[]<>56789", 0, 2, "012[]56789");
+ verifyDeleteSurroundingTextMain("012[]<>56789", 0, 3, "012[]6789");
+ verifyDeleteSurroundingTextMain("0<<[]3456789", 1, 0, "0<[]3456789");
+ verifyDeleteSurroundingTextMain("0<<[]3456789", 2, 0, "0[]3456789");
+ verifyDeleteSurroundingTextMain("0<<[]3456789", 3, 0, "[]3456789");
+ verifyDeleteSurroundingTextMain("012[]<<56789", 0, 1, "012[]<56789");
+ verifyDeleteSurroundingTextMain("012[]<<56789", 0, 2, "012[]56789");
+ verifyDeleteSurroundingTextMain("012[]<<56789", 0, 3, "012[]6789");
+ verifyDeleteSurroundingTextMain("0>>[]3456789", 1, 0, "0>[]3456789");
+ verifyDeleteSurroundingTextMain("0>>[]3456789", 2, 0, "0[]3456789");
+ verifyDeleteSurroundingTextMain("0>>[]3456789", 3, 0, "[]3456789");
+ verifyDeleteSurroundingTextMain("012[]>>56789", 0, 1, "012[]>56789");
+ verifyDeleteSurroundingTextMain("012[]>>56789", 0, 2, "012[]56789");
+ verifyDeleteSurroundingTextMain("012[]>>56789", 0, 3, "012[]6789");
+ }
+
+ private void verifyDeleteSurroundingTextInCodePointsMain(final String initialState,
+ final int deleteBeforeInCodePoints, final int deleteAfterInCodePoints,
+ final String expectedState) {
+ final CharSequence source = InputConnectionTestUtils.formatString(initialState);
+ final BaseInputConnection ic = createDummyConnectionWithSelection(mView, source);
+ ic.deleteSurroundingTextInCodePoints(deleteBeforeInCodePoints, deleteAfterInCodePoints);
+
+ 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.deleteSurroundingTextInCodePoints() 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#deleteSurroundingTextInCodePoints(int, int)}
+ * comprehensively.
+ */
+ @Test
+ public void testDeleteSurroundingTextInCodePoints() throws Throwable {
+ verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 0, 0, "012[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", -1, -1, "012[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 1, 2, "01[]56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 10, 1, "[]456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 1, 10, "01[]");
+ verifyDeleteSurroundingTextInCodePointsMain("[]0123456789", 3, 3, "[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0123456789[]", 3, 3, "0123456[]");
+ verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 0, 0, "012[345]6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", -1, -1, "012[345]6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 1, 2, "01[345]89");
+ verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 10, 1, "[345]789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 1, 10, "01[345]");
+ verifyDeleteSurroundingTextInCodePointsMain("[012]3456789", 3, 3, "[012]6789");
+ verifyDeleteSurroundingTextInCodePointsMain("0123456[789]", 3, 3, "0123[789]");
+ verifyDeleteSurroundingTextInCodePointsMain("[0123456789]", 0, 0, "[0123456789]");
+ verifyDeleteSurroundingTextInCodePointsMain("[0123456789]", 1, 1, "[0123456789]");
+
+ verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 1, 0, "0[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 2, 0, "[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 3, 0, "[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 1, "012[]56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 2, "012[]6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 3, "012[]789");
+
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 0, "[]<><><><><>");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 1, "[]<><><><>");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 2, "[]<><><>");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 3, "[]<><>");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 4, "[]<>");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 5, "[]");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 6, "[]");
+ verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 1000, "[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 0, 0, "<><><><><>[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 1, 0, "<><><><>[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 2, 0, "<><><>[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 3, 0, "<><>[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 4, 0, "<>[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 5, 0, "[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 6, 0, "[]");
+ verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 1000, 0, "[]");
+
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 1, 0, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 2, 0, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 3, 0, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 1, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 2, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 3, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 1, 0, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 2, 0, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 3, 0, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 1, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 2, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 3, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 1, 0, "01<[]>456789");
+ verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 0, 1, "01<[]>456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 1, 0, "<1[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 2, 0, "<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 3, 0, "<12[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 1, 0, "<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 2, 0, "<<>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 3, 0, "<<>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 1, "012[]4>6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 2, "012[]>6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 3, "012[]34>6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 1, "012[]>6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 2, "012[]<>>6789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 3, "012[]<>>6789");
+
+ // Atomicity test.
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 1, 1, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 2, 1, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 3, 1, "0<<[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 1, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 2, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 3, "012[]<<56789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 1, 1, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 2, 1, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 3, 1, "0>>[]3456789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 1, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 2, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 3, "012[]>>56789");
+ verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 1, 1, "01<[]>456789");
+
+ // Do not verify the character sequences in the selected region.
+ verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 1, 0, "0[><]456789");
+ verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 0, 1, "01[><]56789");
+ verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 1, 1, "0[><]56789");
+ }
+
+ @Test
+ public void testCloseConnection() {
+ final CharSequence source = "0123456789";
+ mConnection.commitText(source, source.length());
+ final Editable text = mConnection.getEditable();
+ BaseInputConnection.setComposingSpans(text, 2, 5);
+ assertEquals(2, BaseInputConnection.getComposingSpanStart(text));
+ assertEquals(5, BaseInputConnection.getComposingSpanEnd(text));
+
+ // BaseInputConnection#closeConnection() must clear the on-going composition.
+ mConnection.closeConnection();
+ assertEquals(-1, BaseInputConnection.getComposingSpanStart(text));
+ assertEquals(-1, BaseInputConnection.getComposingSpanEnd(text));
+ }
+
+ @Test
+ public void testGetHandler() {
+ // BaseInputConnection must not implement getHandler().
+ assertNull(mConnection.getHandler());
+ }
+
+ @Test
+ public void testCommitContent() {
+ final InputContentInfo inputContentInfo = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ // The default implementation should do nothing and just return false.
+ assertFalse(mConnection.commitContent(inputContentInfo, 0 /* flags */, null /* opts */));
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/CompletionInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/CompletionInfoTest.java
new file mode 100644
index 0000000..9a8d206
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/CompletionInfoTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2009 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.CompletionInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CompletionInfoTest {
+ private static final int ID = 1;
+ private static final int POSITION = 1;
+ private static final String TEXT = "CompletionInfoText";
+ private static final String LABEL = "CompletionInfoLabel";
+
+ @Test
+ public void testCompletionInfo() {
+ new CompletionInfo(ID, POSITION, TEXT);
+ CompletionInfo info = new CompletionInfo(ID, POSITION, TEXT, LABEL);
+ assertCompletionInfo(info);
+
+ assertEquals(0, info.describeContents());
+ assertNotNull(info.toString());
+
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ CompletionInfo targetInfo = CompletionInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+ assertCompletionInfo(targetInfo);
+ }
+
+ private void assertCompletionInfo(CompletionInfo info) {
+ assertEquals(ID, info.getId());
+ assertEquals(POSITION, info.getPosition());
+ assertEquals(TEXT, info.getText().toString());
+ assertEquals(LABEL, info.getLabel().toString());
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
new file mode 100644
index 0000000..1557511
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2009 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.MoreAsserts;
+import android.text.TextUtils;
+import android.util.Printer;
+import android.view.inputmethod.EditorInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class EditorInfoTest {
+ @Test
+ public void testEditorInfo() {
+ EditorInfo info = new EditorInfo();
+
+ info.actionId = 1;
+ info.actionLabel = "actionLabel";
+ info.fieldId = 2;
+ info.fieldName = "fieldName";
+ info.hintText = "hintText";
+ info.imeOptions = EditorInfo.IME_FLAG_NO_ENTER_ACTION;
+ info.initialCapsMode = TextUtils.CAP_MODE_CHARACTERS;
+ info.initialSelEnd = 10;
+ info.initialSelStart = 0;
+ info.inputType = EditorInfo.TYPE_MASK_CLASS;
+ info.label = "label";
+ info.packageName = "android.view.cts";
+ info.privateImeOptions = "privateIme";
+ Bundle b = new Bundle();
+ String key = "bundleKey";
+ String value = "bundleValue";
+ b.putString(key, value);
+ info.extras = b;
+ info.hintLocales = LocaleList.forLanguageTags("en-PH,en-US");
+ info.contentMimeTypes = new String[]{"image/gif", "image/png"};
+
+ assertEquals(0, info.describeContents());
+
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ EditorInfo targetInfo = EditorInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+ assertEquals(info.actionId, targetInfo.actionId);
+ assertEquals(info.fieldId, targetInfo.fieldId);
+ assertEquals(info.fieldName, targetInfo.fieldName);
+ assertEquals(info.imeOptions, targetInfo.imeOptions);
+ assertEquals(info.initialCapsMode, targetInfo.initialCapsMode);
+ assertEquals(info.initialSelEnd, targetInfo.initialSelEnd);
+ assertEquals(info.initialSelStart, targetInfo.initialSelStart);
+ assertEquals(info.inputType, targetInfo.inputType);
+ assertEquals(info.packageName, targetInfo.packageName);
+ assertEquals(info.privateImeOptions, targetInfo.privateImeOptions);
+ assertEquals(info.hintText.toString(), targetInfo.hintText.toString());
+ assertEquals(info.actionLabel.toString(), targetInfo.actionLabel.toString());
+ assertEquals(info.label.toString(), targetInfo.label.toString());
+ assertEquals(info.extras.getString(key), targetInfo.extras.getString(key));
+ assertEquals(info.hintLocales, targetInfo.hintLocales);
+ MoreAsserts.assertEquals(info.contentMimeTypes, targetInfo.contentMimeTypes);
+
+ Printer printer = mock(Printer.class);
+ String prefix = "TestEditorInfo";
+ info.dump(printer, prefix);
+ verify(printer, atLeastOnce()).println(anyString());
+ }
+
+ @Test
+ public void testNullHintLocals() {
+ EditorInfo info = new EditorInfo();
+ info.hintLocales = null;
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ EditorInfo targetInfo = EditorInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+ assertNull(targetInfo.hintLocales);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextRequestTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextRequestTest.java
new file mode 100644
index 0000000..3e12579
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextRequestTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2009 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;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.ExtractedTextRequest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExtractedTextRequestTest {
+ @Test
+ public void testExtractedTextRequest() {
+ ExtractedTextRequest request = new ExtractedTextRequest();
+ request.flags = 1;
+ request.hintMaxChars = 100;
+ request.hintMaxLines = 10;
+ request.token = 2;
+
+ assertEquals(0, request.describeContents());
+
+ Parcel p = Parcel.obtain();
+ request.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ExtractedTextRequest target = ExtractedTextRequest.CREATOR.createFromParcel(p);
+ p.recycle();
+ assertEquals(request.flags, target.flags);
+ assertEquals(request.hintMaxChars, request.hintMaxChars);
+ assertEquals(request.hintMaxLines, target.hintMaxLines);
+ assertEquals(request.token, target.token);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java
new file mode 100644
index 0000000..41e3efa
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ExtractedTextTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 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;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.ExtractedText;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExtractedTextTest {
+ @Test
+ public void testWriteToParcel() {
+ ExtractedText extractedText = new ExtractedText();
+ extractedText.flags = 1;
+ extractedText.selectionEnd = 11;
+ extractedText.selectionStart = 2;
+ extractedText.startOffset = 1;
+ CharSequence text = "test";
+ extractedText.text = text;
+ Parcel p = Parcel.obtain();
+ extractedText.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ExtractedText target = ExtractedText.CREATOR.createFromParcel(p);
+ assertEquals(extractedText.flags, target.flags);
+ assertEquals(extractedText.selectionEnd, target.selectionEnd);
+ assertEquals(extractedText.selectionStart, target.selectionStart);
+ assertEquals(extractedText.startOffset, target.startOffset);
+ assertEquals(extractedText.partialStartOffset, target.partialStartOffset);
+ assertEquals(extractedText.partialEndOffset, target.partialEndOffset);
+ assertEquals(extractedText.text.toString(), target.text.toString());
+
+ assertEquals(0, extractedText.describeContents());
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputBindingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputBindingTest.java
new file mode 100644
index 0000000..faaff3d
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputBindingTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import android.os.Binder;
+import android.os.Parcel;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.InputBinding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputBindingTest {
+ @Test
+ public void testInputBinding() {
+ View view = new View(InstrumentationRegistry.getTargetContext());
+ BaseInputConnection bic = new BaseInputConnection(view, false);
+ Binder binder = new Binder();
+ int uid = 1;
+ int pid = 2;
+ InputBinding inputBinding = new InputBinding(bic, binder, uid, pid);
+ new InputBinding(bic, inputBinding);
+ assertSame(bic, inputBinding.getConnection());
+ assertSame(binder, inputBinding.getConnectionToken());
+ assertEquals(uid, inputBinding.getUid());
+ assertEquals(pid, inputBinding.getPid());
+
+ assertNotNull(inputBinding.toString());
+ assertEquals(0, inputBinding.describeContents());
+
+ Parcel p = Parcel.obtain();
+ inputBinding.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ InputBinding target = InputBinding.CREATOR.createFromParcel(p);
+ assertEquals(uid, target.getUid());
+ assertEquals(pid, target.getPid());
+ assertSame(binder, target.getConnectionToken());
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
new file mode 100644
index 0000000..71abacc
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionWrapperTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2009 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;
+
+import static com.android.compatibility.common.util.WidgetTestUtils.sameCharSequence;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.InputContentInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputConnectionWrapperTest {
+ @Test
+ public void testInputConnectionWrapper() {
+ InputConnection inputConnection = mock(InputConnection.class);
+ doReturn(true).when(inputConnection).commitContent(any(InputContentInfo.class),
+ anyInt(), any(Bundle.class));
+ InputConnectionWrapper wrapper = new InputConnectionWrapper(null, true);
+ try {
+ wrapper.beginBatchEdit();
+ fail("Failed to throw NullPointerException!");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ wrapper.setTarget(inputConnection);
+
+ wrapper.beginBatchEdit();
+ verify(inputConnection, times(1)).beginBatchEdit();
+
+ wrapper.clearMetaKeyStates(KeyEvent.META_ALT_ON);
+ verify(inputConnection, times(1)).clearMetaKeyStates(KeyEvent.META_ALT_ON);
+
+ wrapper.commitCompletion(new CompletionInfo(1, 1, "testText"));
+ ArgumentCaptor<CompletionInfo> completionInfoCaptor =
+ ArgumentCaptor.forClass(CompletionInfo.class);
+ verify(inputConnection, times(1)).commitCompletion(completionInfoCaptor.capture());
+ assertEquals(1, completionInfoCaptor.getValue().getId());
+ assertEquals(1, completionInfoCaptor.getValue().getPosition());
+ assertEquals("testText", completionInfoCaptor.getValue().getText());
+
+ wrapper.commitCorrection(new CorrectionInfo(0, "oldText", "newText"));
+ ArgumentCaptor<CorrectionInfo> correctionInfoCaptor =
+ ArgumentCaptor.forClass(CorrectionInfo.class);
+ verify(inputConnection, times(1)).commitCorrection(correctionInfoCaptor.capture());
+ assertEquals(0, correctionInfoCaptor.getValue().getOffset());
+ assertEquals("oldText", correctionInfoCaptor.getValue().getOldText());
+ assertEquals("newText", correctionInfoCaptor.getValue().getNewText());
+
+ wrapper.commitText("Text", 1);
+ verify(inputConnection, times(1)).commitText(sameCharSequence("Text"), eq(1));
+
+ wrapper.deleteSurroundingText(10, 100);
+ verify(inputConnection, times(1)).deleteSurroundingText(10, 100);
+
+ wrapper.deleteSurroundingTextInCodePoints(10, 100);
+ verify(inputConnection, times(1)).deleteSurroundingTextInCodePoints(10, 100);
+
+ wrapper.endBatchEdit();
+ verify(inputConnection, times(1)).endBatchEdit();
+
+ wrapper.finishComposingText();
+ verify(inputConnection, times(1)).finishComposingText();
+
+ wrapper.getCursorCapsMode(TextUtils.CAP_MODE_CHARACTERS);
+ verify(inputConnection, times(1)).getCursorCapsMode(TextUtils.CAP_MODE_CHARACTERS);
+
+ wrapper.getExtractedText(new ExtractedTextRequest(), 0);
+ verify(inputConnection, times(1)).getExtractedText(any(ExtractedTextRequest.class), eq(0));
+
+ wrapper.getTextAfterCursor(5, 0);
+ verify(inputConnection, times(1)).getTextAfterCursor(5, 0);
+
+ wrapper.getTextBeforeCursor(3, 0);
+ verify(inputConnection, times(1)).getTextBeforeCursor(3, 0);
+
+ wrapper.performContextMenuAction(1);
+ verify(inputConnection, times(1)).performContextMenuAction(1);
+
+ wrapper.performEditorAction(EditorInfo.IME_ACTION_GO);
+ verify(inputConnection, times(1)).performEditorAction(EditorInfo.IME_ACTION_GO);
+
+ wrapper.performPrivateCommand("com.android.action.MAIN", new Bundle());
+ verify(inputConnection, times(1)).performPrivateCommand(eq("com.android.action.MAIN"),
+ any(Bundle.class));
+
+ wrapper.reportFullscreenMode(true);
+ verify(inputConnection, times(1)).reportFullscreenMode(true);
+
+ wrapper.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0));
+ ArgumentCaptor<KeyEvent> keyEventCaptor = ArgumentCaptor.forClass(KeyEvent.class);
+ verify(inputConnection, times(1)).sendKeyEvent(keyEventCaptor.capture());
+ assertEquals(KeyEvent.ACTION_DOWN, keyEventCaptor.getValue().getAction());
+ assertEquals(KeyEvent.KEYCODE_0, keyEventCaptor.getValue().getKeyCode());
+
+ wrapper.setComposingText("Text", 1);
+ verify(inputConnection, times(1)).setComposingText("Text", 1);
+
+ wrapper.setSelection(0, 10);
+ verify(inputConnection, times(1)).setSelection(0, 10);
+
+ wrapper.getSelectedText(0);
+ verify(inputConnection, times(1)).getSelectedText(0);
+
+ wrapper.setComposingRegion(0, 3);
+ verify(inputConnection, times(1)).setComposingRegion(0, 3);
+
+ wrapper.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE);
+ verify(inputConnection, times(1))
+ .requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE);
+
+ wrapper.closeConnection();
+ verify(inputConnection, times(1)).closeConnection();
+
+ verify(inputConnection, never()).getHandler();
+ assertNull(wrapper.getHandler());
+ verify(inputConnection, times(1)).getHandler();
+
+ verify(inputConnection, never()).commitContent(any(InputContentInfo.class), anyInt(),
+ any(Bundle.class));
+
+ final InputContentInfo inputContentInfo = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ wrapper.commitContent(inputContentInfo, 0 /* flags */, null /* opt */);
+ verify(inputConnection, times(1)).commitContent(inputContentInfo, 0, null);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputContentInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputContentInfoTest.java
new file mode 100644
index 0000000..777b6d8
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputContentInfoTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.InputContentInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.InvalidParameterException;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputContentInfoTest {
+ @Test
+ public void testInputContentInfo() {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+
+ assertEquals(Uri.parse("content://com.example/path"), info.getContentUri());
+ assertEquals(1, info.getDescription().getMimeTypeCount());
+ assertEquals("image/png", info.getDescription().getMimeType(0));
+ assertEquals("sample content", info.getDescription().getLabel());
+ assertEquals(Uri.parse("https://example.com"), info.getLinkUri());
+ assertEquals(0, info.describeContents());
+
+ Parcel p = Parcel.obtain();
+ info.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ InputContentInfo targetInfo = InputContentInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+
+ assertEquals(info.getContentUri(), targetInfo.getContentUri());
+ assertEquals(info.getDescription().getMimeTypeCount(),
+ targetInfo.getDescription().getMimeTypeCount());
+ assertEquals(info.getDescription().getMimeType(0),
+ targetInfo.getDescription().getMimeType(0));
+ assertEquals(info.getDescription().getLabel(), targetInfo.getDescription().getLabel());
+ assertEquals(info.getLinkUri(), targetInfo.getLinkUri());
+ assertEquals(info.describeContents(), targetInfo.describeContents());
+ }
+
+ @Test
+ public void testOptionalConstructorParam() {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}));
+
+ assertEquals(Uri.parse("content://com.example/path"), info.getContentUri());
+ assertEquals(1, info.getDescription().getMimeTypeCount());
+ assertEquals("image/png", info.getDescription().getMimeType(0));
+ assertEquals("sample content", info.getDescription().getLabel());
+ assertNull(info.getLinkUri());
+ assertEquals(0, info.describeContents());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testContentUriNullContentUri() {
+ new InputContentInfo(
+ null, new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ }
+
+ @Test(expected = InvalidParameterException.class)
+ public void testContentUriInvalidContentUri() {
+ new InputContentInfo(
+ Uri.parse("https://example.com"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testMimeTypeNulLDescription() {
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"), null,
+ Uri.parse("https://example.com"));
+ }
+
+ @Test
+ public void testLinkUri() {
+ // Test that we accept null link Uri
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ null);
+
+ // Test that we accept http link Uri
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("http://example.com/path"));
+
+ // Test that we accept https link Uri
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com/path"));
+ }
+
+ @Test(expected = InvalidParameterException.class)
+ public void testLinkUriFtpLinkUri() {
+ // InputContentInfo must accept http and https link Uri only
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("ftp://example.com/path"));
+ }
+
+ @Test(expected = InvalidParameterException.class)
+ public void testLinkUriContentLinkUri() {
+ // InputContentInfo must accept http and https link Uri only
+ new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("content://com.example/path"));
+ }
+
+ @Test
+ public void testRequestAndReleasePermission() {
+ InputContentInfo info = new InputContentInfo(
+ Uri.parse("content://com.example/path"),
+ new ClipDescription("sample content", new String[]{"image/png"}),
+ Uri.parse("https://example.com"));
+
+ // Here we only assert that {request, release}Permission() do not crash, because ensuring
+ // the entire functionality of these methods requires end-to-end IME test environment, which
+ // we do not have yet in CTS.
+ // Note it is actually intentional that calling these methods here has no effect. Those
+ // methods would have effect only after the object is passed from the IME process to the
+ // application process.
+ // TODO: Create an end-to-end CTS test for this functionality.
+ info.requestPermission();
+ info.releasePermission();
+ info.requestPermission();
+ info.releasePermission();
+ }
+
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodCtsActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodCtsActivity.java
new file mode 100644
index 0000000..9501d44
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodCtsActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2008 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;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.inputmethod.cts.R;
+
+public class InputMethodCtsActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.inputmethod_edittext);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
new file mode 100644
index 0000000..b34673b
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2008 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.TextUtils;
+import android.util.Printer;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputMethodInfoTest {
+ private Context mContext;
+
+ private InputMethodInfo mInputMethodInfo;
+ private String mPackageName;
+ private String mClassName;
+ private CharSequence mLabel;
+ private String mSettingsActivity;
+
+ private int mSubtypeNameResId;
+ private int mSubtypeIconResId;
+ private String mSubtypeLocale;
+ private String mSubtypeMode;
+ private String mSubtypeExtraValue_key;
+ private String mSubtypeExtraValue_value;
+ private String mSubtypeExtraValue;
+ private boolean mSubtypeIsAuxiliary;
+ private boolean mSubtypeOverridesImplicitlyEnabledSubtype;
+ private int mSubtypeId;
+ private InputMethodSubtype mInputMethodSubtype;
+
+ @Before
+ public void setup() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mPackageName = mContext.getPackageName();
+ mClassName = InputMethodSettingsActivityStub.class.getName();
+ mLabel = "test";
+ mSettingsActivity = "android.view.inputmethod.cts.InputMethodSettingsActivityStub";
+ mInputMethodInfo = new InputMethodInfo(mPackageName, mClassName, mLabel, mSettingsActivity);
+
+ mSubtypeNameResId = 0;
+ mSubtypeIconResId = 0;
+ mSubtypeLocale = "en_US";
+ mSubtypeMode = "keyboard";
+ mSubtypeExtraValue_key = "key1";
+ mSubtypeExtraValue_value = "value1";
+ mSubtypeExtraValue = "tag," + mSubtypeExtraValue_key + "=" + mSubtypeExtraValue_value;
+ mSubtypeIsAuxiliary = false;
+ mSubtypeOverridesImplicitlyEnabledSubtype = false;
+ mSubtypeId = 99;
+ mInputMethodSubtype = new InputMethodSubtype(mSubtypeNameResId, mSubtypeIconResId,
+ mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue, mSubtypeIsAuxiliary,
+ mSubtypeOverridesImplicitlyEnabledSubtype, mSubtypeId);
+ }
+
+ @Test
+ public void testInputMethodInfoProperties() throws XmlPullParserException, IOException {
+ assertEquals(0, mInputMethodInfo.describeContents());
+ assertNotNull(mInputMethodInfo.toString());
+
+ assertInfo(mInputMethodInfo);
+ assertEquals(0, mInputMethodInfo.getIsDefaultResourceId());
+
+ Intent intent = new Intent(InputMethod.SERVICE_INTERFACE);
+ intent.setClass(mContext, InputMethodSettingsActivityStub.class);
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> ris = pm.queryIntentServices(intent, PackageManager.GET_META_DATA);
+ for (int i = 0; i < ris.size(); i++) {
+ ResolveInfo resolveInfo = ris.get(i);
+ mInputMethodInfo = new InputMethodInfo(mContext, resolveInfo);
+ assertService(resolveInfo.serviceInfo, mInputMethodInfo.getServiceInfo());
+ assertInfo(mInputMethodInfo);
+ }
+ }
+
+ @Test
+ public void testInputMethodSubtypeProperties() {
+ // TODO: Test InputMethodSubtype.getDisplayName()
+ assertEquals(mSubtypeNameResId, mInputMethodSubtype.getNameResId());
+ assertEquals(mSubtypeIconResId, mInputMethodSubtype.getIconResId());
+ assertEquals(mSubtypeLocale, mInputMethodSubtype.getLocale());
+ assertEquals(mSubtypeMode, mInputMethodSubtype.getMode());
+ assertEquals(mSubtypeExtraValue, mInputMethodSubtype.getExtraValue());
+ assertTrue(mInputMethodSubtype.containsExtraValueKey(mSubtypeExtraValue_key));
+ assertEquals(mSubtypeExtraValue_value,
+ mInputMethodSubtype.getExtraValueOf(mSubtypeExtraValue_key));
+ assertEquals(mSubtypeIsAuxiliary, mInputMethodSubtype.isAuxiliary());
+ assertEquals(mSubtypeOverridesImplicitlyEnabledSubtype,
+ mInputMethodSubtype.overridesImplicitlyEnabledSubtype());
+ assertEquals(mSubtypeId, mInputMethodSubtype.hashCode());
+ }
+
+ private void assertService(ServiceInfo expected, ServiceInfo actual) {
+ assertEquals(expected.getIconResource(), actual.getIconResource());
+ assertEquals(expected.labelRes, actual.labelRes);
+ assertEquals(expected.nonLocalizedLabel, actual.nonLocalizedLabel);
+ assertEquals(expected.icon, actual.icon);
+ assertEquals(expected.permission, actual.permission);
+ }
+
+ private void assertInfo(InputMethodInfo info) {
+ assertEquals(mPackageName, info.getPackageName());
+ assertEquals(mSettingsActivity, info.getSettingsActivity());
+ ComponentName component = info.getComponent();
+ assertEquals(mClassName, component.getClassName());
+ String expectedId = component.flattenToShortString();
+ assertEquals(expectedId, info.getId());
+ assertEquals(mClassName, info.getServiceName());
+ }
+
+ @Test
+ public void testDump() {
+ Printer printer = mock(Printer.class);
+ String prefix = "test";
+ mInputMethodInfo.dump(printer, prefix);
+ verify(printer, atLeastOnce()).println(anyString());
+ }
+
+ @Test
+ public void testLoadIcon() {
+ PackageManager pm = mContext.getPackageManager();
+ assertNotNull(mInputMethodInfo.loadIcon(pm));
+ }
+
+ @Test
+ public void testEquals() {
+ InputMethodInfo inputMethodInfo = new InputMethodInfo(mPackageName, mClassName, mLabel,
+ mSettingsActivity);
+ assertTrue(inputMethodInfo.equals(mInputMethodInfo));
+ }
+
+ @Test
+ public void testLoadLabel() {
+ CharSequence expected = "test";
+ PackageManager pm = mContext.getPackageManager();
+ assertEquals(expected.toString(), mInputMethodInfo.loadLabel(pm).toString());
+ }
+
+ @Test
+ public void testInputMethodInfoWriteToParcel() {
+ final Parcel p = Parcel.obtain();
+ mInputMethodInfo.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ final InputMethodInfo imi = InputMethodInfo.CREATOR.createFromParcel(p);
+ p.recycle();
+
+ assertEquals(mInputMethodInfo.getPackageName(), imi.getPackageName());
+ assertEquals(mInputMethodInfo.getServiceName(), imi.getServiceName());
+ assertEquals(mInputMethodInfo.getSettingsActivity(), imi.getSettingsActivity());
+ assertEquals(mInputMethodInfo.getId(), imi.getId());
+ assertEquals(mInputMethodInfo.getIsDefaultResourceId(), imi.getIsDefaultResourceId());
+ assertService(mInputMethodInfo.getServiceInfo(), imi.getServiceInfo());
+ }
+
+ @Test
+ public void testInputMethodSubtypeWriteToParcel() {
+ final Parcel p = Parcel.obtain();
+ mInputMethodSubtype.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ final InputMethodSubtype subtype = InputMethodSubtype.CREATOR.createFromParcel(p);
+ p.recycle();
+
+ assertEquals(mInputMethodSubtype.containsExtraValueKey(mSubtypeExtraValue_key),
+ subtype.containsExtraValueKey(mSubtypeExtraValue_key));
+ assertEquals(mInputMethodSubtype.getExtraValue(), subtype.getExtraValue());
+ assertEquals(mInputMethodSubtype.getExtraValueOf(mSubtypeExtraValue_key),
+ subtype.getExtraValueOf(mSubtypeExtraValue_key));
+ assertEquals(mInputMethodSubtype.getIconResId(), subtype.getIconResId());
+ assertEquals(mInputMethodSubtype.getLocale(), subtype.getLocale());
+ assertEquals(mInputMethodSubtype.getMode(), subtype.getMode());
+ assertEquals(mInputMethodSubtype.getNameResId(), subtype.getNameResId());
+ assertEquals(mInputMethodSubtype.hashCode(), subtype.hashCode());
+ assertEquals(mInputMethodSubtype.isAuxiliary(), subtype.isAuxiliary());
+ assertEquals(mInputMethodSubtype.overridesImplicitlyEnabledSubtype(),
+ subtype.overridesImplicitlyEnabledSubtype());
+ }
+
+ @Test
+ public void testInputMethodSubtypesOfSystemImes() {
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS)) {
+ return;
+ }
+
+ final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+ final List<InputMethodInfo> imis = imm.getInputMethodList();
+ final ArrayList<String> localeList = new ArrayList<>(Arrays.asList(
+ Resources.getSystem().getAssets().getLocales()));
+ boolean foundEnabledSystemImeSubtypeWithValidLanguage = false;
+ for (InputMethodInfo imi : imis) {
+ if ((imi.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ continue;
+ }
+ final int subtypeCount = imi.getSubtypeCount();
+ // System IME must have one subtype at least.
+ assertTrue(subtypeCount > 0);
+ if (foundEnabledSystemImeSubtypeWithValidLanguage) {
+ continue;
+ }
+ final List<InputMethodSubtype> enabledSubtypes =
+ imm.getEnabledInputMethodSubtypeList(imi, true);
+ SUBTYPE_LOOP:
+ for (InputMethodSubtype subtype : enabledSubtypes) {
+ final String subtypeLocale = subtype.getLocale();
+ if (subtypeLocale.length() < 2) {
+ continue;
+ }
+ // TODO: Detect language more strictly.
+ final String subtypeLanguage = subtypeLocale.substring(0, 2);
+ for (final String locale : localeList) {
+ if (locale.startsWith(subtypeLanguage)) {
+ foundEnabledSystemImeSubtypeWithValidLanguage = true;
+ break SUBTYPE_LOOP;
+ }
+ }
+ }
+ }
+ assertTrue(foundEnabledSystemImeSubtypeWithValidLanguage);
+ }
+
+ @Test
+ public void testAtLeastOneEncryptionAwareInputMethodIsAvailable() {
+ if (!mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS)) {
+ return;
+ }
+
+ if (!TextUtils.equals("native", getFbeMode())) {
+ // Skip the test unless the device is in native FBE mode.
+ return;
+ }
+
+ final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+ final List<InputMethodInfo> imis = imm.getInputMethodList();
+ boolean hasEncryptionAwareInputMethod = false;
+ for (final InputMethodInfo imi : imis) {
+ final ServiceInfo serviceInfo = imi.getServiceInfo();
+ if (serviceInfo == null) {
+ continue;
+ }
+ if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) !=
+ ApplicationInfo.FLAG_SYSTEM) {
+ continue;
+ }
+ if (serviceInfo.encryptionAware) {
+ hasEncryptionAwareInputMethod = true;
+ break;
+ }
+ }
+ assertTrue(hasEncryptionAwareInputMethod);
+ }
+
+ private String getFbeMode() {
+ try (ParcelFileDescriptor.AutoCloseInputStream in =
+ new ParcelFileDescriptor.AutoCloseInputStream(InstrumentationRegistry
+ .getInstrumentation()
+ .getUiAutomation()
+ .executeShellCommand("sm get-fbe-mode"))) {
+ try (BufferedReader br =
+ new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
+ // Assume that the output of "sm get-fbe-mode" is always one-line.
+ final String line = br.readLine();
+ return line != null ? line.trim() : "";
+ }
+ } catch (IOException e) {
+ return "";
+ }
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
new file mode 100644
index 0000000..16eaad0
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2008 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;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ResultReceiver;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.KeyEvent;
+import android.view.Window;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.R;
+import android.widget.EditText;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InputMethodManagerTest {
+ private Instrumentation mInstrumentation;
+ private InputMethodCtsActivity mActivity;
+
+ @Rule
+ public ActivityTestRule<InputMethodCtsActivity> mActivityRule =
+ new ActivityTestRule<>(InputMethodCtsActivity.class);
+
+ @Before
+ public void setup() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mActivity = mActivityRule.getActivity();
+ }
+
+ @After
+ public void teardown() {
+ mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+ }
+
+ @Test
+ public void testInputMethodManager() throws Throwable {
+ if (!mActivity.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_INPUT_METHODS)) {
+ return;
+ }
+
+ Window window = mActivity.getWindow();
+ final EditText view = (EditText) window.findViewById(R.id.entry);
+
+ PollingCheck.waitFor(1000, view::hasWindowFocus);
+
+ mActivityRule.runOnUiThread(view::requestFocus);
+ mInstrumentation.waitForIdleSync();
+ assertTrue(view.isFocused());
+
+ BaseInputConnection connection = new BaseInputConnection(view, false);
+ Context context = mInstrumentation.getTargetContext();
+ final InputMethodManager imManager = (InputMethodManager) context
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ PollingCheck.waitFor(imManager::isActive);
+
+ assertTrue(imManager.isAcceptingText());
+ assertTrue(imManager.isActive(view));
+
+ assertFalse(imManager.isFullscreenMode());
+ connection.reportFullscreenMode(true);
+ // Only IMEs are allowed to report full-screen mode. Calling this method from the
+ // application should have no effect.
+ assertFalse(imManager.isFullscreenMode());
+
+ mActivityRule.runOnUiThread(() -> {
+ IBinder token = view.getWindowToken();
+
+ // Show and hide input method.
+ assertTrue(imManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT));
+ assertTrue(imManager.hideSoftInputFromWindow(token, 0));
+
+ Handler handler = new Handler();
+ ResultReceiver receiver = new ResultReceiver(handler);
+ assertTrue(imManager.showSoftInput(view, 0, receiver));
+ receiver = new ResultReceiver(handler);
+ assertTrue(imManager.hideSoftInputFromWindow(token, 0, receiver));
+
+ imManager.showSoftInputFromInputMethod(token, InputMethodManager.SHOW_FORCED);
+ imManager.hideSoftInputFromInputMethod(token, InputMethodManager.HIDE_NOT_ALWAYS);
+
+ // status: hide to show to hide
+ imManager.toggleSoftInputFromWindow(token, 0, InputMethodManager.HIDE_NOT_ALWAYS);
+ imManager.toggleSoftInputFromWindow(token, 0, InputMethodManager.HIDE_NOT_ALWAYS);
+
+ List<InputMethodInfo> enabledImList = imManager.getEnabledInputMethodList();
+ if (enabledImList != null && enabledImList.size() > 0) {
+ imManager.setInputMethod(token, enabledImList.get(0).getId());
+ // cannot test whether setting was successful
+ }
+
+ List<InputMethodInfo> imList = imManager.getInputMethodList();
+ if (imList != null && enabledImList != null) {
+ assertTrue(imList.size() >= enabledImList.size());
+ }
+ });
+ mInstrumentation.waitForIdleSync();
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodSettingsActivityStub.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodSettingsActivityStub.java
new file mode 100644
index 0000000..58aa364
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodSettingsActivityStub.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2008 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;
+
+import android.preference.PreferenceActivity;
+
+public class InputMethodSettingsActivityStub extends PreferenceActivity {
+
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardTest.java
new file mode 100644
index 0000000..3f61093
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 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;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.Keyboard.Key;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.inputmethod.cts.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KeyboardTest {
+ @Test
+ public void testKeyOnPressedAndReleased() {
+ Key nonStickyKey = null;
+ Key stickyKey = null;
+ // Indirectly instantiate Keyboard.Key with XML resources.
+ final Keyboard keyboard =
+ new Keyboard(InstrumentationRegistry.getTargetContext(), R.xml.keyboard);
+ for (final Key key : keyboard.getKeys()) {
+ if (!key.sticky) {
+ nonStickyKey = key;
+ break;
+ }
+ }
+ for (final Key key : keyboard.getModifierKeys()) {
+ if (key.sticky) {
+ stickyKey = key;
+ break;
+ }
+ }
+
+ // Asserting existences of following keys is not the goal of this test, but this should work
+ // anyway.
+ assertNotNull(nonStickyKey);
+ assertNotNull(stickyKey);
+
+ // At first, both "pressed" and "on" must be false.
+ assertFalse(nonStickyKey.pressed);
+ assertFalse(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertFalse(stickyKey.on);
+
+ // Pressing the key must flip the "pressed" state only.
+ nonStickyKey.onPressed();
+ stickyKey.onPressed();
+ assertTrue(nonStickyKey.pressed);
+ assertTrue(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertFalse(stickyKey.on);
+
+ // Releasing the key inside the key area must flip the "pressed" state and toggle the "on"
+ // state if the key is marked as sticky.
+ nonStickyKey.onReleased(true /* inside */);
+ stickyKey.onReleased(true /* inside */);
+ assertFalse(nonStickyKey.pressed);
+ assertFalse(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertTrue(stickyKey.on); // The key state is toggled.
+
+ // Pressing the key again must flip the "pressed" state only.
+ nonStickyKey.onPressed();
+ stickyKey.onPressed();
+ assertTrue(nonStickyKey.pressed);
+ assertTrue(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertTrue(stickyKey.on);
+
+ // Releasing the key inside the key area must flip the "pressed" state and toggle the "on"
+ // state if the key is marked as sticky hence we will be back to the initial state.
+ nonStickyKey.onReleased(true /* inside */);
+ stickyKey.onReleased(true /* inside */);
+ assertFalse(nonStickyKey.pressed);
+ assertFalse(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertFalse(stickyKey.on);
+
+ // Pressing then releasing the key outside the key area must not affect the "on" state.
+ nonStickyKey.onPressed();
+ stickyKey.onPressed();
+ nonStickyKey.onReleased(false /* inside */);
+ stickyKey.onReleased(false /* inside */);
+ assertFalse(nonStickyKey.pressed);
+ assertFalse(stickyKey.pressed);
+ assertFalse(nonStickyKey.on);
+ assertFalse(stickyKey.on);
+ }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputConnectionTestUtils.java
new file mode 100644
index 0000000..3735c33
--- /dev/null
+++ b/tests/inputmethod/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;
+ }
+}