Merge "Refine surrounding text logic and test case readability."
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index c80a1ae..7f90d57 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -33,11 +33,11 @@
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.Objects;
/**
* An EditorInfo describes several attributes of a text editing object
@@ -558,7 +558,7 @@
* editor wants to trim out the first 10 chars, subTextStart should be 10.
*/
public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) {
- Preconditions.checkNotNull(subText);
+ Objects.requireNonNull(subText);
// Swap selection start and end if necessary.
final int subTextSelStart = initialSelStart > initialSelEnd
@@ -585,25 +585,35 @@
return;
}
- // The input text is too long. Let's try to trim it reasonably. Fundamental rules are:
- // 1. Text before the cursor is the most important information to IMEs.
- // 2. Text after the cursor is the second important information to IMEs.
- // 3. Selected text is the least important information but it shall NEVER be truncated.
- // When it is too long, just drop it.
- //
- // Source: <TextBeforeCursor><Selection><TextAfterCursor>
- // Possible results:
- // 1. <(maybeTrimmedAtHead)TextBeforeCursor><Selection><TextAfterCursor(maybeTrimmedAtTail)>
- // 2. <(maybeTrimmedAtHead)TextBeforeCursor><TextAfterCursor(maybeTrimmedAtTail)>
- //
- final int sourceSelLength = subTextSelEnd - subTextSelStart;
+ trimLongSurroundingText(subText, subTextSelStart, subTextSelEnd);
+ }
+
+ /**
+ * Trims the initial surrounding text when it is over sized. Fundamental trimming rules are:
+ * - The text before the cursor is the most important information to IMEs.
+ * - The text after the cursor is the second important information to IMEs.
+ * - The selected text is the least important information but it shall NEVER be truncated. When
+ * it is too long, just drop it.
+ *<p><pre>
+ * For example, the subText can be viewed as
+ * TextBeforeCursor + Selection + TextAfterCursor
+ * The result could be
+ * 1. (maybeTrimmedAtHead)TextBeforeCursor + Selection + TextAfterCursor(maybeTrimmedAtTail)
+ * 2. (maybeTrimmedAtHead)TextBeforeCursor + TextAfterCursor(maybeTrimmedAtTail)</pre>
+ *
+ * @param subText The long text that needs to be trimmed.
+ * @param selStart The text offset of the start of the selection.
+ * @param selEnd The text offset of the end of the selection
+ */
+ private void trimLongSurroundingText(CharSequence subText, int selStart, int selEnd) {
+ final int sourceSelLength = selEnd - selStart;
// When the selected text is too long, drop it.
final int newSelLength = (sourceSelLength > MAX_INITIAL_SELECTION_LENGTH)
? 0 : sourceSelLength;
// Distribute rest of length quota to TextBeforeCursor and TextAfterCursor in 4:1 ratio.
- final int subTextBeforeCursorLength = subTextSelStart;
- final int subTextAfterCursorLength = subTextLength - subTextSelEnd;
+ final int subTextBeforeCursorLength = selStart;
+ final int subTextAfterCursorLength = subText.length() - selEnd;
final int maxLengthMinusSelection = MEMORY_EFFICIENT_TEXT_LENGTH - newSelLength;
final int possibleMaxBeforeCursorLength =
Math.min(subTextBeforeCursorLength, (int) (0.8 * maxLengthMinusSelection));
@@ -617,24 +627,23 @@
// We don't want to cut surrogate pairs in the middle. Exam that at the new head and tail.
if (isCutOnSurrogate(subText,
- subTextSelStart - newBeforeCursorLength, TrimPolicy.HEAD)) {
+ selStart - newBeforeCursorLength, TrimPolicy.HEAD)) {
newBeforeCursorHead = newBeforeCursorHead + 1;
newBeforeCursorLength = newBeforeCursorLength - 1;
}
if (isCutOnSurrogate(subText,
- subTextSelEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) {
+ selEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) {
newAfterCursorLength = newAfterCursorLength - 1;
}
// Now we know where to trim, compose the initialSurroundingText.
final int newTextLength = newBeforeCursorLength + newSelLength + newAfterCursorLength;
- CharSequence newInitialSurroundingText;
+ final CharSequence newInitialSurroundingText;
if (newSelLength != sourceSelLength) {
final CharSequence beforeCursor = subText.subSequence(newBeforeCursorHead,
newBeforeCursorHead + newBeforeCursorLength);
-
- final CharSequence afterCursor = subText.subSequence(subTextSelEnd,
- subTextSelEnd + newAfterCursorLength);
+ final CharSequence afterCursor = subText.subSequence(selEnd,
+ selEnd + newAfterCursorLength);
newInitialSurroundingText = TextUtils.concat(beforeCursor, afterCursor);
} else {
@@ -651,15 +660,16 @@
}
/**
- * Get <var>n</var> characters of text before the current cursor position. May be {@code null}
- * when the protocol is not supported.
+ * Get <var>length</var> characters of text before the current cursor position. May be
+ * {@code null} when the protocol is not supported.
*
* @param length The expected length of the text.
* @param flags Supplies additional options controlling how the text is returned. May be
* either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
* @return the text before the cursor position; the length of the returned text might be less
- * than <var>n</var>. When there is no text before the cursor, an empty string will be returned.
- * It could also be {@code null} when the editor or system could not support this protocol.
+ * than <var>length</var>. When there is no text before the cursor, an empty string will be
+ * returned. It could also be {@code null} when the editor or system could not support this
+ * protocol.
*/
@Nullable
public CharSequence getInitialTextBeforeCursor(int length, int flags) {
@@ -667,8 +677,8 @@
}
/**
- * Gets the selected text, if any. May be {@code null} when no text is selected or the selected
- * text is way too long.
+ * Gets the selected text, if any. May be {@code null} when the protocol is not supported or the
+ * selected text is way too long.
*
* @param flags Supplies additional options controlling how the text is returned. May be
* either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
@@ -693,15 +703,16 @@
}
/**
- * Get <var>n</var> characters of text after the current cursor position. May be {@code null}
- * when the protocol is not supported.
+ * Get <var>length</var> characters of text after the current cursor position. May be
+ * {@code null} when the protocol is not supported.
*
* @param length The expected length of the text.
* @param flags Supplies additional options controlling how the text is returned. May be
* either 0 or {@link InputConnection#GET_TEXT_WITH_STYLES}.
* @return the text after the cursor position; the length of the returned text might be less
- * than <var>n</var>. When there is no text after the cursor, an empty string will be returned.
- * It could also be {@code null} when the editor or system could not support this protocol.
+ * than <var>length</var>. When there is no text after the cursor, an empty string will be
+ * returned. It could also be {@code null} when the editor or system could not support this
+ * protocol.
*/
@Nullable
public CharSequence getInitialTextAfterCursor(int length, int flags) {
@@ -863,7 +874,6 @@
return 0;
}
- // TODO(b/148035211): Unit tests for this class
static final class InitialSurroundingText implements Parcelable {
@Nullable final CharSequence mSurroundingText;
final int mSelectionHead;
diff --git a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
index 12c057f..02ffc00 100644
--- a/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java
@@ -20,11 +20,11 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.UserHandle;
-import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@@ -41,6 +41,7 @@
@RunWith(AndroidJUnit4.class)
public class EditorInfoTest {
private static final int TEST_USER_ID = 42;
+ private static final int LONG_EXP_TEXT_LENGTH = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH * 2;
/**
* Makes sure that {@code null} {@link EditorInfo#targetInputMethodUser} can be copied via
@@ -79,8 +80,8 @@
}
@Test
- public void testNullTextInputComposeInitialSurroundingText() {
- final Spannable testText = null;
+ public void setInitialText_nullInputText_throwsException() {
+ final CharSequence testText = null;
final EditorInfo editorInfo = new EditorInfo();
try {
@@ -92,56 +93,75 @@
}
@Test
- public void testNonNullTextInputComposeInitialSurroundingText() {
- final Spannable testText = createTestText(/* prependLength= */ 0,
- EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
- final EditorInfo editorInfo = new EditorInfo();
+ public void setInitialText_cursorAtHead_dividesByCursorPosition() {
+ final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
- // Cursor at position 0.
- int selectionLength = 0;
+ final EditorInfo editorInfo = new EditorInfo();
+ final int selectionLength = 0;
editorInfo.initialSelStart = 0;
editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
- int expectedTextBeforeCursorLength = 0;
- int expectedTextAfterCursorLength = testText.length();
+ final int expectedTextBeforeCursorLength = 0;
+ final int expectedTextAfterCursorLength = testText.length();
editorInfo.setInitialSurroundingText(testText);
assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
expectedTextAfterCursorLength);
+ }
- // Cursor at the end.
+ @Test
+ public void setInitialText_cursorAtTail_dividesByCursorPosition() {
+ final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+ final EditorInfo editorInfo = new EditorInfo();
+ final int selectionLength = 0;
editorInfo.initialSelStart = testText.length() - selectionLength;
editorInfo.initialSelEnd = testText.length();
- expectedTextBeforeCursorLength = testText.length();
- expectedTextAfterCursorLength = 0;
+ final int expectedTextBeforeCursorLength = testText.length();
+ final int expectedTextAfterCursorLength = 0;
editorInfo.setInitialSurroundingText(testText);
assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
expectedTextAfterCursorLength);
+ }
- // Cursor at the middle.
- selectionLength = 2;
+ @Test
+ public void setInitialText_cursorAtMiddle_dividesByCursorPosition() {
+ final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+ final EditorInfo editorInfo = new EditorInfo();
+ final int selectionLength = 2;
editorInfo.initialSelStart = testText.length() / 2;
editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
- expectedTextBeforeCursorLength = editorInfo.initialSelStart;
- expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd;
+ final int expectedTextBeforeCursorLength = editorInfo.initialSelStart;
+ final int expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd;
editorInfo.setInitialSurroundingText(testText);
assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
expectedTextAfterCursorLength);
+ }
- // Accidentally swap selection start and end.
+ @Test
+ public void setInitialText_incorrectCursorOrder_correctsThenDivide() {
+ final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+ final EditorInfo editorInfo = new EditorInfo();
+ final int selectionLength = 2;
editorInfo.initialSelEnd = testText.length() / 2;
editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength;
+ final int expectedTextBeforeCursorLength = testText.length() / 2;
+ final int expectedTextAfterCursorLength = testText.length() - testText.length() / 2
+ - selectionLength;
editorInfo.setInitialSurroundingText(testText);
assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
expectedTextAfterCursorLength);
+ }
- // Invalid cursor position.
+ @Test
+ public void setInitialText_invalidCursorPosition_returnsNull() {
+ final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+ final EditorInfo editorInfo = new EditorInfo();
editorInfo.initialSelStart = -1;
editorInfo.setInitialSurroundingText(testText);
@@ -153,64 +173,33 @@
}
@Test
- public void testTooLongTextInputComposeInitialSurroundingText() {
- final Spannable testText = createTestText(/* prependLength= */ 0,
- EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2);
+ public void setOverSizeInitialText_cursorAtMiddle_dividesProportionately() {
+ final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2);
final EditorInfo editorInfo = new EditorInfo();
-
- // Cursor at position 0.
- int selectionLength = 0;
- editorInfo.initialSelStart = 0;
- editorInfo.initialSelEnd = 0 + selectionLength;
- int expectedTextBeforeCursorLength = 0;
- int expectedTextAfterCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH;
-
- editorInfo.setInitialSurroundingText(testText);
-
- assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
- expectedTextAfterCursorLength);
-
- // Cursor at the end.
- editorInfo.initialSelStart = testText.length() - selectionLength;
- editorInfo.initialSelEnd = testText.length();
- expectedTextBeforeCursorLength = editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH;
- expectedTextAfterCursorLength = 0;
-
- editorInfo.setInitialSurroundingText(testText);
-
- assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
- expectedTextAfterCursorLength);
-
- // Cursor at the middle.
- selectionLength = 2;
+ final int selectionLength = 2;
editorInfo.initialSelStart = testText.length() / 2;
editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
- expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart,
+ final int expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart,
(int) (0.8 * (EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH - selectionLength)));
- expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH
+ final int expectedTextAfterCursorLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH
- expectedTextBeforeCursorLength - selectionLength;
editorInfo.setInitialSurroundingText(testText);
assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
expectedTextAfterCursorLength);
+ }
- // Accidentally swap selection start and end.
- editorInfo.initialSelEnd = testText.length() / 2;
- editorInfo.initialSelStart = editorInfo.initialSelEnd + selectionLength;
-
- editorInfo.setInitialSurroundingText(testText);
-
- assertExpectedTextLength(editorInfo, expectedTextBeforeCursorLength, selectionLength,
- expectedTextAfterCursorLength);
-
- // Selection too long, selected text should be dropped.
- selectionLength = EditorInfo.MAX_INITIAL_SELECTION_LENGTH + 1;
+ @Test
+ public void setOverSizeInitialText_overSizeSelection_dropsSelection() {
+ final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH + 2);
+ final EditorInfo editorInfo = new EditorInfo();
+ final int selectionLength = EditorInfo.MAX_INITIAL_SELECTION_LENGTH + 1;
editorInfo.initialSelStart = testText.length() / 2;
editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
- expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart,
+ final int expectedTextBeforeCursorLength = Math.min(editorInfo.initialSelStart,
(int) (0.8 * EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH));
- expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd;
+ final int expectedTextAfterCursorLength = testText.length() - editorInfo.initialSelEnd;
editorInfo.setInitialSurroundingText(testText);
@@ -219,34 +208,59 @@
}
@Test
- public void testTooLongSubTextInputComposeInitialSurroundingText() {
- final int prependLength = 5;
- final int subTextLength = EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH;
- final Spannable fullText = createTestText(prependLength, subTextLength);
+ public void setInitialSubText_trimmedSubText_dividesByOriginalCursorPosition() {
+ final String prefixString = "prefix";
+ final CharSequence subText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+ final CharSequence originalText = TextUtils.concat(prefixString, subText);
final EditorInfo editorInfo = new EditorInfo();
- // Cursor at the middle.
- final int selectionLength = 2;
- editorInfo.initialSelStart = fullText.length() / 2;
- editorInfo.initialSelEnd = editorInfo.initialSelStart + selectionLength;
- // #prependLength characters will be trimmed out.
- final Spannable expectedTextBeforeCursor = createExpectedText(/* startNumber= */0,
- editorInfo.initialSelStart - prependLength);
- final Spannable expectedSelectedText = createExpectedText(
- editorInfo.initialSelStart - prependLength, selectionLength);
- final Spannable expectedTextAfterCursor = createExpectedText(
- editorInfo.initialSelEnd - prependLength,
- fullText.length() - editorInfo.initialSelEnd);
+ final int selLength = 2;
+ editorInfo.initialSelStart = originalText.length() / 2;
+ editorInfo.initialSelEnd = editorInfo.initialSelStart + selLength;
+ final CharSequence expectedTextBeforeCursor = createExpectedText(/* startNumber= */0,
+ editorInfo.initialSelStart - prefixString.length());
+ final CharSequence expectedSelectedText = createExpectedText(
+ editorInfo.initialSelStart - prefixString.length(), selLength);
+ final CharSequence expectedTextAfterCursor = createExpectedText(
+ editorInfo.initialSelEnd - prefixString.length(),
+ originalText.length() - editorInfo.initialSelEnd);
- editorInfo.setInitialSurroundingSubText(fullText.subSequence(prependLength,
- fullText.length()), prependLength);
+ editorInfo.setInitialSurroundingSubText(subText, prefixString.length());
assertTrue(TextUtils.equals(expectedTextBeforeCursor,
- editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
- InputConnection.GET_TEXT_WITH_STYLES)));
+ editorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH, anyInt())));
assertTrue(TextUtils.equals(expectedSelectedText,
- editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES)));
+ editorInfo.getInitialSelectedText(anyInt())));
assertTrue(TextUtils.equals(expectedTextAfterCursor,
- editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
+ editorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH, anyInt())));
+ }
+
+ @Test
+ public void initialSurroundingText_wrapIntoParcel_staysIntact() {
+ // EditorInfo.InitialSurroundingText is not visible to test class. But all its key elements
+ // must stay intact for its getter methods to return correct value and it will be wrapped
+ // into its outer class for parcel transfer, therefore we can verify its parcel
+ // wrapping/unwrapping logic through its outer class.
+ final CharSequence testText = createTestText(EditorInfo.MEMORY_EFFICIENT_TEXT_LENGTH);
+ final EditorInfo sourceEditorInfo = new EditorInfo();
+ final int selectionLength = 2;
+ sourceEditorInfo.initialSelStart = testText.length() / 2;
+ sourceEditorInfo.initialSelEnd = sourceEditorInfo.initialSelStart + selectionLength;
+ sourceEditorInfo.setInitialSurroundingText(testText);
+
+ final EditorInfo targetEditorInfo = cloneViaParcel(sourceEditorInfo);
+
+ assertTrue(TextUtils.equals(
+ sourceEditorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH,
+ InputConnection.GET_TEXT_WITH_STYLES),
+ targetEditorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH,
+ InputConnection.GET_TEXT_WITH_STYLES)));
+ assertTrue(TextUtils.equals(
+ sourceEditorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES),
+ targetEditorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES)));
+ assertTrue(TextUtils.equals(
+ sourceEditorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH,
+ InputConnection.GET_TEXT_WITH_STYLES),
+ targetEditorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH,
InputConnection.GET_TEXT_WITH_STYLES)));
}
@@ -254,12 +268,12 @@
@Nullable Integer expectBeforeCursorLength, @Nullable Integer expectSelectionLength,
@Nullable Integer expectAfterCursorLength) {
final CharSequence textBeforeCursor =
- editorInfo.getInitialTextBeforeCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
+ editorInfo.getInitialTextBeforeCursor(LONG_EXP_TEXT_LENGTH,
InputConnection.GET_TEXT_WITH_STYLES);
final CharSequence selectedText =
editorInfo.getInitialSelectedText(InputConnection.GET_TEXT_WITH_STYLES);
final CharSequence textAfterCursor =
- editorInfo.getInitialTextAfterCursor(editorInfo.MEMORY_EFFICIENT_TEXT_LENGTH,
+ editorInfo.getInitialTextAfterCursor(LONG_EXP_TEXT_LENGTH,
InputConnection.GET_TEXT_WITH_STYLES);
if (expectBeforeCursorLength == null) {
@@ -281,19 +295,15 @@
}
}
- private static Spannable createTestText(int prependLength, int surroundingLength) {
+ private static CharSequence createTestText(int surroundingLength) {
final SpannableStringBuilder builder = new SpannableStringBuilder();
- for (int i = 0; i < prependLength; i++) {
- builder.append("a");
- }
-
for (int i = 0; i < surroundingLength; i++) {
builder.append(Integer.toString(i % 10));
}
return builder;
}
- private static Spannable createExpectedText(int startNumber, int length) {
+ private static CharSequence createExpectedText(int startNumber, int length) {
final SpannableStringBuilder builder = new SpannableStringBuilder();
for (int i = startNumber; i < startNumber + length; i++) {
builder.append(Integer.toString(i % 10));