Loading api/current.txt +5 −0 Original line number Diff line number Diff line Loading @@ -55826,7 +55826,12 @@ package android.view.inputmethod { ctor public EditorInfo(); method public int describeContents(); method public void dump(android.util.Printer, String); method @Nullable public CharSequence getInitialSelectedText(int); method @Nullable public CharSequence getInitialTextAfterCursor(int, int); method @Nullable public CharSequence getInitialTextBeforeCursor(int, int); method public final void makeCompatible(int); method public void setInitialSurroundingSubText(@NonNull CharSequence, int); method public void setInitialSurroundingText(@NonNull CharSequence); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorInfo> CREATOR; field public static final int IME_ACTION_DONE = 6; // 0x6 core/java/android/view/inputmethod/EditorInfo.java +333 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package android.view.inputmethod; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.os.Bundle; Loading @@ -28,7 +30,13 @@ import android.os.UserHandle; import android.text.InputType; import android.text.TextUtils; import android.util.Printer; 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; /** Loading Loading @@ -491,6 +499,238 @@ public class EditorInfo implements InputType, Parcelable { @Nullable public UserHandle targetInputMethodUser = null; @IntDef({TrimPolicy.HEAD, TrimPolicy.TAIL}) @Retention(RetentionPolicy.SOURCE) @interface TrimPolicy { int HEAD = 0; int TAIL = 1; } /** * The maximum length of initialSurroundingText. When the input text from * {@code setInitialSurroundingText(CharSequence)} is longer than this, trimming shall be * performed to keep memory efficiency. */ @VisibleForTesting static final int MEMORY_EFFICIENT_TEXT_LENGTH = 2048; /** * When the input text is longer than {@code #MEMORY_EFFICIENT_TEXT_LENGTH}, we start trimming * the input text into three parts: BeforeCursor, Selection, and AfterCursor. We don't want to * trim the Selection but we also don't want it consumes all available space. Therefore, the * maximum acceptable Selection length is half of {@code #MEMORY_EFFICIENT_TEXT_LENGTH}. */ @VisibleForTesting static final int MAX_INITIAL_SELECTION_LENGTH = MEMORY_EFFICIENT_TEXT_LENGTH / 2; @NonNull private InitialSurroundingText mInitialSurroundingText = new InitialSurroundingText(); /** * Editors may use this method to provide initial input text to IMEs. As the surrounding text * could be used to provide various input assistance, we recommend editors to provide the * complete initial input text in its {@link View#onCreateInputConnection(EditorInfo)} callback. * The supplied text will then be processed to serve {@code #getInitialTextBeforeCursor}, * {@code #getInitialSelectedText}, and {@code #getInitialTextBeforeCursor}. System is allowed * to trim {@code sourceText} for various reasons while keeping the most valuable data to IMEs. * * <p><strong>Editor authors: </strong>Providing the initial input text helps reducing IPC calls * for IMEs to provide many modern features right after the connection setup. We recommend * calling this method in your implementation. * * @param sourceText The complete input text. */ public void setInitialSurroundingText(@NonNull CharSequence sourceText) { setInitialSurroundingSubText(sourceText, /* subTextStart = */ 0); } /** * Editors may use this method to provide initial input text to IMEs. As the surrounding text * could be used to provide various input assistance, we recommend editors to provide the * complete initial input text in its {@link View#onCreateInputConnection(EditorInfo)} callback. * When trimming the input text is needed, call this method instead of * {@code setInitialSurroundingText(CharSequence)} and provide the trimmed position info. Always * try to include the selected text within {@code subText} to give the system best flexibility * to choose where and how to trim {@code subText} when necessary. * * @param subText The input text. When it was trimmed, {@code subTextStart} must be provided * correctly. * @param subTextStart The position that the input text got trimmed. For example, when the * editor wants to trim out the first 10 chars, subTextStart should be 10. */ public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) { Preconditions.checkNotNull(subText); // Swap selection start and end if necessary. final int subTextSelStart = initialSelStart > initialSelEnd ? initialSelEnd - subTextStart : initialSelStart - subTextStart; final int subTextSelEnd = initialSelStart > initialSelEnd ? initialSelStart - subTextStart : initialSelEnd - subTextStart; final int subTextLength = subText.length(); // Unknown or invalid selection. if (subTextStart < 0 || subTextSelStart < 0 || subTextSelEnd > subTextLength) { mInitialSurroundingText = new InitialSurroundingText(); return; } // For privacy protection reason, we don't carry password inputs to IMEs. if (isPasswordInputType(inputType)) { mInitialSurroundingText = new InitialSurroundingText(); return; } if (subTextLength <= MEMORY_EFFICIENT_TEXT_LENGTH) { mInitialSurroundingText = new InitialSurroundingText(subText, subTextSelStart, subTextSelEnd); 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; // 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 maxLengthMinusSelection = MEMORY_EFFICIENT_TEXT_LENGTH - newSelLength; final int possibleMaxBeforeCursorLength = Math.min(subTextBeforeCursorLength, (int) (0.8 * maxLengthMinusSelection)); int newAfterCursorLength = Math.min(subTextAfterCursorLength, maxLengthMinusSelection - possibleMaxBeforeCursorLength); int newBeforeCursorLength = Math.min(subTextBeforeCursorLength, maxLengthMinusSelection - newAfterCursorLength); // As trimming may happen at the head of TextBeforeCursor, calculate new starting position. int newBeforeCursorHead = subTextBeforeCursorLength - newBeforeCursorLength; // 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)) { newBeforeCursorHead = newBeforeCursorHead + 1; newBeforeCursorLength = newBeforeCursorLength - 1; } if (isCutOnSurrogate(subText, subTextSelEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) { newAfterCursorLength = newAfterCursorLength - 1; } // Now we know where to trim, compose the initialSurroundingText. final int newTextLength = newBeforeCursorLength + newSelLength + newAfterCursorLength; CharSequence newInitialSurroundingText; if (newSelLength != sourceSelLength) { final CharSequence beforeCursor = subText.subSequence(newBeforeCursorHead, newBeforeCursorHead + newBeforeCursorLength); final CharSequence afterCursor = subText.subSequence(subTextSelEnd, subTextSelEnd + newAfterCursorLength); newInitialSurroundingText = TextUtils.concat(beforeCursor, afterCursor); } else { newInitialSurroundingText = subText .subSequence(newBeforeCursorHead, newBeforeCursorHead + newTextLength); } // As trimming may happen at the head, adjust cursor position in the initialSurroundingText // obj. newBeforeCursorHead = 0; final int newSelHead = newBeforeCursorHead + newBeforeCursorLength; mInitialSurroundingText = new InitialSurroundingText( newInitialSurroundingText, newSelHead, newSelHead + newSelLength); } /** * Get <var>n</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. */ @Nullable public CharSequence getInitialTextBeforeCursor(int length, int flags) { return mInitialSurroundingText.getInitialTextBeforeCursor(length, flags); } /** * Gets the selected text, if any. May be {@code null} when no text is selected 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}. * @return the text that is currently selected, if any. It could be an empty string when there * is no text selected. When {@code null} is returned, the selected text might be too long or * this protocol is not supported. */ @Nullable public CharSequence getInitialSelectedText(int flags) { // Swap selection start and end if necessary. final int correctedTextSelStart = initialSelStart > initialSelEnd ? initialSelEnd : initialSelStart; final int correctedTextSelEnd = initialSelStart > initialSelEnd ? initialSelStart : initialSelEnd; final int sourceSelLength = correctedTextSelEnd - correctedTextSelStart; if (initialSelStart < 0 || initialSelEnd < 0 || mInitialSurroundingText.getSelectionLength() != sourceSelLength) { return null; } return mInitialSurroundingText.getInitialSelectedText(flags); } /** * Get <var>n</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. */ @Nullable public CharSequence getInitialTextAfterCursor(int length, int flags) { return mInitialSurroundingText.getInitialTextAfterCursor(length, flags); } private static boolean isCutOnSurrogate(CharSequence sourceText, int cutPosition, @TrimPolicy int policy) { switch (policy) { case TrimPolicy.HEAD: return Character.isLowSurrogate(sourceText.charAt(cutPosition)); case TrimPolicy.TAIL: return Character.isHighSurrogate(sourceText.charAt(cutPosition)); default: return false; } } private static boolean isPasswordInputType(int inputType) { final int variation = inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); return variation == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD) || variation == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD) || variation == (TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD); } /** * Ensure that the data in this EditorInfo is compatible with an application * that was developed against the given target API version. This can Loading Loading @@ -573,6 +813,7 @@ public class EditorInfo implements InputType, Parcelable { dest.writeInt(fieldId); dest.writeString(fieldName); dest.writeBundle(extras); mInitialSurroundingText.writeToParcel(dest, flags); if (hintLocales != null) { hintLocales.writeToParcel(dest, flags); } else { Loading Loading @@ -603,6 +844,9 @@ public class EditorInfo implements InputType, Parcelable { res.fieldId = source.readInt(); res.fieldName = source.readString(); res.extras = source.readBundle(); InitialSurroundingText initialSurroundingText = InitialSurroundingText.CREATOR.createFromParcel(source); res.mInitialSurroundingText = initialSurroundingText; LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source); res.hintLocales = hintLocales.isEmpty() ? null : hintLocales; res.contentMimeTypes = source.readStringArray(); Loading @@ -619,4 +863,93 @@ public class EditorInfo implements InputType, Parcelable { return 0; } // TODO(b/148035211): Unit tests for this class static final class InitialSurroundingText implements Parcelable { @Nullable final CharSequence mSurroundingText; final int mSelectionHead; final int mSelectionEnd; InitialSurroundingText() { mSurroundingText = null; mSelectionHead = 0; mSelectionEnd = 0; } InitialSurroundingText(@Nullable CharSequence surroundingText, int selectionHead, int selectionEnd) { mSurroundingText = surroundingText; mSelectionHead = selectionHead; mSelectionEnd = selectionEnd; } @Nullable private CharSequence getInitialTextBeforeCursor(int n, int flags) { if (mSurroundingText == null) { return null; } final int length = Math.min(n, mSelectionHead); return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) ? mSurroundingText.subSequence(mSelectionHead - length, mSelectionHead) : TextUtils.substring(mSurroundingText, mSelectionHead - length, mSelectionHead); } @Nullable private CharSequence getInitialSelectedText(int flags) { if (mSurroundingText == null) { return null; } return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) ? mSurroundingText.subSequence(mSelectionHead, mSelectionEnd) : TextUtils.substring(mSurroundingText, mSelectionHead, mSelectionEnd); } @Nullable private CharSequence getInitialTextAfterCursor(int n, int flags) { if (mSurroundingText == null) { return null; } final int length = Math.min(n, mSurroundingText.length() - mSelectionEnd); return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) ? mSurroundingText.subSequence(mSelectionEnd, mSelectionEnd + length) : TextUtils.substring(mSurroundingText, mSelectionEnd, mSelectionEnd + length); } private int getSelectionLength() { return mSelectionEnd - mSelectionHead; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { TextUtils.writeToParcel(mSurroundingText, dest, flags); dest.writeInt(mSelectionHead); dest.writeInt(mSelectionEnd); } public static final @android.annotation.NonNull Parcelable.Creator<InitialSurroundingText> CREATOR = new Parcelable.Creator<InitialSurroundingText>() { @Override public InitialSurroundingText createFromParcel(Parcel source) { final CharSequence initialText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); final int selectionHead = source.readInt(); final int selectionEnd = source.readInt(); return new InitialSurroundingText(initialText, selectionHead, selectionEnd); } @Override public InitialSurroundingText[] newArray(int size) { return new InitialSurroundingText[size]; } }; } } core/java/android/view/inputmethod/InputConnection.java +15 −3 Original line number Diff line number Diff line Loading @@ -158,7 +158,11 @@ public interface InputConnection { * trigger an IPC round-trip that will take some time. Assume this * method consumes a lot of time. Also, please keep in mind the * Editor may choose to return less characters than requested even * if they are available for performance reasons.</p> * if they are available for performance reasons. If you are using * this to get the initial text around the cursor, you may consider * using {@link EditorInfo#getInitialTextBeforeCursor(int, int)}, * {@link EditorInfo#getInitialSelectedText(int)}, and * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change Loading Loading @@ -196,7 +200,11 @@ public interface InputConnection { * * <p><strong>IME authors:</strong> please consider this will * trigger an IPC round-trip that will take some time. Assume this * method consumes a lot of time.</p> * method consumes a lot of time. If you are using this to get the * initial text around the cursor, you may consider using * {@link EditorInfo#getInitialTextBeforeCursor(int, int)}, * {@link EditorInfo#getInitialSelectedText(int)}, and * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change Loading Loading @@ -234,7 +242,11 @@ public interface InputConnection { * * <p><strong>IME authors:</strong> please consider this will * trigger an IPC round-trip that will take some time. Assume this * method consumes a lot of time.</p> * method consumes a lot of time. If you are using this to get the * initial text around the cursor, you may consider using * {@link EditorInfo#getInitialTextBeforeCursor(int, int)}, * {@link EditorInfo#getInitialSelectedText(int)}, and * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change Loading core/java/android/widget/TextView.java +1 −0 Original line number Diff line number Diff line Loading @@ -8668,6 +8668,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialSelStart = getSelectionStart(); outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); return ic; } } Loading core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +229 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
api/current.txt +5 −0 Original line number Diff line number Diff line Loading @@ -55826,7 +55826,12 @@ package android.view.inputmethod { ctor public EditorInfo(); method public int describeContents(); method public void dump(android.util.Printer, String); method @Nullable public CharSequence getInitialSelectedText(int); method @Nullable public CharSequence getInitialTextAfterCursor(int, int); method @Nullable public CharSequence getInitialTextBeforeCursor(int, int); method public final void makeCompatible(int); method public void setInitialSurroundingSubText(@NonNull CharSequence, int); method public void setInitialSurroundingText(@NonNull CharSequence); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.EditorInfo> CREATOR; field public static final int IME_ACTION_DONE = 6; // 0x6
core/java/android/view/inputmethod/EditorInfo.java +333 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,8 @@ package android.view.inputmethod; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.os.Bundle; Loading @@ -28,7 +30,13 @@ import android.os.UserHandle; import android.text.InputType; import android.text.TextUtils; import android.util.Printer; 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; /** Loading Loading @@ -491,6 +499,238 @@ public class EditorInfo implements InputType, Parcelable { @Nullable public UserHandle targetInputMethodUser = null; @IntDef({TrimPolicy.HEAD, TrimPolicy.TAIL}) @Retention(RetentionPolicy.SOURCE) @interface TrimPolicy { int HEAD = 0; int TAIL = 1; } /** * The maximum length of initialSurroundingText. When the input text from * {@code setInitialSurroundingText(CharSequence)} is longer than this, trimming shall be * performed to keep memory efficiency. */ @VisibleForTesting static final int MEMORY_EFFICIENT_TEXT_LENGTH = 2048; /** * When the input text is longer than {@code #MEMORY_EFFICIENT_TEXT_LENGTH}, we start trimming * the input text into three parts: BeforeCursor, Selection, and AfterCursor. We don't want to * trim the Selection but we also don't want it consumes all available space. Therefore, the * maximum acceptable Selection length is half of {@code #MEMORY_EFFICIENT_TEXT_LENGTH}. */ @VisibleForTesting static final int MAX_INITIAL_SELECTION_LENGTH = MEMORY_EFFICIENT_TEXT_LENGTH / 2; @NonNull private InitialSurroundingText mInitialSurroundingText = new InitialSurroundingText(); /** * Editors may use this method to provide initial input text to IMEs. As the surrounding text * could be used to provide various input assistance, we recommend editors to provide the * complete initial input text in its {@link View#onCreateInputConnection(EditorInfo)} callback. * The supplied text will then be processed to serve {@code #getInitialTextBeforeCursor}, * {@code #getInitialSelectedText}, and {@code #getInitialTextBeforeCursor}. System is allowed * to trim {@code sourceText} for various reasons while keeping the most valuable data to IMEs. * * <p><strong>Editor authors: </strong>Providing the initial input text helps reducing IPC calls * for IMEs to provide many modern features right after the connection setup. We recommend * calling this method in your implementation. * * @param sourceText The complete input text. */ public void setInitialSurroundingText(@NonNull CharSequence sourceText) { setInitialSurroundingSubText(sourceText, /* subTextStart = */ 0); } /** * Editors may use this method to provide initial input text to IMEs. As the surrounding text * could be used to provide various input assistance, we recommend editors to provide the * complete initial input text in its {@link View#onCreateInputConnection(EditorInfo)} callback. * When trimming the input text is needed, call this method instead of * {@code setInitialSurroundingText(CharSequence)} and provide the trimmed position info. Always * try to include the selected text within {@code subText} to give the system best flexibility * to choose where and how to trim {@code subText} when necessary. * * @param subText The input text. When it was trimmed, {@code subTextStart} must be provided * correctly. * @param subTextStart The position that the input text got trimmed. For example, when the * editor wants to trim out the first 10 chars, subTextStart should be 10. */ public void setInitialSurroundingSubText(@NonNull CharSequence subText, int subTextStart) { Preconditions.checkNotNull(subText); // Swap selection start and end if necessary. final int subTextSelStart = initialSelStart > initialSelEnd ? initialSelEnd - subTextStart : initialSelStart - subTextStart; final int subTextSelEnd = initialSelStart > initialSelEnd ? initialSelStart - subTextStart : initialSelEnd - subTextStart; final int subTextLength = subText.length(); // Unknown or invalid selection. if (subTextStart < 0 || subTextSelStart < 0 || subTextSelEnd > subTextLength) { mInitialSurroundingText = new InitialSurroundingText(); return; } // For privacy protection reason, we don't carry password inputs to IMEs. if (isPasswordInputType(inputType)) { mInitialSurroundingText = new InitialSurroundingText(); return; } if (subTextLength <= MEMORY_EFFICIENT_TEXT_LENGTH) { mInitialSurroundingText = new InitialSurroundingText(subText, subTextSelStart, subTextSelEnd); 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; // 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 maxLengthMinusSelection = MEMORY_EFFICIENT_TEXT_LENGTH - newSelLength; final int possibleMaxBeforeCursorLength = Math.min(subTextBeforeCursorLength, (int) (0.8 * maxLengthMinusSelection)); int newAfterCursorLength = Math.min(subTextAfterCursorLength, maxLengthMinusSelection - possibleMaxBeforeCursorLength); int newBeforeCursorLength = Math.min(subTextBeforeCursorLength, maxLengthMinusSelection - newAfterCursorLength); // As trimming may happen at the head of TextBeforeCursor, calculate new starting position. int newBeforeCursorHead = subTextBeforeCursorLength - newBeforeCursorLength; // 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)) { newBeforeCursorHead = newBeforeCursorHead + 1; newBeforeCursorLength = newBeforeCursorLength - 1; } if (isCutOnSurrogate(subText, subTextSelEnd + newAfterCursorLength - 1, TrimPolicy.TAIL)) { newAfterCursorLength = newAfterCursorLength - 1; } // Now we know where to trim, compose the initialSurroundingText. final int newTextLength = newBeforeCursorLength + newSelLength + newAfterCursorLength; CharSequence newInitialSurroundingText; if (newSelLength != sourceSelLength) { final CharSequence beforeCursor = subText.subSequence(newBeforeCursorHead, newBeforeCursorHead + newBeforeCursorLength); final CharSequence afterCursor = subText.subSequence(subTextSelEnd, subTextSelEnd + newAfterCursorLength); newInitialSurroundingText = TextUtils.concat(beforeCursor, afterCursor); } else { newInitialSurroundingText = subText .subSequence(newBeforeCursorHead, newBeforeCursorHead + newTextLength); } // As trimming may happen at the head, adjust cursor position in the initialSurroundingText // obj. newBeforeCursorHead = 0; final int newSelHead = newBeforeCursorHead + newBeforeCursorLength; mInitialSurroundingText = new InitialSurroundingText( newInitialSurroundingText, newSelHead, newSelHead + newSelLength); } /** * Get <var>n</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. */ @Nullable public CharSequence getInitialTextBeforeCursor(int length, int flags) { return mInitialSurroundingText.getInitialTextBeforeCursor(length, flags); } /** * Gets the selected text, if any. May be {@code null} when no text is selected 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}. * @return the text that is currently selected, if any. It could be an empty string when there * is no text selected. When {@code null} is returned, the selected text might be too long or * this protocol is not supported. */ @Nullable public CharSequence getInitialSelectedText(int flags) { // Swap selection start and end if necessary. final int correctedTextSelStart = initialSelStart > initialSelEnd ? initialSelEnd : initialSelStart; final int correctedTextSelEnd = initialSelStart > initialSelEnd ? initialSelStart : initialSelEnd; final int sourceSelLength = correctedTextSelEnd - correctedTextSelStart; if (initialSelStart < 0 || initialSelEnd < 0 || mInitialSurroundingText.getSelectionLength() != sourceSelLength) { return null; } return mInitialSurroundingText.getInitialSelectedText(flags); } /** * Get <var>n</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. */ @Nullable public CharSequence getInitialTextAfterCursor(int length, int flags) { return mInitialSurroundingText.getInitialTextAfterCursor(length, flags); } private static boolean isCutOnSurrogate(CharSequence sourceText, int cutPosition, @TrimPolicy int policy) { switch (policy) { case TrimPolicy.HEAD: return Character.isLowSurrogate(sourceText.charAt(cutPosition)); case TrimPolicy.TAIL: return Character.isHighSurrogate(sourceText.charAt(cutPosition)); default: return false; } } private static boolean isPasswordInputType(int inputType) { final int variation = inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); return variation == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD) || variation == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD) || variation == (TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD); } /** * Ensure that the data in this EditorInfo is compatible with an application * that was developed against the given target API version. This can Loading Loading @@ -573,6 +813,7 @@ public class EditorInfo implements InputType, Parcelable { dest.writeInt(fieldId); dest.writeString(fieldName); dest.writeBundle(extras); mInitialSurroundingText.writeToParcel(dest, flags); if (hintLocales != null) { hintLocales.writeToParcel(dest, flags); } else { Loading Loading @@ -603,6 +844,9 @@ public class EditorInfo implements InputType, Parcelable { res.fieldId = source.readInt(); res.fieldName = source.readString(); res.extras = source.readBundle(); InitialSurroundingText initialSurroundingText = InitialSurroundingText.CREATOR.createFromParcel(source); res.mInitialSurroundingText = initialSurroundingText; LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source); res.hintLocales = hintLocales.isEmpty() ? null : hintLocales; res.contentMimeTypes = source.readStringArray(); Loading @@ -619,4 +863,93 @@ public class EditorInfo implements InputType, Parcelable { return 0; } // TODO(b/148035211): Unit tests for this class static final class InitialSurroundingText implements Parcelable { @Nullable final CharSequence mSurroundingText; final int mSelectionHead; final int mSelectionEnd; InitialSurroundingText() { mSurroundingText = null; mSelectionHead = 0; mSelectionEnd = 0; } InitialSurroundingText(@Nullable CharSequence surroundingText, int selectionHead, int selectionEnd) { mSurroundingText = surroundingText; mSelectionHead = selectionHead; mSelectionEnd = selectionEnd; } @Nullable private CharSequence getInitialTextBeforeCursor(int n, int flags) { if (mSurroundingText == null) { return null; } final int length = Math.min(n, mSelectionHead); return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) ? mSurroundingText.subSequence(mSelectionHead - length, mSelectionHead) : TextUtils.substring(mSurroundingText, mSelectionHead - length, mSelectionHead); } @Nullable private CharSequence getInitialSelectedText(int flags) { if (mSurroundingText == null) { return null; } return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) ? mSurroundingText.subSequence(mSelectionHead, mSelectionEnd) : TextUtils.substring(mSurroundingText, mSelectionHead, mSelectionEnd); } @Nullable private CharSequence getInitialTextAfterCursor(int n, int flags) { if (mSurroundingText == null) { return null; } final int length = Math.min(n, mSurroundingText.length() - mSelectionEnd); return ((flags & InputConnection.GET_TEXT_WITH_STYLES) != 0) ? mSurroundingText.subSequence(mSelectionEnd, mSelectionEnd + length) : TextUtils.substring(mSurroundingText, mSelectionEnd, mSelectionEnd + length); } private int getSelectionLength() { return mSelectionEnd - mSelectionHead; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { TextUtils.writeToParcel(mSurroundingText, dest, flags); dest.writeInt(mSelectionHead); dest.writeInt(mSelectionEnd); } public static final @android.annotation.NonNull Parcelable.Creator<InitialSurroundingText> CREATOR = new Parcelable.Creator<InitialSurroundingText>() { @Override public InitialSurroundingText createFromParcel(Parcel source) { final CharSequence initialText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); final int selectionHead = source.readInt(); final int selectionEnd = source.readInt(); return new InitialSurroundingText(initialText, selectionHead, selectionEnd); } @Override public InitialSurroundingText[] newArray(int size) { return new InitialSurroundingText[size]; } }; } }
core/java/android/view/inputmethod/InputConnection.java +15 −3 Original line number Diff line number Diff line Loading @@ -158,7 +158,11 @@ public interface InputConnection { * trigger an IPC round-trip that will take some time. Assume this * method consumes a lot of time. Also, please keep in mind the * Editor may choose to return less characters than requested even * if they are available for performance reasons.</p> * if they are available for performance reasons. If you are using * this to get the initial text around the cursor, you may consider * using {@link EditorInfo#getInitialTextBeforeCursor(int, int)}, * {@link EditorInfo#getInitialSelectedText(int)}, and * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change Loading Loading @@ -196,7 +200,11 @@ public interface InputConnection { * * <p><strong>IME authors:</strong> please consider this will * trigger an IPC round-trip that will take some time. Assume this * method consumes a lot of time.</p> * method consumes a lot of time. If you are using this to get the * initial text around the cursor, you may consider using * {@link EditorInfo#getInitialTextBeforeCursor(int, int)}, * {@link EditorInfo#getInitialSelectedText(int)}, and * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change Loading Loading @@ -234,7 +242,11 @@ public interface InputConnection { * * <p><strong>IME authors:</strong> please consider this will * trigger an IPC round-trip that will take some time. Assume this * method consumes a lot of time.</p> * method consumes a lot of time. If you are using this to get the * initial text around the cursor, you may consider using * {@link EditorInfo#getInitialTextBeforeCursor(int, int)}, * {@link EditorInfo#getInitialSelectedText(int)}, and * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.</p> * * <p><strong>Editor authors:</strong> please be careful of race * conditions in implementing this call. An IME can make a change Loading
core/java/android/widget/TextView.java +1 −0 Original line number Diff line number Diff line Loading @@ -8668,6 +8668,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialSelStart = getSelectionStart(); outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); return ic; } } Loading
core/tests/coretests/src/android/view/inputmethod/EditorInfoTest.java +229 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes