Loading core/java/android/widget/Editor.java +10 −156 Original line number Diff line number Diff line Loading @@ -124,13 +124,11 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.animation.LinearInterpolator; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorBoundsInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.TextAppearanceInfo; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationManager; import android.widget.AdapterView.OnItemClickListener; Loading Loading @@ -4667,7 +4665,7 @@ public class Editor { * {@link InputMethodManager#isWatchingCursor(View)} returns false. */ private final class CursorAnchorInfoNotifier implements TextViewPositionListener { final CursorAnchorInfo.Builder mSelectionInfoBuilder = new CursorAnchorInfo.Builder(); final CursorAnchorInfo.Builder mCursorAnchorInfoBuilder = new CursorAnchorInfo.Builder(); final Matrix mViewToScreenMatrix = new Matrix(); @Override Loading @@ -4687,167 +4685,23 @@ public class Editor { // Skip if the IME has not requested the cursor/anchor position. final int knownCursorAnchorInfoModes = InputConnection.CURSOR_UPDATE_IMMEDIATE | InputConnection.CURSOR_UPDATE_MONITOR; if ((mInputMethodState.mUpdateCursorAnchorInfoMode & knownCursorAnchorInfoModes) == 0) { if ((ims.mUpdateCursorAnchorInfoMode & knownCursorAnchorInfoModes) == 0) { return; } Layout layout = mTextView.getLayout(); if (layout == null) { return; } final int filter = mInputMethodState.mUpdateCursorAnchorInfoFilter; boolean includeEditorBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0; boolean includeCharacterBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0; boolean includeInsertionMarker = (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; boolean includeVisibleLineBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0; boolean includeTextAppearance = (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0; boolean includeAll = (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker && !includeVisibleLineBounds && !includeTextAppearance); includeEditorBounds |= includeAll; includeCharacterBounds |= includeAll; includeInsertionMarker |= includeAll; includeVisibleLineBounds |= includeAll; includeTextAppearance |= includeAll; final CursorAnchorInfo.Builder builder = mSelectionInfoBuilder; builder.reset(); final int selectionStart = mTextView.getSelectionStart(); builder.setSelectionRange(selectionStart, mTextView.getSelectionEnd()); // Construct transformation matrix from view local coordinates to screen coordinates. mViewToScreenMatrix.reset(); mTextView.transformMatrixToGlobal(mViewToScreenMatrix); builder.setMatrix(mViewToScreenMatrix); if (includeEditorBounds) { final RectF editorBounds = new RectF(); editorBounds.set(0 /* left */, 0 /* top */, mTextView.getWidth(), mTextView.getHeight()); final RectF handwritingBounds = new RectF( -mTextView.getHandwritingBoundsOffsetLeft(), -mTextView.getHandwritingBoundsOffsetTop(), mTextView.getWidth() + mTextView.getHandwritingBoundsOffsetRight(), mTextView.getHeight() + mTextView.getHandwritingBoundsOffsetBottom()); EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds) .setHandwritingBounds(handwritingBounds).build(); builder.setEditorBoundsInfo(editorBoundsInfo); } if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) { final float viewportToContentHorizontalOffset = mTextView.viewportToContentHorizontalOffset(); final float viewportToContentVerticalOffset = mTextView.viewportToContentVerticalOffset(); final boolean isTextTransformed = (mTextView.getTransformationMethod() != null && mTextView.getTransformed() instanceof OffsetMapping); if (includeCharacterBounds && !isTextTransformed) { 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 (includeInsertionMarker) { // Treat selectionStart as the insertion point. if (0 <= selectionStart) { final int offsetTransformed = mTextView.originalToTransformed( selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR); final int line = layout.getLineForOffset(offsetTransformed); final float insertionMarkerX = layout.getPrimaryHorizontal(offsetTransformed) + viewportToContentHorizontalOffset; final float insertionMarkerTop = layout.getLineTop(line) + viewportToContentVerticalOffset; final float insertionMarkerBaseline = layout.getLineBaseline(line) + viewportToContentVerticalOffset; final float insertionMarkerBottom = layout.getLineBottom(line, /* includeLineSpacing= */ false) + 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(offsetTransformed)) { insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; } builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags); } } if (includeVisibleLineBounds) { final Rect visibleRect = new Rect(); if (mTextView.getContentVisibleRect(visibleRect)) { // Subtract the viewportToContentVerticalOffset to convert the view // coordinates to layout coordinates. final float visibleTop = visibleRect.top - viewportToContentVerticalOffset; final float visibleBottom = visibleRect.bottom - viewportToContentVerticalOffset; final int firstLine = layout.getLineForVertical((int) Math.floor(visibleTop)); final int lastLine = layout.getLineForVertical((int) Math.ceil(visibleBottom)); for (int line = firstLine; line <= lastLine; ++line) { final float left = layout.getLineLeft(line) + viewportToContentHorizontalOffset; final float top = layout.getLineTop(line) + viewportToContentVerticalOffset; final float right = layout.getLineRight(line) + viewportToContentHorizontalOffset; final float bottom = layout.getLineBottom(line, false) + viewportToContentVerticalOffset; builder.addVisibleLineBounds(left, top, right, bottom); } } } } final CursorAnchorInfo cursorAnchorInfo = mTextView.getCursorAnchorInfo(ims.mUpdateCursorAnchorInfoFilter, mCursorAnchorInfoBuilder, mViewToScreenMatrix); if (includeTextAppearance) { builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(mTextView)); } imm.updateCursorAnchorInfo(mTextView, builder.build()); if (cursorAnchorInfo != null) { imm.updateCursorAnchorInfo(mTextView, cursorAnchorInfo); // Drop the immediate flag if any. mInputMethodState.mUpdateCursorAnchorInfoMode &= ~InputConnection.CURSOR_UPDATE_IMMEDIATE; } } } private static class MagnifierMotionAnimator { private static final long DURATION = 100 /* miliseconds */; Loading core/java/android/widget/TextView.java +173 −1 Original line number Diff line number Diff line Loading @@ -196,6 +196,7 @@ import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.DeleteGesture; import android.view.inputmethod.DeleteRangeGesture; import android.view.inputmethod.EditorBoundsInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; Loading @@ -209,6 +210,7 @@ import android.view.inputmethod.PreviewableHandwritingGesture; import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; import android.view.inputmethod.SelectRangeGesture; import android.view.inputmethod.TextAppearanceInfo; import android.view.inputmethod.TextBoundsInfo; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumEntry; Loading Loading @@ -13658,7 +13660,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @return true if at least part of the text content is visible; false if the text content is * completely clipped or translated out of the visible area. */ boolean getContentVisibleRect(Rect rect) { private boolean getContentVisibleRect(Rect rect) { if (!getLocalVisibleRect(rect)) { return false; } Loading Loading @@ -13743,6 +13745,176 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return characterBounds; } /** * Compute {@link CursorAnchorInfo} from this {@link TextView}. * * @param filter the {@link CursorAnchorInfo} update filter which specified the needed * information from IME. * @param cursorAnchorInfoBuilder a cached {@link CursorAnchorInfo.Builder} object used to build * the result {@link CursorAnchorInfo}. * @param viewToScreenMatrix a cached {@link Matrix} object used to compute the view to screen * matrix. * @return the result {@link CursorAnchorInfo} to be passed to IME. * @hide */ @VisibleForTesting @Nullable public CursorAnchorInfo getCursorAnchorInfo(@InputConnection.CursorUpdateFilter int filter, @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, @NonNull Matrix viewToScreenMatrix) { Layout layout = getLayout(); if (layout == null) { return null; } boolean includeEditorBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0; boolean includeCharacterBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0; boolean includeInsertionMarker = (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; boolean includeVisibleLineBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0; boolean includeTextAppearance = (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0; boolean includeAll = (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker && !includeVisibleLineBounds && !includeTextAppearance); includeEditorBounds |= includeAll; includeCharacterBounds |= includeAll; includeInsertionMarker |= includeAll; includeVisibleLineBounds |= includeAll; includeTextAppearance |= includeAll; final CursorAnchorInfo.Builder builder = cursorAnchorInfoBuilder; builder.reset(); final int selectionStart = getSelectionStart(); builder.setSelectionRange(selectionStart, getSelectionEnd()); // Construct transformation matrix from view local coordinates to screen coordinates. viewToScreenMatrix.reset(); transformMatrixToGlobal(viewToScreenMatrix); builder.setMatrix(viewToScreenMatrix); if (includeEditorBounds) { final RectF editorBounds = new RectF(); editorBounds.set(0 /* left */, 0 /* top */, getWidth(), getHeight()); final RectF handwritingBounds = new RectF( -getHandwritingBoundsOffsetLeft(), -getHandwritingBoundsOffsetTop(), getWidth() + getHandwritingBoundsOffsetRight(), getHeight() + getHandwritingBoundsOffsetBottom()); EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds) .setHandwritingBounds(handwritingBounds).build(); builder.setEditorBoundsInfo(editorBoundsInfo); } if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) { final float viewportToContentHorizontalOffset = viewportToContentHorizontalOffset(); final float viewportToContentVerticalOffset = viewportToContentVerticalOffset(); final boolean isTextTransformed = (getTransformationMethod() != null && getTransformed() instanceof OffsetMapping); if (includeCharacterBounds && !isTextTransformed) { final CharSequence text = 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); populateCharacterBounds(builder, composingTextStart, composingTextEnd, viewportToContentHorizontalOffset, viewportToContentVerticalOffset); } } } if (includeInsertionMarker) { // Treat selectionStart as the insertion point. if (0 <= selectionStart) { final int offsetTransformed = originalToTransformed( selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR); final int line = layout.getLineForOffset(offsetTransformed); final float insertionMarkerX = layout.getPrimaryHorizontal(offsetTransformed) + viewportToContentHorizontalOffset; final float insertionMarkerTop = layout.getLineTop(line) + viewportToContentVerticalOffset; final float insertionMarkerBaseline = layout.getLineBaseline(line) + viewportToContentVerticalOffset; final float insertionMarkerBottom = layout.getLineBottom(line, /* includeLineSpacing= */ false) + viewportToContentVerticalOffset; final boolean isTopVisible = isPositionVisible(insertionMarkerX, insertionMarkerTop); final boolean isBottomVisible = 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(offsetTransformed)) { insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; } builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags); } } if (includeVisibleLineBounds) { final Rect visibleRect = new Rect(); if (getContentVisibleRect(visibleRect)) { // Subtract the viewportToContentVerticalOffset to convert the view // coordinates to layout coordinates. final float visibleTop = visibleRect.top - viewportToContentVerticalOffset; final float visibleBottom = visibleRect.bottom - viewportToContentVerticalOffset; final int firstLine = layout.getLineForVertical((int) Math.floor(visibleTop)); final int lastLine = layout.getLineForVertical((int) Math.ceil(visibleBottom)); for (int line = firstLine; line <= lastLine; ++line) { final float left = layout.getLineLeft(line) + viewportToContentHorizontalOffset; final float top = layout.getLineTop(line) + viewportToContentVerticalOffset; final float right = layout.getLineRight(line) + viewportToContentHorizontalOffset; final float bottom = layout.getLineBottom(line, false) + viewportToContentVerticalOffset; builder.addVisibleLineBounds(left, top, right, bottom); } } } } if (includeTextAppearance) { builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this)); } return builder.build(); } /** * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}. * @hide core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java 0 → 100644 +576 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/widget/Editor.java +10 −156 Original line number Diff line number Diff line Loading @@ -124,13 +124,11 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.animation.LinearInterpolator; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorBoundsInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.TextAppearanceInfo; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationManager; import android.widget.AdapterView.OnItemClickListener; Loading Loading @@ -4667,7 +4665,7 @@ public class Editor { * {@link InputMethodManager#isWatchingCursor(View)} returns false. */ private final class CursorAnchorInfoNotifier implements TextViewPositionListener { final CursorAnchorInfo.Builder mSelectionInfoBuilder = new CursorAnchorInfo.Builder(); final CursorAnchorInfo.Builder mCursorAnchorInfoBuilder = new CursorAnchorInfo.Builder(); final Matrix mViewToScreenMatrix = new Matrix(); @Override Loading @@ -4687,167 +4685,23 @@ public class Editor { // Skip if the IME has not requested the cursor/anchor position. final int knownCursorAnchorInfoModes = InputConnection.CURSOR_UPDATE_IMMEDIATE | InputConnection.CURSOR_UPDATE_MONITOR; if ((mInputMethodState.mUpdateCursorAnchorInfoMode & knownCursorAnchorInfoModes) == 0) { if ((ims.mUpdateCursorAnchorInfoMode & knownCursorAnchorInfoModes) == 0) { return; } Layout layout = mTextView.getLayout(); if (layout == null) { return; } final int filter = mInputMethodState.mUpdateCursorAnchorInfoFilter; boolean includeEditorBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0; boolean includeCharacterBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0; boolean includeInsertionMarker = (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; boolean includeVisibleLineBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0; boolean includeTextAppearance = (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0; boolean includeAll = (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker && !includeVisibleLineBounds && !includeTextAppearance); includeEditorBounds |= includeAll; includeCharacterBounds |= includeAll; includeInsertionMarker |= includeAll; includeVisibleLineBounds |= includeAll; includeTextAppearance |= includeAll; final CursorAnchorInfo.Builder builder = mSelectionInfoBuilder; builder.reset(); final int selectionStart = mTextView.getSelectionStart(); builder.setSelectionRange(selectionStart, mTextView.getSelectionEnd()); // Construct transformation matrix from view local coordinates to screen coordinates. mViewToScreenMatrix.reset(); mTextView.transformMatrixToGlobal(mViewToScreenMatrix); builder.setMatrix(mViewToScreenMatrix); if (includeEditorBounds) { final RectF editorBounds = new RectF(); editorBounds.set(0 /* left */, 0 /* top */, mTextView.getWidth(), mTextView.getHeight()); final RectF handwritingBounds = new RectF( -mTextView.getHandwritingBoundsOffsetLeft(), -mTextView.getHandwritingBoundsOffsetTop(), mTextView.getWidth() + mTextView.getHandwritingBoundsOffsetRight(), mTextView.getHeight() + mTextView.getHandwritingBoundsOffsetBottom()); EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds) .setHandwritingBounds(handwritingBounds).build(); builder.setEditorBoundsInfo(editorBoundsInfo); } if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) { final float viewportToContentHorizontalOffset = mTextView.viewportToContentHorizontalOffset(); final float viewportToContentVerticalOffset = mTextView.viewportToContentVerticalOffset(); final boolean isTextTransformed = (mTextView.getTransformationMethod() != null && mTextView.getTransformed() instanceof OffsetMapping); if (includeCharacterBounds && !isTextTransformed) { 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 (includeInsertionMarker) { // Treat selectionStart as the insertion point. if (0 <= selectionStart) { final int offsetTransformed = mTextView.originalToTransformed( selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR); final int line = layout.getLineForOffset(offsetTransformed); final float insertionMarkerX = layout.getPrimaryHorizontal(offsetTransformed) + viewportToContentHorizontalOffset; final float insertionMarkerTop = layout.getLineTop(line) + viewportToContentVerticalOffset; final float insertionMarkerBaseline = layout.getLineBaseline(line) + viewportToContentVerticalOffset; final float insertionMarkerBottom = layout.getLineBottom(line, /* includeLineSpacing= */ false) + 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(offsetTransformed)) { insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; } builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags); } } if (includeVisibleLineBounds) { final Rect visibleRect = new Rect(); if (mTextView.getContentVisibleRect(visibleRect)) { // Subtract the viewportToContentVerticalOffset to convert the view // coordinates to layout coordinates. final float visibleTop = visibleRect.top - viewportToContentVerticalOffset; final float visibleBottom = visibleRect.bottom - viewportToContentVerticalOffset; final int firstLine = layout.getLineForVertical((int) Math.floor(visibleTop)); final int lastLine = layout.getLineForVertical((int) Math.ceil(visibleBottom)); for (int line = firstLine; line <= lastLine; ++line) { final float left = layout.getLineLeft(line) + viewportToContentHorizontalOffset; final float top = layout.getLineTop(line) + viewportToContentVerticalOffset; final float right = layout.getLineRight(line) + viewportToContentHorizontalOffset; final float bottom = layout.getLineBottom(line, false) + viewportToContentVerticalOffset; builder.addVisibleLineBounds(left, top, right, bottom); } } } } final CursorAnchorInfo cursorAnchorInfo = mTextView.getCursorAnchorInfo(ims.mUpdateCursorAnchorInfoFilter, mCursorAnchorInfoBuilder, mViewToScreenMatrix); if (includeTextAppearance) { builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(mTextView)); } imm.updateCursorAnchorInfo(mTextView, builder.build()); if (cursorAnchorInfo != null) { imm.updateCursorAnchorInfo(mTextView, cursorAnchorInfo); // Drop the immediate flag if any. mInputMethodState.mUpdateCursorAnchorInfoMode &= ~InputConnection.CURSOR_UPDATE_IMMEDIATE; } } } private static class MagnifierMotionAnimator { private static final long DURATION = 100 /* miliseconds */; Loading
core/java/android/widget/TextView.java +173 −1 Original line number Diff line number Diff line Loading @@ -196,6 +196,7 @@ import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.DeleteGesture; import android.view.inputmethod.DeleteRangeGesture; import android.view.inputmethod.EditorBoundsInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; Loading @@ -209,6 +210,7 @@ import android.view.inputmethod.PreviewableHandwritingGesture; import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; import android.view.inputmethod.SelectRangeGesture; import android.view.inputmethod.TextAppearanceInfo; import android.view.inputmethod.TextBoundsInfo; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumEntry; Loading Loading @@ -13658,7 +13660,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @return true if at least part of the text content is visible; false if the text content is * completely clipped or translated out of the visible area. */ boolean getContentVisibleRect(Rect rect) { private boolean getContentVisibleRect(Rect rect) { if (!getLocalVisibleRect(rect)) { return false; } Loading Loading @@ -13743,6 +13745,176 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return characterBounds; } /** * Compute {@link CursorAnchorInfo} from this {@link TextView}. * * @param filter the {@link CursorAnchorInfo} update filter which specified the needed * information from IME. * @param cursorAnchorInfoBuilder a cached {@link CursorAnchorInfo.Builder} object used to build * the result {@link CursorAnchorInfo}. * @param viewToScreenMatrix a cached {@link Matrix} object used to compute the view to screen * matrix. * @return the result {@link CursorAnchorInfo} to be passed to IME. * @hide */ @VisibleForTesting @Nullable public CursorAnchorInfo getCursorAnchorInfo(@InputConnection.CursorUpdateFilter int filter, @NonNull CursorAnchorInfo.Builder cursorAnchorInfoBuilder, @NonNull Matrix viewToScreenMatrix) { Layout layout = getLayout(); if (layout == null) { return null; } boolean includeEditorBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS) != 0; boolean includeCharacterBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS) != 0; boolean includeInsertionMarker = (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0; boolean includeVisibleLineBounds = (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0; boolean includeTextAppearance = (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0; boolean includeAll = (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker && !includeVisibleLineBounds && !includeTextAppearance); includeEditorBounds |= includeAll; includeCharacterBounds |= includeAll; includeInsertionMarker |= includeAll; includeVisibleLineBounds |= includeAll; includeTextAppearance |= includeAll; final CursorAnchorInfo.Builder builder = cursorAnchorInfoBuilder; builder.reset(); final int selectionStart = getSelectionStart(); builder.setSelectionRange(selectionStart, getSelectionEnd()); // Construct transformation matrix from view local coordinates to screen coordinates. viewToScreenMatrix.reset(); transformMatrixToGlobal(viewToScreenMatrix); builder.setMatrix(viewToScreenMatrix); if (includeEditorBounds) { final RectF editorBounds = new RectF(); editorBounds.set(0 /* left */, 0 /* top */, getWidth(), getHeight()); final RectF handwritingBounds = new RectF( -getHandwritingBoundsOffsetLeft(), -getHandwritingBoundsOffsetTop(), getWidth() + getHandwritingBoundsOffsetRight(), getHeight() + getHandwritingBoundsOffsetBottom()); EditorBoundsInfo.Builder boundsBuilder = new EditorBoundsInfo.Builder(); EditorBoundsInfo editorBoundsInfo = boundsBuilder.setEditorBounds(editorBounds) .setHandwritingBounds(handwritingBounds).build(); builder.setEditorBoundsInfo(editorBoundsInfo); } if (includeCharacterBounds || includeInsertionMarker || includeVisibleLineBounds) { final float viewportToContentHorizontalOffset = viewportToContentHorizontalOffset(); final float viewportToContentVerticalOffset = viewportToContentVerticalOffset(); final boolean isTextTransformed = (getTransformationMethod() != null && getTransformed() instanceof OffsetMapping); if (includeCharacterBounds && !isTextTransformed) { final CharSequence text = 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); populateCharacterBounds(builder, composingTextStart, composingTextEnd, viewportToContentHorizontalOffset, viewportToContentVerticalOffset); } } } if (includeInsertionMarker) { // Treat selectionStart as the insertion point. if (0 <= selectionStart) { final int offsetTransformed = originalToTransformed( selectionStart, OffsetMapping.MAP_STRATEGY_CURSOR); final int line = layout.getLineForOffset(offsetTransformed); final float insertionMarkerX = layout.getPrimaryHorizontal(offsetTransformed) + viewportToContentHorizontalOffset; final float insertionMarkerTop = layout.getLineTop(line) + viewportToContentVerticalOffset; final float insertionMarkerBaseline = layout.getLineBaseline(line) + viewportToContentVerticalOffset; final float insertionMarkerBottom = layout.getLineBottom(line, /* includeLineSpacing= */ false) + viewportToContentVerticalOffset; final boolean isTopVisible = isPositionVisible(insertionMarkerX, insertionMarkerTop); final boolean isBottomVisible = 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(offsetTransformed)) { insertionMarkerFlags |= CursorAnchorInfo.FLAG_IS_RTL; } builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags); } } if (includeVisibleLineBounds) { final Rect visibleRect = new Rect(); if (getContentVisibleRect(visibleRect)) { // Subtract the viewportToContentVerticalOffset to convert the view // coordinates to layout coordinates. final float visibleTop = visibleRect.top - viewportToContentVerticalOffset; final float visibleBottom = visibleRect.bottom - viewportToContentVerticalOffset; final int firstLine = layout.getLineForVertical((int) Math.floor(visibleTop)); final int lastLine = layout.getLineForVertical((int) Math.ceil(visibleBottom)); for (int line = firstLine; line <= lastLine; ++line) { final float left = layout.getLineLeft(line) + viewportToContentHorizontalOffset; final float top = layout.getLineTop(line) + viewportToContentVerticalOffset; final float right = layout.getLineRight(line) + viewportToContentHorizontalOffset; final float bottom = layout.getLineBottom(line, false) + viewportToContentVerticalOffset; builder.addVisibleLineBounds(left, top, right, bottom); } } } } if (includeTextAppearance) { builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(this)); } return builder.build(); } /** * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}. * @hide
core/tests/coretests/src/android/widget/EditTextCursorAnchorInfoTest.java 0 → 100644 +576 −0 File added.Preview size limit exceeded, changes collapsed. Show changes