blob: 5953349fc6ffc6b154d91ff04bf0b8ef2af2f369 [file] [log] [blame]
/*
* 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.enableService;
import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
import static org.junit.Assert.assertTrue;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
import android.accessibilityservice.InputMethod;
import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
import android.accessibilityservice.cts.utils.AsyncUtils;
import android.accessibilityservice.cts.utils.InputConnectionSplitter;
import android.accessibilityservice.cts.utils.NoOpInputConnection;
import android.accessibilityservice.cts.utils.RunOnMainUtils;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.Presubmit;
import android.text.InputType;
import android.text.TextUtils;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.widget.EditText;
import android.widget.LinearLayout;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.CddTest;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
/**
* Tests for {@link InputMethod.AccessibilityInputConnection}.
*/
@LargeTest
@AppModeFull
@RunWith(AndroidJUnit4.class)
@CddTest(requirements = {"3.10/C-1-1,C-1-2"})
@Presubmit
public final class AccessibilityInputConnectionTest {
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
private static StubImeAccessibilityService sStubImeAccessibilityService;
private ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
new AccessibilityDumpOnFailureRule();
private AtomicReference<InputConnection> mLastInputConnectionSpy = new AtomicReference<>();
@Rule
public final RuleChain mRuleChain = RuleChain
.outerRule(mActivityRule)
.around(mDumpOnFailureRule);
@BeforeClass
public static void oneTimeSetup() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
sUiAutomation = sInstrumentation.getUiAutomation();
sInstrumentation
.getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
sStubImeAccessibilityService = enableService(StubImeAccessibilityService.class);
}
@AfterClass
public static void postTestTearDown() {
sStubImeAccessibilityService.disableSelfAndRemove();
sUiAutomation.destroy();
}
@Before
public void setUp() throws Exception {
final String markerValue = "Test-" + SystemClock.elapsedRealtimeNanos();
final CountDownLatch startInputLatch = new CountDownLatch(1);
sStubImeAccessibilityService.setOnStartInputCallback(((editorInfo, restarting) -> {
if (editorInfo != null && TextUtils.equals(markerValue, editorInfo.privateImeOptions)) {
startInputLatch.countDown();
}
}));
final AccessibilityEndToEndActivity activity = launchActivityAndWaitForItToBeOnscreen(
sInstrumentation, sUiAutomation, mActivityRule);
final LinearLayout layout = (LinearLayout) activity.findViewById(R.id.edittext).getParent();
sInstrumentation.runOnMainSync(() -> {
final EditText editText = new EditText(activity) {
@Override
public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
final InputConnection ic = super.onCreateInputConnection(editorInfo);
// For some reasons, Mockito.spy() for real Framework classes did not work...
// Use NoOpInputConnection/InputConnectionSplitter instead.
final InputConnection spy = Mockito.spy(new NoOpInputConnection());
if (mLastInputConnectionSpy.get() == null) {
mLastInputConnectionSpy.set(spy);
}
return new InputConnectionSplitter(ic, spy);
}
};
editText.setPrivateImeOptions(markerValue);
layout.addView(editText);
editText.requestFocus();
});
// Wait until EditorInfo#privateImeOptions becomes the expected marker value.
assertTrue("time out waiting for input to start",
startInputLatch.await(AsyncUtils.DEFAULT_TIMEOUT_MS, MILLISECONDS));
}
private InputMethod.AccessibilityInputConnection getInputConnection() {
return RunOnMainUtils.getOnMain(
sInstrumentation,
() -> sStubImeAccessibilityService.getInputMethod().getCurrentInputConnection());
}
private InputConnection resetAndGetLastInputConnectionSpy() {
final InputConnection spy = mLastInputConnectionSpy.get();
Mockito.reset(spy);
return spy;
}
@Test
public void testCommitText() {
final InputMethod.AccessibilityInputConnection ic = getInputConnection();
final InputConnection spy = resetAndGetLastInputConnectionSpy();
ic.commitText("test", 1, null);
Mockito.verify(spy, Mockito.timeout(AsyncUtils.DEFAULT_TIMEOUT_MS))
.commitText("test", 1, null);
}
@Test
public void testSetSelection() {
final InputMethod.AccessibilityInputConnection ic = getInputConnection();
final InputConnection spy = resetAndGetLastInputConnectionSpy();
ic.setSelection(1, 2);
Mockito.verify(spy, Mockito.timeout(AsyncUtils.DEFAULT_TIMEOUT_MS)).setSelection(1, 2);
}
@Test
public void testGetSurroundingText() {
final InputMethod.AccessibilityInputConnection ic = getInputConnection();
final InputConnection spy = resetAndGetLastInputConnectionSpy();
ic.getSurroundingText(1, 2, InputConnection.GET_TEXT_WITH_STYLES);
Mockito.verify(spy, Mockito.timeout(AsyncUtils.DEFAULT_TIMEOUT_MS))
.getSurroundingText(1, 2, InputConnection.GET_TEXT_WITH_STYLES);
}
@Test
public void testDeleteSurroundingText() {
final InputMethod.AccessibilityInputConnection ic = getInputConnection();
final InputConnection spy = resetAndGetLastInputConnectionSpy();
ic.deleteSurroundingText(2, 1);
Mockito.verify(spy, Mockito.timeout(AsyncUtils.DEFAULT_TIMEOUT_MS))
.deleteSurroundingText(2, 1);
}
@Test
public void testSendKeyEvent() {
final InputMethod.AccessibilityInputConnection ic = getInputConnection();
final InputConnection spy = resetAndGetLastInputConnectionSpy();
final long eventTime = SystemClock.uptimeMillis();
final KeyEvent keyEvent = new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
ic.sendKeyEvent(keyEvent);
Mockito.verify(spy, Mockito.timeout(AsyncUtils.DEFAULT_TIMEOUT_MS))
.sendKeyEvent(keyEvent);
}
@Test
@FlakyTest
public void testPerformEditorAction() {
final InputMethod.AccessibilityInputConnection ic = getInputConnection();
final InputConnection spy = resetAndGetLastInputConnectionSpy();
ic.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
Mockito.verify(spy, Mockito.timeout(AsyncUtils.DEFAULT_TIMEOUT_MS))
.performEditorAction(EditorInfo.IME_ACTION_PREVIOUS);
}
@Test
public void testPerformContextMenuAction() {
final InputMethod.AccessibilityInputConnection ic = getInputConnection();
final InputConnection spy = resetAndGetLastInputConnectionSpy();
ic.performContextMenuAction(android.R.id.selectAll);
Mockito.verify(spy, Mockito.timeout(AsyncUtils.DEFAULT_TIMEOUT_MS))
.performContextMenuAction(android.R.id.selectAll);
}
@Test
public void testGetCursorCapsMode() {
final InputMethod.AccessibilityInputConnection ic = getInputConnection();
final InputConnection spy = resetAndGetLastInputConnectionSpy();
ic.getCursorCapsMode(InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
Mockito.verify(spy, Mockito.timeout(AsyncUtils.DEFAULT_TIMEOUT_MS))
.getCursorCapsMode(InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
}
@Test
public void testClearMetaKeyStates() {
final InputMethod.AccessibilityInputConnection ic = getInputConnection();
final InputConnection spy = resetAndGetLastInputConnectionSpy();
ic.clearMetaKeyStates(KeyEvent.META_SHIFT_ON);
Mockito.verify(spy, Mockito.timeout(AsyncUtils.DEFAULT_TIMEOUT_MS))
.clearMetaKeyStates(KeyEvent.META_SHIFT_ON);
}
}