Add MockSpellChecker and SpellCheckerTest.
This CL adds a minimal end-to-end test for Spell checker framework.
To this end, this CL introduces MockSpellChecker, which is inspired by
MockIme.
Bug: 166304720
Test: atest CtsInputMethodTestCases:SpellCheckerTest
Change-Id: I6d92ed0c7f7ca88011f6f5470798cfbc7ff3fa96
diff --git a/tests/inputmethod/Android.bp b/tests/inputmethod/Android.bp
index 6f07ac6..aa3be52 100644
--- a/tests/inputmethod/Android.bp
+++ b/tests/inputmethod/Android.bp
@@ -29,10 +29,13 @@
"compatibility-device-util-axt",
"ctstestrunner-axt",
"CtsMockInputMethodLib",
+ "CtsMockSpellCheckerLib",
"testng",
+ "kotlin-test",
],
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"src/**/I*.aidl",
],
aidl: {
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index fd2ea5d..9108a44 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -36,6 +36,15 @@
<option name="force-install-mode" value="FULL"/>
<option name="test-file-name" value="CtsMockInputMethod.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <!--
+ MockSpellChecker always needs to be instaleld as a full package, even when CTS is
+ running for instant apps.
+ -->
+ <option name="force-install-mode" value="FULL"/>
+ <option name="test-file-name" value="CtsMockSpellChecker.apk" />
+ </target_preparer>
<!--
TODO(yukawa): come up with a proper way to take care of devices that do not support
installable IMEs. Ideally target_preparer should have an option to annotate required
diff --git a/tests/inputmethod/mockspellchecker/Android.bp b/tests/inputmethod/mockspellchecker/Android.bp
new file mode 100644
index 0000000..dce4215
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2020 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.
+
+java_test_helper_library {
+ name: "CtsMockSpellCheckerLib",
+ sdk_version: "test_current",
+
+ srcs: [
+ "src/**/*.kt",
+ "src/**/*.proto",
+ ],
+ libs: ["junit"],
+ proto: {
+ type: "lite",
+ },
+ static_libs: [
+ "androidx.annotation_annotation",
+ "compatibility-device-util-axt",
+ ],
+}
+
+android_test_helper_app {
+ name: "CtsMockSpellChecker",
+ defaults: ["cts_defaults"],
+ optimize: {
+ enabled: false,
+ },
+ sdk_version: "current",
+ min_sdk_version: "19",
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "CtsMockSpellCheckerLib",
+ ],
+}
diff --git a/tests/inputmethod/mockspellchecker/AndroidManifest.xml b/tests/inputmethod/mockspellchecker/AndroidManifest.xml
new file mode 100644
index 0000000..f076bb1
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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="com.android.cts.mockspellchecker">
+
+ <application android:multiArch="true"
+ android:supportsRtl="true">
+
+ <meta-data android:name="instantapps.clients.allowed"
+ android:value="true"/>
+
+ <service android:name=".MockSpellChecker"
+ android:label="@string/spell_checker_name"
+ android:permission="android.permission.BIND_TEXT_SERVICE"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.service.textservice.SpellCheckerService"/>
+ </intent-filter>
+
+ <meta-data
+ android:name="android.view.textservice.scs"
+ android:resource="@xml/spellchecker"/>
+ </service>
+
+ <provider android:authorities="com.android.cts.mockspellchecker.provider"
+ android:name=".SharedPrefsProvider"
+ android:exported="true"
+ android:visibleToInstantApps="true">
+ </provider>
+
+ </application>
+</manifest>
diff --git a/tests/inputmethod/mockspellchecker/res/values/values.xml b/tests/inputmethod/mockspellchecker/res/values/values.xml
new file mode 100644
index 0000000..4accba9
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/res/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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>
+ <string name="spell_checker_name">Mock Spell Checker</string>
+</resources>
diff --git a/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml b/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
new file mode 100644
index 0000000..8820f29
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/res/xml/spellchecker.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+-->
+
+<spell-checker xmlns:android="http://schemas.android.com/apk/res/android"
+ android:label="@string/spell_checker_name">
+ <subtype
+ android:label="English"
+ android:subtypeLocale="en"
+ />
+</spell-checker>
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/Constants.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/Constants.kt
new file mode 100644
index 0000000..8653e94
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/Constants.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.mockspellchecker
+
+const val TAG = "MockSpellChecker"
+const val PACKAGE = "com.android.cts.mockspellchecker"
+const val AUTHORITY = "com.android.cts.mockspellchecker.provider"
+
+internal const val KEY_CONFIGURATION = "configuration"
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
new file mode 100644
index 0000000..ee16ce4
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellChecker.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.mockspellchecker
+
+import android.content.ComponentName
+import android.service.textservice.SpellCheckerService
+import android.util.Log
+import android.view.textservice.SuggestionsInfo
+import android.view.textservice.TextInfo
+import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+import java.io.FileDescriptor
+import java.io.PrintWriter
+
+internal inline fun <T> withLog(msg: String, block: () -> T): T {
+ Log.i(TAG, msg)
+ return block()
+}
+
+/** Mock Spell checker for end-to-end tests. */
+class MockSpellChecker : SpellCheckerService() {
+
+ override fun onCreate() = withLog("MockSpellChecker.onCreate") {
+ super.onCreate()
+ }
+
+ override fun onDestroy() = withLog("MockSpellChecker.onDestroy") {
+ super.onDestroy()
+ }
+
+ override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
+ writer?.println("MockSpellChecker")
+ }
+
+ override fun createSession(): Session = withLog("MockSpellChecker.createSession") {
+ val configuration = MockSpellCheckerConfiguration.parseFrom(
+ SharedPrefsProvider.get(contentResolver, KEY_CONFIGURATION))
+ return MockSpellCheckerSession(configuration)
+ }
+
+ private inner class MockSpellCheckerSession(
+ val configuration: MockSpellCheckerConfiguration
+ ) : SpellCheckerService.Session() {
+
+ override fun onCreate() = withLog("MockSpellCheckerSession.onCreate") {
+ }
+
+ override fun onGetSuggestions(
+ textInfo: TextInfo?,
+ suggestionsLimit: Int
+ ): SuggestionsInfo = withLog(
+ "MockSpellCheckerSession.onGetSuggestions: ${textInfo?.text}") {
+ if (textInfo == null) return emptySuggestionsInfo()
+ return configuration.suggestionRulesList
+ .find { it.match == textInfo.text }
+ ?.let { SuggestionsInfo(it.attributes, it.suggestionsList.toTypedArray()) }
+ ?: emptySuggestionsInfo()
+ }
+
+ private fun emptySuggestionsInfo() = SuggestionsInfo(0, arrayOf())
+ }
+
+ companion object {
+ @JvmStatic
+ fun getId(): String =
+ ComponentName(PACKAGE, MockSpellChecker::class.java.name).flattenToShortString()
+ }
+}
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
new file mode 100644
index 0000000..90c9fd5
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/MockSpellCheckerClient.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.mockspellchecker
+
+import android.content.Context
+import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+
+/**
+ * Client interface for {@link MockSpellChecker}.
+ *
+ * <p>This class should be used by test apps.
+ */
+class MockSpellCheckerClient(
+ private val context: Context,
+ private val configuration: MockSpellCheckerConfiguration
+)
+ : AutoCloseable {
+
+ fun initialize() {
+ SharedPrefsProvider.put(
+ context.contentResolver, KEY_CONFIGURATION, configuration.toByteArray())
+ }
+
+ override fun close() {
+ SharedPrefsProvider.delete(context.contentResolver, KEY_CONFIGURATION)
+ }
+
+ companion object {
+ @JvmStatic
+ fun create(context: Context, configuration: MockSpellCheckerConfiguration):
+ MockSpellCheckerClient {
+ val client = MockSpellCheckerClient(context, configuration)
+ client.initialize()
+ return client
+ }
+ }
+}
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/SharedPrefsProvider.kt b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/SharedPrefsProvider.kt
new file mode 100644
index 0000000..7e23885
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/SharedPrefsProvider.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.mockspellchecker
+
+import android.content.ContentProvider
+import android.content.ContentResolver
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import android.database.MatrixCursor
+import android.net.Uri
+import android.util.Base64
+
+private const val PREFS_FILE_NAME = "prefs.xml"
+private const val COLUMN_NAME = "value"
+
+/**
+ * ContentProvider to access MockSpellChecker's shared preferences.
+ *
+ * <p>Please use the companion object methods to interact with this ContentProvider. The companion
+ * object methods can be used from other processes.
+ *
+ * <p>This class supports ByteArray value only.
+ */
+class SharedPrefsProvider : ContentProvider() {
+
+ override fun onCreate(): Boolean = withLog("SharedPrefsProvider.onCreate") { true }
+
+ override fun getType(uri: Uri): String? = null
+
+ override fun query(
+ uri: Uri,
+ projection: Array<String>?,
+ selection: String?,
+ selectionArgs: Array<String>?,
+ sortOrder: String?
+ ): Cursor? = withLog("SharedPrefsProvider.query: $uri") {
+ val context = context ?: return null
+ val prefs = getSharedPreferences(context)
+ val bytes = Base64.decode(prefs.getString(uri.path, ""), Base64.DEFAULT)
+ val cursor = MatrixCursor(arrayOf(COLUMN_NAME))
+ cursor.addRow(arrayOf(bytes))
+ return cursor
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? =
+ withLog("SharedPrefsProvider.insert: $uri") { null }
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<String>?
+ ): Int = withLog("SharedPrefsProvider.update: $uri") {
+ val context = context ?: return 0
+ if (values == null) return 0
+ val prefs = getSharedPreferences(context)
+ val bytes = values.getAsByteArray(COLUMN_NAME)
+ val str = Base64.encodeToString(bytes, Base64.DEFAULT)
+ prefs.edit().putString(uri.path, str).apply()
+ return 1
+ }
+
+ override fun delete(
+ uri: Uri,
+ selection: String?,
+ selectionArgs: Array<String>?
+ ): Int = withLog("SharedPrefsProvider.delete: $uri") {
+ val context = context ?: return 0
+ val prefs = getSharedPreferences(context)
+ prefs.edit().remove(uri.path).apply()
+ return 1
+ }
+
+ private fun getSharedPreferences(context: Context) =
+ context.getSharedPreferences(PREFS_FILE_NAME, Context.MODE_PRIVATE)
+
+ companion object {
+ /** Returns the data for the key. */
+ fun get(resolver: ContentResolver, key: String): ByteArray {
+ val cursor = resolver.query(uriFor(key), arrayOf(COLUMN_NAME), null, null)
+ return if (cursor != null && cursor.moveToNext()) {
+ cursor.getBlob(0)
+ } else {
+ ByteArray(0)
+ }
+ }
+
+ /** Stores the data for the key. */
+ fun put(resolver: ContentResolver, key: String, value: ByteArray) {
+ val values = ContentValues()
+ values.put(COLUMN_NAME, value)
+ resolver.update(uriFor(key), values, null)
+ }
+
+ /** Deletes the data for the key. */
+ fun delete(resolver: ContentResolver, key: String) {
+ resolver.delete(uriFor(key), null)
+ }
+
+ private fun uriFor(key: String): Uri = Uri.parse("content://$AUTHORITY/$key")
+ }
+}
diff --git a/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
new file mode 100644
index 0000000..58b127f
--- /dev/null
+++ b/tests/inputmethod/mockspellchecker/src/com/android/cts/mockspellchecker/mockspellchecker.proto
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+
+package com.android.cts.mockspellchecker;
+
+option java_outer_classname = "MockSpellCheckerProto";
+
+// Represents a suggestion rule.
+// If the string matches 'match', SuggestionsInfo with attributes and suggestions are appended.
+message SuggestionRule {
+ optional string match = 1;
+ optional int32 attributes = 2;
+ repeated string suggestions = 3;
+}
+
+// Represents a MockSpellChecker configuration.
+message MockSpellCheckerConfiguration {
+ repeated SuggestionRule suggestion_rules = 1;
+};
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
new file mode 100644
index 0000000..77eaa64
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SpellCheckerTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 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.content.Context
+import android.provider.Settings
+import android.text.style.SuggestionSpan
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.inputmethod.cts.util.EndToEndImeTestBase
+import android.view.inputmethod.cts.util.InputMethodVisibilityVerifier
+import android.view.inputmethod.cts.util.TestActivity
+import android.view.inputmethod.cts.util.TestUtils
+import android.view.inputmethod.cts.util.UnlockScreenRule
+import android.view.textservice.SpellCheckerSubtype
+import android.view.textservice.SuggestionsInfo
+import android.view.textservice.TextServicesManager
+import android.widget.EditText
+import android.widget.LinearLayout
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.CtsTouchUtils
+import com.android.compatibility.common.util.SettingsStateChangerRule
+import com.android.cts.mockime.MockImeSession
+import com.android.cts.mockspellchecker.MockSpellChecker
+import com.android.cts.mockspellchecker.MockSpellCheckerClient
+import com.android.cts.mockspellchecker.MockSpellCheckerProto
+import com.android.cts.mockspellchecker.MockSpellCheckerProto.MockSpellCheckerConfiguration
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.TimeUnit
+
+private val TIMEOUT = TimeUnit.SECONDS.toMillis(5)
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class SpellCheckerTest : EndToEndImeTestBase() {
+
+ private val context: Context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+
+ @Rule
+ fun unlockScreenRule() = UnlockScreenRule()
+
+ @Rule
+ fun spellCheckerSettingsRule() = SettingsStateChangerRule(
+ context, Settings.Secure.SELECTED_SPELL_CHECKER, MockSpellChecker.getId())
+
+ @Rule
+ fun spellCheckerSubtypeSettingsRule() = SettingsStateChangerRule(
+ context, Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE,
+ SpellCheckerSubtype.SUBTYPE_ID_NONE.toString())
+
+ @Before
+ fun setUp() {
+ val tsm = context.getSystemService(TextServicesManager::class.java)!!
+ // Skip if spell checker is not enabled by default.
+ Assume.assumeNotNull(tsm)
+ Assume.assumeTrue(tsm.isSpellCheckerEnabled)
+ }
+
+ @Test
+ fun test() {
+ val configuration = MockSpellCheckerConfiguration.newBuilder()
+ .addSuggestionRules(
+ MockSpellCheckerProto.SuggestionRule.newBuilder()
+ .setMatch("match")
+ .addSuggestions("suggestion")
+ .setAttributes(SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO)
+ ).build()
+ MockImeSession.create(context).use { session ->
+ MockSpellCheckerClient.create(context, configuration).use {
+ val (_, editText) = startTestActivity()
+ CtsTouchUtils.emulateTapOnViewCenter(
+ InstrumentationRegistry.getInstrumentation(), null, editText)
+ TestUtils.waitOnMainUntil({ editText.hasFocus() }, TIMEOUT)
+ InputMethodVisibilityVerifier.expectImeVisible(TIMEOUT)
+ session.callCommitText("match", 1)
+ session.callCommitText(" ", 1)
+ TestUtils.waitOnMainUntil({ hasSuggestionSpan(editText) }, TIMEOUT)
+ }
+ }
+ }
+
+ private fun hasSuggestionSpan(editText: EditText): Boolean {
+ val editable = editText.text
+ val spans = editable.getSpans(0, editable.length, SuggestionSpan::class.java)
+ return spans != null && spans.isNotEmpty()
+ }
+
+ private fun startTestActivity(): Pair<TestActivity, EditText> {
+ var editText: EditText? = null
+ val activity = TestActivity.startSync { activity: TestActivity? ->
+ val layout = LinearLayout(activity)
+ editText = EditText(activity)
+ layout.addView(editText, LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT))
+ layout
+ }
+ return Pair(activity, editText!!)
+ }
+}
\ No newline at end of file