diff options
| author | Taran Singh <tarandeep@google.com> | 2022-01-20 22:36:14 +0000 |
|---|---|---|
| committer | Taran Singh <tarandeep@google.com> | 2022-01-26 15:41:27 +0000 |
| commit | 7fbcbf27c79dc7203236c13e1daee48bbb970fd5 (patch) | |
| tree | 7832d0a64620f1a7c2e88eb44f2baac71844af75 /core/java | |
| parent | 342509f09402284912fb894175ad414b977a81b8 (diff) | |
Scribe in IMF: Introduce filters in CursoAnchorInfo API 7/N
New filter modes for InputConnection#requestCursorUpdates(mode) to allow
partial data in cursor/anchor updates.
Selective data helps with speedy CursorAnchorInfo updates.
Test: atest InputMethodServiceTest
Bug: 203086136
Change-Id: Ibba06e9a2533ca91c5cc215dfb18d21c9a74fb73
Diffstat (limited to 'core/java')
4 files changed, 164 insertions, 65 deletions
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index b85fe7c3aae2..78509fe6e0b5 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -972,6 +972,13 @@ public interface InputConnection { * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be * used together with {@link #CURSOR_UPDATE_MONITOR}. + * <p> + * Note by default all of {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS} and + * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} are included but specifying them can + * filter-out others. + * It can be CPU intensive to include all, filtering specific info is recommended. + * </p> */ int CURSOR_UPDATE_IMMEDIATE = 1 << 0; @@ -983,17 +990,69 @@ public interface InputConnection { * <p> * This flag can be used together with {@link #CURSOR_UPDATE_IMMEDIATE}. * </p> + * <p> + * Note by default all of {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS} and + * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} are included but specifying them can + * filter-out others. + * It can be CPU intensive to include all, filtering specific info is recommended. + * </p> */ int CURSOR_UPDATE_MONITOR = 1 << 1; /** + * The editor is requested to call + * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} + * with new {@link EditorBoundsInfo} whenever cursor/anchor position is changed. To disable + * monitoring, call {@link InputConnection#requestCursorUpdates(int)} again with this flag off. + * <p> + * This flag can be used together with filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags + * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}. + * </p> + */ + int CURSOR_UPDATE_FILTER_EDITOR_BOUNDS = 1 << 2; + + /** + * The editor is requested to call + * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} + * with new character bounds {@link CursorAnchorInfo#getCharacterBounds(int)} whenever + * cursor/anchor position is changed. To disable + * monitoring, call {@link InputConnection#requestCursorUpdates(int)} again with this flag off. + * <p> + * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags + * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}. + * </p> + */ + int CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS = 1 << 3; + + /** + * The editor is requested to call + * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} + * with new Insertion marker info {@link CursorAnchorInfo#getInsertionMarkerFlags()}, + * {@link CursorAnchorInfo#getInsertionMarkerBaseline()}, etc whenever cursor/anchor position is + * changed. To disable monitoring, call {@link InputConnection#requestCursorUpdates(int)} again + * with this flag off. + * <p> + * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS} and update flags {@link #CURSOR_UPDATE_IMMEDIATE} + * and {@link #CURSOR_UPDATE_MONITOR}. + * </p> + */ + int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 1 << 4; + + /** * Called by the input method to ask the editor for calling back * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} to * notify cursor/anchor locations. * - * @param cursorUpdateMode {@link #CURSOR_UPDATE_IMMEDIATE} and/or - * {@link #CURSOR_UPDATE_MONITOR}. Pass {@code 0} to disable the effect of - * {@link #CURSOR_UPDATE_MONITOR}. + * @param cursorUpdateMode any combination of update modes and filters: + * {@link #CURSOR_UPDATE_IMMEDIATE}, {@link #CURSOR_UPDATE_MONITOR}, and date filters: + * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, + * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}. + * Pass {@code 0} to disable them. However, if an unknown flag is provided, request will be + * rejected and method will return {@code false}. * @return {@code true} if the request is scheduled. {@code false} to indicate that when the * application will not call {@link InputMethodManager#updateCursorAnchorInfo( * android.view.View, CursorAnchorInfo)}. diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 849f3c95d191..f480b24bdba3 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -18,6 +18,7 @@ package android.view.inputmethod; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; +import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS; import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE; import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID; @@ -1797,11 +1798,12 @@ public final class InputMethodManager { } if (mServedInputConnection != null && getDelegate().hasActiveConnection(view)) { // TODO (b/210039666): optimize CURSOR_UPDATE_IMMEDIATE. - // TODO (b/215533103): Introduce new modes in requestCursorUpdates(). // TODO (b/210039666): Pipe IME displayId from InputBindResult and use it here. // instead of mDisplayId. mServedInputConnection.requestCursorUpdatesFromImm( - CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR, mDisplayId); + CURSOR_UPDATE_IMMEDIATE | CURSOR_UPDATE_MONITOR + | CURSOR_UPDATE_FILTER_EDITOR_BOUNDS, + mDisplayId); } try { @@ -2449,6 +2451,17 @@ public final class InputMethodManager { } /** + * Get the requested mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}. + * + * @hide + */ + public int getUpdateCursorAnchorInfoMode() { + synchronized (mH) { + return mRequestUpdateCursorAnchorInfoMonitorMode; + } + } + + /** * Report the current cursor location in its window. * * @deprecated Use {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} instead. diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index dd70d69a8e02..3a7a544a334b 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -4567,6 +4567,19 @@ public class Editor { if (layout == null) { return; } + int mode = imm.getUpdateCursorAnchorInfoMode(); + boolean includeEditorBounds = + (mode & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0; + boolean includeCharacterBounds = + (mode & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0; + boolean includeInsertionMarker = + (mode & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; + boolean includeAll = + (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker); + + includeEditorBounds |= includeAll; + includeCharacterBounds |= includeAll; + includeInsertionMarker |= includeAll; final CursorAnchorInfo.Builder builder = mSelectionInfoBuilder; builder.reset(); @@ -4579,68 +4592,79 @@ public class Editor { mTextView.getLocationOnScreen(mTmpIntOffset); mViewToScreenMatrix.postTranslate(mTmpIntOffset[0], mTmpIntOffset[1]); builder.setMatrix(mViewToScreenMatrix); - final RectF bounds = new RectF(); - mTextView.getBoundsOnScreen(bounds, false /* clipToParent */); - EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); - //TODO(b/210039666): add Handwriting bounds once they're available. - builder.setEditorBoundsInfo( - boundsBuilder.setEditorBounds(bounds).build()); - - final float viewportToContentHorizontalOffset = - mTextView.viewportToContentHorizontalOffset(); - final float viewportToContentVerticalOffset = - mTextView.viewportToContentVerticalOffset(); - - final CharSequence text = mTextView.getText(); - if (text instanceof Spannable) { - final Spannable sp = (Spannable) text; - int composingTextStart = EditableInputConnection.getComposingSpanStart(sp); - int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp); - if (composingTextEnd < composingTextStart) { - final int temp = composingTextEnd; - composingTextEnd = composingTextStart; - composingTextStart = temp; - } - final boolean hasComposingText = - (0 <= composingTextStart) && (composingTextStart < composingTextEnd); - if (hasComposingText) { - final CharSequence composingText = text.subSequence(composingTextStart, - composingTextEnd); - builder.setComposingText(composingTextStart, composingText); - mTextView.populateCharacterBounds(builder, composingTextStart, - composingTextEnd, viewportToContentHorizontalOffset, - viewportToContentVerticalOffset); - } - } - // Treat selectionStart as the insertion point. - if (0 <= selectionStart) { - final int offset = selectionStart; - final int line = layout.getLineForOffset(offset); - final float insertionMarkerX = layout.getPrimaryHorizontal(offset) - + viewportToContentHorizontalOffset; - final float insertionMarkerTop = layout.getLineTop(line) - + viewportToContentVerticalOffset; - final float insertionMarkerBaseline = layout.getLineBaseline(line) - + viewportToContentVerticalOffset; - final float insertionMarkerBottom = layout.getLineBottomWithoutSpacing(line) - + viewportToContentVerticalOffset; - final boolean isTopVisible = mTextView - .isPositionVisible(insertionMarkerX, insertionMarkerTop); - final boolean isBottomVisible = mTextView - .isPositionVisible(insertionMarkerX, insertionMarkerBottom); - int insertionMarkerFlags = 0; - if (isTopVisible || isBottomVisible) { - insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; - } - if (!isTopVisible || !isBottomVisible) { - insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; + if (includeEditorBounds) { + final RectF bounds = new RectF(); + mTextView.getBoundsOnScreen(bounds, false /* clipToParent */); + EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); + //TODO(b/210039666): add Handwriting bounds once they're available. + builder.setEditorBoundsInfo( + boundsBuilder.setEditorBounds(bounds).build()); + } + + if (includeCharacterBounds || includeInsertionMarker) { + final float viewportToContentHorizontalOffset = + mTextView.viewportToContentHorizontalOffset(); + final float viewportToContentVerticalOffset = + mTextView.viewportToContentVerticalOffset(); + + if (includeCharacterBounds) { + final CharSequence text = mTextView.getText(); + if (text instanceof Spannable) { + final Spannable sp = (Spannable) text; + int composingTextStart = EditableInputConnection.getComposingSpanStart(sp); + int composingTextEnd = EditableInputConnection.getComposingSpanEnd(sp); + if (composingTextEnd < composingTextStart) { + final int temp = composingTextEnd; + composingTextEnd = composingTextStart; + composingTextStart = temp; + } + final boolean hasComposingText = + (0 <= composingTextStart) && (composingTextStart + < composingTextEnd); + if (hasComposingText) { + final CharSequence composingText = text.subSequence(composingTextStart, + composingTextEnd); + builder.setComposingText(composingTextStart, composingText); + mTextView.populateCharacterBounds(builder, composingTextStart, + composingTextEnd, viewportToContentHorizontalOffset, + viewportToContentVerticalOffset); + } + } } - if (layout.isRtlCharAt(offset)) { - insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; + + if (includeInsertionMarker) { + // Treat selectionStart as the insertion point. + if (0 <= selectionStart) { + final int offset = selectionStart; + final int line = layout.getLineForOffset(offset); + final float insertionMarkerX = layout.getPrimaryHorizontal(offset) + + viewportToContentHorizontalOffset; + final float insertionMarkerTop = layout.getLineTop(line) + + viewportToContentVerticalOffset; + final float insertionMarkerBaseline = layout.getLineBaseline(line) + + viewportToContentVerticalOffset; + final float insertionMarkerBottom = layout.getLineBottomWithoutSpacing(line) + + viewportToContentVerticalOffset; + final boolean isTopVisible = mTextView + .isPositionVisible(insertionMarkerX, insertionMarkerTop); + final boolean isBottomVisible = mTextView + .isPositionVisible(insertionMarkerX, insertionMarkerBottom); + int insertionMarkerFlags = 0; + if (isTopVisible || isBottomVisible) { + insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; + } + if (!isTopVisible || !isBottomVisible) { + insertionMarkerFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; + } + if (layout.isRtlCharAt(offset)) { + insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; + } + builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, + insertionMarkerBaseline, insertionMarkerBottom, + insertionMarkerFlags); + } } - builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, - insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags); } imm.updateCursorAnchorInfo(mTextView, builder.build()); diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index 29bb3111d83e..630c271d9418 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -211,7 +211,10 @@ public final class EditableInputConnection extends BaseInputConnection // It is possible that any other bit is used as a valid flag in a future release. // We should reject the entire request in such a case. final int knownFlagMask = InputConnection.CURSOR_UPDATE_IMMEDIATE - | InputConnection.CURSOR_UPDATE_MONITOR; + | InputConnection.CURSOR_UPDATE_MONITOR + | InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS + | InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER + | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS; final int unknownFlags = cursorUpdateMode & ~knownFlagMask; if (unknownFlags != 0) { if (DEBUG) { |
