IMF: Add requestTextBoundsInfo CTS
Bug: 265457058
Test: atest InputConnectionEndToEndTest
Change-Id: Iedbbb4bf70f304e104b1a60bbfb07cb98bb3744c
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 91b227a..0c75cd0 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -36,6 +36,7 @@
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.RectF;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.Bundle;
@@ -86,6 +87,7 @@
import android.view.inputmethod.SelectGesture;
import android.view.inputmethod.SelectRangeGesture;
import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextBoundsInfoResult;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
@@ -462,6 +464,15 @@
.performHandwritingGesture(gesture, Runnable::run, consumer);
return ImeEvent.RETURN_VALUE_UNAVAILABLE;
}
+ case "requestTextBoundsInfo": {
+ var rectF = command.getExtras().getParcelable("rectF", RectF.class);
+ Consumer<TextBoundsInfoResult> consumer = value ->
+ getTracer().onRequestTextBoundsInfoResult(
+ value, command.getId());
+ getMemorizedOrCurrentInputConnection().requestTextBoundsInfo(
+ rectF, mMainHandler::post, consumer);
+ return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+ }
case "requestCursorUpdates": {
final int cursorUpdateMode = command.getExtras().getInt("cursorUpdateMode");
final int cursorUpdateFilter =
@@ -1715,6 +1726,14 @@
recordEventInternal("onPerformHandwritingGestureResult", runnable, arguments);
}
+ public void onRequestTextBoundsInfoResult(TextBoundsInfoResult result, long requestId) {
+ final Bundle arguments = new Bundle();
+ arguments.putInt("resultCode", result.getResultCode());
+ arguments.putParcelable("boundsInfo", result.getTextBoundsInfo());
+ arguments.putLong("requestId", requestId);
+ recordEventInternal("onRequestTextBoundsInfoResult", () -> {}, arguments);
+ }
+
void getWindowLayoutInfo(@NonNull WindowLayoutInfo windowLayoutInfo,
@NonNull Runnable runnable) {
final Bundle arguments = new Bundle();
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 762e813..8031dc2 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -30,6 +30,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -1257,9 +1258,8 @@
* {@link InputConnection#performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)}
* with the given parameters.
*
- * <p>Use {@link ImeEvent#getReturnIntegerValue()} for {@link ImeEvent} returned from
- * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
- * value returned from the API.</p>
+ * <p>The result callback will be recorded as an {@code onPerformHandwritingGestureResult}
+ * event.
*
* <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
*
@@ -1277,6 +1277,25 @@
}
/**
+ * Lets {@link MockIme} to call {@link InputConnection#requestTextBoundsInfo}.
+ *
+ * <p>The result callback will be recorded as an {@code onRequestTextBoundsInfoResult} event.
+ *
+ * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+ *
+ * @param gesture {@link SelectGesture} or {@link InsertGesture} or {@link DeleteGesture}.
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}.
+ */
+ @NonNull
+ public ImeCommand callRequestTextBoundsInfo(RectF rectF) {
+ final Bundle params = new Bundle();
+ params.putParcelable("rectF", rectF);
+ return callCommandInternal("requestTextBoundsInfo", params);
+ }
+
+ /**
* Lets {@link MockIme} to call
* {@link InputConnection#previewHandwritingGesture(PreviewableHandwritingGesture,
* CancellationSignal)} with the given parameters.
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
index 39c506a..8a88c2c 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
@@ -39,6 +39,7 @@
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 static org.junit.Assert.fail;
@@ -79,6 +80,8 @@
import android.view.inputmethod.SelectRangeGesture;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextBoundsInfo;
+import android.view.inputmethod.TextBoundsInfoResult;
import android.view.inputmethod.TextSnapshot;
import android.view.inputmethod.cts.util.EndToEndImeTestBase;
import android.view.inputmethod.cts.util.MockTestActivityUtil;
@@ -106,7 +109,9 @@
import com.google.common.truth.Correspondence;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ErrorCollector;
import org.junit.runner.RunWith;
import java.util.ArrayList;
@@ -146,6 +151,9 @@
return TEST_MARKER_PREFIX + "/" + SystemClock.elapsedRealtimeNanos();
}
+ @Rule
+ public final ErrorCollector mErrorCollector = new ErrorCollector();
+
/**
* A utility method to verify a method is called within a certain timeout period then block
* it by {@link BlockingMethodVerifier#close()} is called.
@@ -1118,6 +1126,71 @@
});
}
+ @Test
+ @ApiTest(apis = {"android.view.inputmethod.InputConnection#requestTextBoundsInfo"})
+ public void testRequestTextBoundsInfo() throws Exception {
+ final var methodCallVerifier = new MethodCallVerifier();
+ final var tbiResult = new TextBoundsInfoResult(TextBoundsInfoResult.CODE_FAILED, null);
+
+ final class Wrapper extends InputConnectionWrapper {
+ private Wrapper(InputConnection target) {
+ super(target, false);
+ }
+
+ @Override
+ public void requestTextBoundsInfo(RectF rectF, Executor executor,
+ Consumer<TextBoundsInfoResult> consumer) {
+ mErrorCollector.checkSucceeds(() -> {
+ methodCallVerifier.onMethodCalled(args -> {
+ args.putParcelable("rectF", rectF);
+ });
+
+ var called = new boolean[1];
+ executor.execute(() -> {
+ called[0] = true;
+ consumer.accept(tbiResult);
+ });
+ assertTrue("editor-side executor must be Runnable::run", called[0]);
+ return null;
+ });
+ }
+ }
+
+ testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+ final RectF rectF = new RectF(1f, 2f, 3f, 4f);
+ final ImeCommand command = session.callRequestTextBoundsInfo(rectF);
+ methodCallVerifier.expectCalledOnce(args -> {
+ assertEquals(rectF, args.getParcelable("rectF", RectF.class));
+ }, TIMEOUT);
+ expectCommand(stream, command, TIMEOUT);
+ var event = expectEvent(stream, onRequestTextBoundsInfoResultMatcher(command.getId()),
+ TIMEOUT);
+ var actualResultCode = event.getArguments().getInt("resultCode");
+ var actualBoundsInfo = event.getArguments().getParcelable("boundsInfo",
+ TextBoundsInfo.class);
+
+ assertEquals(TextBoundsInfoResult.CODE_FAILED, actualResultCode);
+ assertNull(actualBoundsInfo);
+ });
+ }
+
+ @Test
+ public void testRequestTextBoundsInfo_unimplemented() throws Exception {
+ testMinimallyImplementedInputConnection((session, stream) -> {
+ final RectF rectF = new RectF(1f, 2f, 3f, 4f);
+ final ImeCommand command = session.callRequestTextBoundsInfo(rectF);
+ expectCommand(stream, command, TIMEOUT);
+ var event = expectEvent(stream, onRequestTextBoundsInfoResultMatcher(command.getId()),
+ TIMEOUT);
+ var actualResultCode = event.getArguments().getInt("resultCode");
+ var actualBoundsInfo = event.getArguments().getParcelable("boundsInfo",
+ TextBoundsInfo.class);
+
+ assertEquals(TextBoundsInfoResult.CODE_UNSUPPORTED, actualResultCode);
+ assertNull(actualBoundsInfo);
+ });
+ }
+
/**
* Test {@link InputConnection#getSurroundingText(int, int, int)} works as expected.
*/
@@ -1796,6 +1869,16 @@
};
}
+ private static Predicate<ImeEvent> onRequestTextBoundsInfoResultMatcher(
+ long requestId) {
+ return withDescription("onRequestTextBoundsInfoResult(" + requestId + ")", event -> {
+ if (!TextUtils.equals("onRequestTextBoundsInfoResult", event.getEventName())) {
+ return false;
+ }
+ return event.getArguments().getLong("requestId") == requestId;
+ });
+ }
+
/**
* Test
* {@link InputConnection#previewHandwritingGesture(HandwritingGesture, CancellationSignal)}