Commit cfa2ae6b authored by Changwan Ryu's avatar Changwan Ryu Committed by Commit Bot

[Android] Support initial surrounding text

This allows keyboard apps to avoid querying text around the selection,
which is required for predictive suggestions on keyboard.

The synchronous text querying process in InputConnection is known to
be very slow on low-end devices, and supporting the new API will reduce the time
taken in showing the keyboard.

This CL also ensures that the last text data in ImeAdapterImpl is correct.

Bug: b/162992484
Change-Id: I8137782536afcbfcd95200b77e3d1127b2b67031
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2340295Reviewed-by: default avatarShimi Zhang <ctzsm@chromium.org>
Commit-Queue: Changwan Ryu <changwan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795621}
parent 7174cfcd
......@@ -22,7 +22,7 @@ public interface ChromiumBaseInputConnection extends InputConnection {
public interface Factory {
ChromiumBaseInputConnection initializeAndGet(View view, ImeAdapterImpl imeAdapter,
int inputType, int inputFlags, int inputMode, int inputAction, int selectionStart,
int selectionEnd, EditorInfo outAttrs);
int selectionEnd, String lastText, EditorInfo outAttrs);
@VisibleForTesting
Handler getHandler();
......
......@@ -312,9 +312,10 @@ public class ImeAdapterImpl
}
if (mInputConnectionFactory == null) return null;
View containerView = getContainerView();
if (DEBUG_LOGS) Log.i(TAG, "Last text: " + mLastText);
setInputConnection(mInputConnectionFactory.initializeAndGet(containerView, this,
mTextInputType, mTextInputFlags, mTextInputMode, mTextInputAction,
mLastSelectionStart, mLastSelectionEnd, outAttrs));
mLastSelectionStart, mLastSelectionEnd, mLastText, outAttrs));
if (DEBUG_LOGS) Log.i(TAG, "onCreateInputConnection: " + mInputConnection);
if (mCursorAnchorInfoController != null) {
......
......@@ -4,6 +4,8 @@
package org.chromium.content.browser.input;
import android.annotation.TargetApi;
import android.os.Build;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
......@@ -14,6 +16,7 @@ import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.VerifiesOnR;
import org.chromium.blink_public.web.WebTextInputFlags;
import org.chromium.blink_public.web.WebTextInputMode;
import org.chromium.ui.base.ime.TextInputAction;
......@@ -25,6 +28,26 @@ import java.util.Locale;
* Utilities for IME such as computing outAttrs, and dumping object information.
*/
public class ImeUtils {
/**
* A class to contain R-specific code inside a separate class to avoid performance regression.
*
* See
* https://source.chromium.org/chromium/chromium/src/+/master:build/android/docs/class_verification_failures.md
* for details.
*/
@VerifiesOnR
@TargetApi(Build.VERSION_CODES.R)
private static final class HelperForR {
/** see {@link EditorInfo#setInitialSurroundingText(EditorInfo, String)} */
public static void setInitialSurroundingText(EditorInfo outAttrs, String lastText) {
// Note: Android's internal implementation trims the text up to 2048 chars before
// sending it to the IMM service. In the future, if we consider limiting the number of
// chars between renderer and browser, then consider calling
// setInitialSurroundingSubText() instead.
outAttrs.setInitialSurroundingText(lastText);
}
}
/**
* Compute {@link EditorInfo} based on the given parameters. This is needed for
* {@link View#onCreateInputConnection(EditorInfo)}.
......@@ -38,7 +61,8 @@ public class ImeUtils {
* @param outAttrs An instance of {@link EditorInfo} that we are going to change.
*/
public static void computeEditorInfo(int inputType, int inputFlags, int inputMode,
int inputAction, int initialSelStart, int initialSelEnd, EditorInfo outAttrs) {
int inputAction, int initialSelStart, int initialSelEnd, String lastText,
EditorInfo outAttrs) {
outAttrs.inputType =
EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
......@@ -135,6 +159,9 @@ public class ImeUtils {
outAttrs.initialSelStart = initialSelStart;
outAttrs.initialSelEnd = initialSelEnd;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
HelperForR.setInitialSurroundingText(outAttrs, lastText);
}
}
private static int getImeAction(int inputType, int inputFlags, int inputMode, int inputAction,
......
......@@ -118,13 +118,13 @@ public class ThreadedInputConnectionFactory implements ChromiumBaseInputConnecti
@Override
public ThreadedInputConnection initializeAndGet(View view, ImeAdapterImpl imeAdapter,
int inputType, int inputFlags, int inputMode, int inputAction, int selectionStart,
int selectionEnd, EditorInfo outAttrs) {
int selectionEnd, String lastText, EditorInfo outAttrs) {
ImeUtils.checkOnUiThread();
// Compute outAttrs early in case we early out to prevent reentrancy. (crbug.com/636197)
// TODO(changwan): move this up to ImeAdapter once ReplicaInputConnection is deprecated.
ImeUtils.computeEditorInfo(inputType, inputFlags, inputMode, inputAction, selectionStart,
selectionEnd, outAttrs);
selectionEnd, lastText, outAttrs);
if (DEBUG_LOGS) {
Log.i(TAG, "initializeAndGet. outAttr: " + ImeUtils.getEditorInfoDebugString(outAttrs));
}
......
......@@ -251,6 +251,10 @@ class ImeActivityTestRule extends ContentShellActivityTestRule {
+ ", input action history: " + Arrays.deepToString(inputActionHistory);
}
String[] getLastTextHistory() {
return mConnectionFactory.getTextInputLastTextHistory();
}
void waitForEditorAction(final int expectedAction) {
CriteriaHelper.pollUiThread(() -> {
EditorInfo editorInfo = mConnectionFactory.getOutAttrs();
......@@ -306,6 +310,11 @@ class ImeActivityTestRule extends ContentShellActivityTestRule {
});
}
void verifyNoUpdateSelection() {
final List<Pair<Range, Range>> states = mInputMethodManagerWrapper.getUpdateSelectionList();
Assert.assertEquals(0, states.size());
}
void waitAndVerifyUpdateSelection(final int index, final int selectionStart,
final int selectionEnd, final int compositionStart, final int compositionEnd) {
final List<Pair<Range, Range>> states = mInputMethodManagerWrapper.getUpdateSelectionList();
......@@ -590,6 +599,7 @@ class ImeActivityTestRule extends ContentShellActivityTestRule {
private final List<Integer> mTextInputTypeList = new ArrayList<>();
private final List<Integer> mTextInputModeList = new ArrayList<>();
private final List<Integer> mTextInputActionList = new ArrayList<>();
private final List<String> mTextInputLastTextList = new ArrayList<>();
private EditorInfo mOutAttrs;
public TestInputConnectionFactory(ChromiumBaseInputConnection.Factory factory) {
......@@ -599,13 +609,14 @@ class ImeActivityTestRule extends ContentShellActivityTestRule {
@Override
public ChromiumBaseInputConnection initializeAndGet(View view, ImeAdapterImpl imeAdapter,
int inputType, int inputFlags, int inputMode, int inputAction, int selectionStart,
int selectionEnd, EditorInfo outAttrs) {
int selectionEnd, String lastText, EditorInfo outAttrs) {
mTextInputTypeList.add(inputType);
mTextInputModeList.add(inputMode);
mTextInputActionList.add(inputAction);
mTextInputLastTextList.add(lastText);
mOutAttrs = outAttrs;
return mFactory.initializeAndGet(view, imeAdapter, inputType, inputFlags, inputMode,
inputAction, selectionStart, selectionEnd, outAttrs);
inputAction, selectionStart, selectionEnd, lastText, outAttrs);
}
@Override
......@@ -623,6 +634,7 @@ class ImeActivityTestRule extends ContentShellActivityTestRule {
mTextInputTypeList.clear();
mTextInputModeList.clear();
mTextInputActionList.clear();
mTextInputLastTextList.clear();
}
public Integer[] getTextInputModeHistory() {
......@@ -637,6 +649,12 @@ class ImeActivityTestRule extends ContentShellActivityTestRule {
return result;
}
public String[] getTextInputLastTextHistory() {
String[] result = new String[mTextInputLastTextList.size()];
mTextInputLastTextList.toArray(result);
return result;
}
public EditorInfo getOutAttrs() {
return mOutAttrs;
}
......
......@@ -472,7 +472,7 @@ public class ImeTest {
Assert.assertEquals(EditorInfo.IME_ACTION_NONE, getImeAction(editorInfoList.get(4)));
// search1.
Assert.assertEquals(EditorInfo.IME_ACTION_SEARCH, getImeAction(editorInfoList.get(5)));
// input_text1.
// input_text3.
Assert.assertEquals(EditorInfo.IME_ACTION_GO, getImeAction(editorInfoList.get(6)));
mRule.resetAllStates();
......@@ -1709,4 +1709,37 @@ public class ImeTest {
mRule.getConnectionFactory().getOutAttrs().inputType
& EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT);
}
@Test
@SmallTest
@Feature({"TextInput"})
public void testLastText() throws Exception {
// Hide the keyboard first.
DOMUtils.focusNode(mRule.getWebContents(), "input_radio");
mRule.assertWaitForKeyboardStatus(false);
mRule.verifyNoUpdateSelection();
// Focus on input_text1 which has 'sometext' in it.
DOMUtils.focusNode(mRule.getWebContents(), "input_text1");
mRule.assertWaitForKeyboardStatus(true);
// By the time the keyboard is shown, we should have the correct last text to pass to
// EditorInfo in onCreateInputConnection(...).
Assert.assertArrayEquals(new String[] {"sometext"}, mRule.getLastTextHistory());
// Hide the keyboard again.
DOMUtils.focusNode(mRule.getWebContents(), "input_radio");
mRule.assertWaitForKeyboardStatus(false);
// Focus on input_text2 which has 'othertext' in it.
DOMUtils.focusNode(mRule.getWebContents(), "input_text2");
mRule.assertWaitForKeyboardStatus(true);
// By the time the keyboard is shown, we should have the correct last text to pass to
// EditorInfo in onCreateInputConnection(...).
Assert.assertArrayEquals(
new String[] {"sometext", "othertext"}, mRule.getLastTextHistory());
}
}
......@@ -163,7 +163,7 @@ public class ThreadedInputConnectionFactoryTest {
@Override
public InputConnection call() {
return mFactory.initializeAndGet(
mContainerView, mImeAdapter, 1, 0, 0, 0, 0, 0, mEditorInfo);
mContainerView, mImeAdapter, 1, 0, 0, 0, 0, 0, "", mEditorInfo);
}
};
when(mProxyView.onCreateInputConnection(any(EditorInfo.class)))
......@@ -206,7 +206,7 @@ public class ThreadedInputConnectionFactoryTest {
@Override
public void run() {
assertNull(mFactory.initializeAndGet(
mContainerView, mImeAdapter, 1, 0, 0, 0, 0, 0, mEditorInfo));
mContainerView, mImeAdapter, 1, 0, 0, 0, 0, 0, "", mEditorInfo));
}
});
}
......
......@@ -8,8 +8,10 @@
</head>
<body>
<form action="about:blank">
<input id="input_text" type="text" size="10">
<input id="input_text" type="text" size="10"><br>
</form>
<input id="input_text1" type="text" size="10" value="sometext"><br>
<input id="input_text2" type="text" size="10" value="othertext"><br>
<form>
<textarea id="textarea" rows="2" cols="10"></textarea>
<textarea id="textarea2" rows="2" cols="10" autocomplete="off"></textarea>
......@@ -19,8 +21,8 @@
would get snapped to the surrounding <input> elements. -->
<span id="plain_text">This is Plain Text One</span><br><br>
<div id="contenteditable1" contenteditable>contenteditable1</div>
<input id="search1" type="search" size="10" size="10">
<input id="input_text1" type="text" size="10" size="10"><br>
<input id="search1" type="search" size="10">
<input id="input_text3" type="text" size="10"><br>
<input id="input_radio" type="radio" style="width:50px;height:50px"><br>
</form>
<form>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment