Add CTS test for a11y ime apis

Test: test passes.
Bug: 187453053

Change-Id: Icc1f61be79f8e16e7c2e53031937701b847b2b06
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 584a8d5..2dfb7fd 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -214,6 +214,30 @@
             <meta-data android:name="android.view.im"
                 android:resource="@xml/stub_ime"/>
         </service>
+
+        <service android:name=".StubImeAccessibilityService"
+            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
+            </intent-filter>
+
+            <meta-data android:name="android.accessibilityservice"
+                android:resource="@xml/stub_ime_accessibility_service"/>
+        </service>
+
+        <service android:name=".StubNonImeAccessibilityService"
+            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService"/>
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC"/>
+            </intent-filter>
+
+            <meta-data android:name="android.accessibilityservice"
+                android:resource="@xml/stub_non_ime_accessibility_service"/>
+        </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index bbc55619..c79d255 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -190,6 +190,10 @@
 
     <string name="stub_focus_indicator_service_description">com.android.accessibilityservice.cts.StubFocusIndicatorService</string>
 
+    <string name="stub_ime_accessibility_service_description">com.android.accessibilityservice.cts.StubImeAccessibilityService</string>
+
+    <string name="stub_non_ime_accessibility_service_description">com.android.accessibilityservice.cts.StubNonImeAccessibilityService</string>
+
     <!-- AccessibilityWindowQueryTest and AccessibilityReportingTest -->
 
     <!-- String title of embedded display activity -->
diff --git a/tests/accessibilityservice/res/xml/stub_ime_accessibility_service.xml b/tests/accessibilityservice/res/xml/stub_ime_accessibility_service.xml
new file mode 100644
index 0000000..17a1aeb
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_ime_accessibility_service.xml
@@ -0,0 +1,22 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:description="@string/stub_ime_accessibility_service_description"
+    android:accessibilityEventTypes="typeAllMask"
+    android:accessibilityFeedbackType="feedbackGeneric"
+    android:accessibilityFlags="flagInputMethodEditor"
+    android:notificationTimeout="0" />
diff --git a/tests/accessibilityservice/res/xml/stub_non_ime_accessibility_service.xml b/tests/accessibilityservice/res/xml/stub_non_ime_accessibility_service.xml
new file mode 100644
index 0000000..35d7fb4
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_non_ime_accessibility_service.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:description="@string/stub_non_ime_accessibility_service_description"
+    android:accessibilityEventTypes="typeAllMask"
+    android:accessibilityFeedbackType="feedbackGeneric"
+    android:notificationTimeout="0" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityImeTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityImeTest.java
new file mode 100644
index 0000000..2e599a2
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityImeTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2022 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.accessibilityservice.cts;
+
+import static android.accessibility.cts.common.InstrumentedAccessibilityService.disableAllServices;
+import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibilityservice.InputMethod;
+import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.platform.test.annotations.AppModeFull;
+import android.widget.EditText;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test one a11y service requiring ime capabilities and one doesn't.
+ */
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityImeTest {
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
+    private AccessibilityEndToEndActivity mActivity;
+
+    private ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    private EditText mEditText;
+    private String mInitialText;
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mDumpOnFailureRule);
+
+    @Before
+    public void setUp() throws Exception {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
+        sInstrumentation
+                .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        mActivity = launchActivityAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, mActivityRule);
+        // focus the edit text
+        mEditText = mActivity.findViewById(R.id.edittext);
+        // initial text
+        mInitialText = mActivity.getString(R.string.text_input_blah);
+        disableAllServices();
+    }
+
+    @Test
+    public void testInputConnection_requestIme() throws InterruptedException {
+        StubImeAccessibilityService stubImeAccessibilityService =
+                enableService(StubImeAccessibilityService.class);
+
+        CountDownLatch startInputLatch = new CountDownLatch(1);
+        stubImeAccessibilityService.setStartInputCountDownLatch(startInputLatch);
+
+        mActivity.runOnUiThread(() -> {
+            mEditText.requestFocus();
+            mEditText.setSelection(mInitialText.length(), mInitialText.length());
+        });
+
+        startInputLatch.await(2, TimeUnit.SECONDS);
+        assertNotNull(stubImeAccessibilityService.getInputMethod());
+        InputMethod.AccessibilityInputConnection connection =
+                stubImeAccessibilityService.getInputMethod().getCurrentInputConnection();
+        assertNotNull(connection);
+
+        CountDownLatch selectionChangeLatch = new CountDownLatch(1);
+        stubImeAccessibilityService.setSelectionChangeLatch(selectionChangeLatch);
+        stubImeAccessibilityService.setSelectionTarget(mInitialText.length() * 2);
+
+        connection.commitText(mInitialText, 1, null);
+
+        selectionChangeLatch.await(2, TimeUnit.SECONDS);
+        assertEquals(mInitialText + mInitialText, mEditText.getText().toString());
+    }
+
+    @Test
+    public void testInputConnection_notRequestIme() throws InterruptedException {
+        StubNonImeAccessibilityService stubNonImeAccessibilityService =
+                enableService(StubNonImeAccessibilityService.class);
+
+        CountDownLatch startInputLatch = new CountDownLatch(1);
+        stubNonImeAccessibilityService.setStartInputCountDownLatch(startInputLatch);
+
+        mActivity.runOnUiThread(() -> {
+            mEditText.requestFocus();
+            mEditText.setSelection(mInitialText.length(), mInitialText.length());
+        });
+
+        startInputLatch.await(2, TimeUnit.SECONDS);
+        assertNull(stubNonImeAccessibilityService.getInputMethod());
+    }
+
+    @Test
+    public void testSelectionChange_requestIme() throws InterruptedException {
+        StubImeAccessibilityService stubImeAccessibilityService =
+                enableService(StubImeAccessibilityService.class);
+
+        CountDownLatch startInputLatch = new CountDownLatch(1);
+        stubImeAccessibilityService.setStartInputCountDownLatch(startInputLatch);
+
+        mActivity.runOnUiThread(() -> {
+            mEditText.requestFocus();
+            mEditText.setSelection(mInitialText.length(), mInitialText.length());
+        });
+
+        final int targetPos = mInitialText.length() - 1;
+        startInputLatch.await(2, TimeUnit.SECONDS);
+        assertNotNull(stubImeAccessibilityService.getInputMethod());
+        InputMethod.AccessibilityInputConnection connection =
+                stubImeAccessibilityService.getInputMethod().getCurrentInputConnection();
+        assertNotNull(connection);
+
+        CountDownLatch selectionChangeLatch = new CountDownLatch(1);
+        stubImeAccessibilityService.setSelectionChangeLatch(selectionChangeLatch);
+        stubImeAccessibilityService.setSelectionTarget(targetPos);
+
+        connection.setSelection(targetPos, targetPos);
+        selectionChangeLatch.await(2, TimeUnit.SECONDS);
+
+        assertEquals(targetPos, mEditText.getSelectionStart());
+        assertEquals(targetPos, mEditText.getSelectionEnd());
+
+        assertEquals(targetPos, stubImeAccessibilityService.selStart);
+        assertEquals(targetPos, stubImeAccessibilityService.selEnd);
+    }
+
+    @Test
+    public void testSelectionChange_notRequestIme() throws InterruptedException {
+        StubNonImeAccessibilityService stubNonImeAccessibilityService =
+                enableService(StubNonImeAccessibilityService.class);
+
+        mActivity.runOnUiThread(() -> {
+            mEditText.requestFocus();
+            mEditText.setSelection(mInitialText.length(), mInitialText.length());
+        });
+
+        final int targetPos = mInitialText.length() - 1;
+        CountDownLatch selectionChangeLatch = new CountDownLatch(1);
+        stubNonImeAccessibilityService.setSelectionChangeLatch(selectionChangeLatch);
+        stubNonImeAccessibilityService.setSelectionTarget(targetPos);
+
+        mActivity.runOnUiThread(() -> {
+            mEditText.setSelection(targetPos, targetPos);
+        });
+        selectionChangeLatch.await(2, TimeUnit.SECONDS);
+
+        assertEquals(targetPos, mEditText.getSelectionStart());
+        assertEquals(targetPos, mEditText.getSelectionEnd());
+
+        assertEquals(-1, stubNonImeAccessibilityService.oldSelStart);
+        assertEquals(-1, stubNonImeAccessibilityService.oldSelEnd);
+        assertEquals(-1, stubNonImeAccessibilityService.selStart);
+        assertEquals(-1, stubNonImeAccessibilityService.selEnd);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubImeAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubImeAccessibilityService.java
new file mode 100644
index 0000000..6bfc66b
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubImeAccessibilityService.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.accessibilityservice.cts;
+
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.InputMethod;
+import android.view.inputmethod.EditorInfo;
+
+import androidx.annotation.NonNull;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A stub accessibility service to install for testing IME APIs
+ */
+public class StubImeAccessibilityService extends InstrumentedAccessibilityService {
+    private static final int INVALID = -11;
+    public int selStart = -1;
+    public int selEnd = -1;
+    public int oldSelStart = -1;
+    public int oldSelEnd = -1;
+    private CountDownLatch mStartInputLatch = null;
+    private CountDownLatch mSelectionChangeLatch = null;
+    private int mSelectionTarget = INVALID;
+
+    public void setStartInputCountDownLatch(CountDownLatch latch) {
+        mStartInputLatch = latch;
+    }
+
+    public void setSelectionChangeLatch(CountDownLatch latch) {
+        mSelectionChangeLatch = latch;
+    }
+    public void setSelectionTarget(int target) {
+        mSelectionTarget = target;
+    }
+
+    class InputMethodImpl extends InputMethod {
+        InputMethodImpl(@NonNull AccessibilityService service) {
+            super(service);
+        }
+
+        @Override
+        public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart,
+                int newSelEnd, int candidatesStart, int candidatesEnd) {
+            super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart,
+                    candidatesEnd);
+            // A11y receive placeholder notification.
+            if ((mSelectionChangeLatch != null) && (newSelStart == mSelectionTarget)) {
+                StubImeAccessibilityService.this.oldSelStart = oldSelStart;
+                StubImeAccessibilityService.this.oldSelEnd = oldSelEnd;
+                StubImeAccessibilityService.this.selStart = newSelStart;
+                StubImeAccessibilityService.this.selEnd = newSelEnd;
+                mSelectionChangeLatch.countDown();
+                mSelectionChangeLatch = null;
+            }
+        }
+
+        @Override
+        public void onStartInput(EditorInfo attribute, boolean restarting) {
+            super.onStartInput(attribute, restarting);
+            if (mStartInputLatch != null) {
+                mStartInputLatch.countDown();
+                mStartInputLatch = null;
+            }
+        }
+    }
+
+    @Override
+    public InputMethod onCreateInputMethod() {
+        return new InputMethodImpl(this);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubNonImeAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubNonImeAccessibilityService.java
new file mode 100644
index 0000000..7f6ff33
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubNonImeAccessibilityService.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2022 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.accessibilityservice.cts;
+
+/**
+ * A stub accessibility service to install for testing IME APIs
+ */
+public class StubNonImeAccessibilityService extends StubImeAccessibilityService {
+}