Loading core/java/android/widget/Editor.java +122 −2 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; Loading Loading @@ -94,6 +95,8 @@ import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; Loading Loading @@ -215,6 +218,8 @@ public class Editor { private TextView mTextView; final CursorAnchorInfoNotifier mCursorAnchorInfoNotifier = new CursorAnchorInfoNotifier(); Editor(TextView textView) { mTextView = textView; } Loading Loading @@ -249,9 +254,13 @@ public class Editor { // We had an active selection from before, start the selection mode. startSelectionActionMode(); } getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true); } void onDetachedFromWindow() { getPositionListener().removeSubscriber(mCursorAnchorInfoNotifier); if (mError != null) { hideError(); } Loading Loading @@ -780,7 +789,7 @@ public class Editor { boolean parentPositionChanged, boolean parentScrolled); } private boolean isPositionVisible(int positionX, int positionY) { private boolean isPositionVisible(final float positionX, final float positionY) { synchronized (TEMP_POSITION) { final float[] position = TEMP_POSITION; position[0] = positionX; Loading Loading @@ -2134,7 +2143,8 @@ public class Editor { private class PositionListener implements ViewTreeObserver.OnPreDrawListener { // 3 handles // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others) private final int MAXIMUM_NUMBER_OF_LISTENERS = 6; // 1 CursorAnchorInfoNotifier private final int MAXIMUM_NUMBER_OF_LISTENERS = 7; private TextViewPositionListener[] mPositionListeners = new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS]; private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS]; Loading Loading @@ -2997,6 +3007,116 @@ public class Editor { } } /** * A listener to call {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)} * while the input method is requesting the cursor/anchor position. Does nothing as long as * {@link InputMethodManager#isWatchingCursor(View)} returns false. */ private final class CursorAnchorInfoNotifier implements TextViewPositionListener { final CursorAnchorInfoBuilder mSelectionInfoBuilder = new CursorAnchorInfoBuilder(); final int[] mTmpIntOffset = new int[2]; final Matrix mViewToScreenMatrix = new Matrix(); @Override public void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled) { final InputMethodState ims = mInputMethodState; if (ims == null || ims.mBatchEditNesting > 0) { return; } final InputMethodManager imm = InputMethodManager.peekInstance(); if (null == imm) { return; } // Skip if the IME has not requested the cursor/anchor position. if (!imm.isWatchingCursor(mTextView)) { return; } Layout layout = mTextView.getLayout(); if (layout == null) { return; } final CursorAnchorInfoBuilder builder = mSelectionInfoBuilder; builder.reset(); final int selectionStart = mTextView.getSelectionStart(); final int selectionEnd = mTextView.getSelectionEnd(); builder.setSelectionRange(mTextView.getSelectionStart(), mTextView.getSelectionEnd()); // Construct transformation matrix from view local coordinates to screen coordinates. mViewToScreenMatrix.set(mTextView.getMatrix()); mTextView.getLocationOnScreen(mTmpIntOffset); mViewToScreenMatrix.postTranslate(mTmpIntOffset[0], mTmpIntOffset[1]); builder.setMatrix(mViewToScreenMatrix); final float viewportToContentHorizontalOffset = mTextView.viewportToContentHorizontalOffset(); final float viewportToContentVerticalOffset = mTextView.viewportToContentVerticalOffset(); if (mTextView.getText() instanceof Spannable) { final Spannable sp = (Spannable) mTextView.getText(); int compositionStart = EditableInputConnection.getComposingSpanStart(sp); int compositionEnd = EditableInputConnection.getComposingSpanEnd(sp); if (compositionEnd < compositionStart) { final int temp = compositionEnd; compositionEnd = compositionStart; compositionStart = temp; } builder.setCandidateRange(compositionStart, compositionEnd); for (int offset = compositionStart; offset < compositionEnd; offset++) { if (offset < 0) { continue; } final int line = layout.getLineForOffset(offset); final float left = layout.getPrimaryHorizontal(offset) + viewportToContentHorizontalOffset; final float top = layout.getLineTop(line) + viewportToContentVerticalOffset; // Here we are tentatively passing offset + 1 to calculate the other side of // the primary horizontal to preserve as many positions as possible so that // the IME can reconstruct the layout entirely. However, we should revisit this // to have a clear specification about the relationship between the index of // the character and its bounding box. See also the TODO comment below. final float right = layout.getPrimaryHorizontal(offset + 1) + viewportToContentHorizontalOffset; final float bottom = layout.getLineBottom(line) + viewportToContentVerticalOffset; // Take TextView's padding and scroll into account. if (isPositionVisible(left, top) && isPositionVisible(right, bottom)) { // Here offset is the index in Java chars. // TODO: We must have a well-defined specification. For example, how // RTL, surrogate pairs, and composition letters are handled must be // documented. builder.addCharacterRect(offset, left, top, right, bottom); } } } // 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.getLineBottom(line) + viewportToContentVerticalOffset; // Take TextView's padding and scroll into account. if (isPositionVisible(insertionMarkerX, insertionMarkerTop) && isPositionVisible(insertionMarkerX, insertionMarkerBottom)) { builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, insertionMarkerBaseline, insertionMarkerBottom); } } imm.updateCursorAnchorInfo(mTextView, builder.build()); } } private abstract class HandleView extends View implements TextViewPositionListener { protected Drawable mDrawable; protected Drawable mDrawableLtr; Loading Loading
core/java/android/widget/Editor.java +122 −2 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; Loading Loading @@ -94,6 +95,8 @@ import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.CursorAnchorInfo.CursorAnchorInfoBuilder; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; Loading Loading @@ -215,6 +218,8 @@ public class Editor { private TextView mTextView; final CursorAnchorInfoNotifier mCursorAnchorInfoNotifier = new CursorAnchorInfoNotifier(); Editor(TextView textView) { mTextView = textView; } Loading Loading @@ -249,9 +254,13 @@ public class Editor { // We had an active selection from before, start the selection mode. startSelectionActionMode(); } getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true); } void onDetachedFromWindow() { getPositionListener().removeSubscriber(mCursorAnchorInfoNotifier); if (mError != null) { hideError(); } Loading Loading @@ -780,7 +789,7 @@ public class Editor { boolean parentPositionChanged, boolean parentScrolled); } private boolean isPositionVisible(int positionX, int positionY) { private boolean isPositionVisible(final float positionX, final float positionY) { synchronized (TEMP_POSITION) { final float[] position = TEMP_POSITION; position[0] = positionX; Loading Loading @@ -2134,7 +2143,8 @@ public class Editor { private class PositionListener implements ViewTreeObserver.OnPreDrawListener { // 3 handles // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others) private final int MAXIMUM_NUMBER_OF_LISTENERS = 6; // 1 CursorAnchorInfoNotifier private final int MAXIMUM_NUMBER_OF_LISTENERS = 7; private TextViewPositionListener[] mPositionListeners = new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS]; private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS]; Loading Loading @@ -2997,6 +3007,116 @@ public class Editor { } } /** * A listener to call {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)} * while the input method is requesting the cursor/anchor position. Does nothing as long as * {@link InputMethodManager#isWatchingCursor(View)} returns false. */ private final class CursorAnchorInfoNotifier implements TextViewPositionListener { final CursorAnchorInfoBuilder mSelectionInfoBuilder = new CursorAnchorInfoBuilder(); final int[] mTmpIntOffset = new int[2]; final Matrix mViewToScreenMatrix = new Matrix(); @Override public void updatePosition(int parentPositionX, int parentPositionY, boolean parentPositionChanged, boolean parentScrolled) { final InputMethodState ims = mInputMethodState; if (ims == null || ims.mBatchEditNesting > 0) { return; } final InputMethodManager imm = InputMethodManager.peekInstance(); if (null == imm) { return; } // Skip if the IME has not requested the cursor/anchor position. if (!imm.isWatchingCursor(mTextView)) { return; } Layout layout = mTextView.getLayout(); if (layout == null) { return; } final CursorAnchorInfoBuilder builder = mSelectionInfoBuilder; builder.reset(); final int selectionStart = mTextView.getSelectionStart(); final int selectionEnd = mTextView.getSelectionEnd(); builder.setSelectionRange(mTextView.getSelectionStart(), mTextView.getSelectionEnd()); // Construct transformation matrix from view local coordinates to screen coordinates. mViewToScreenMatrix.set(mTextView.getMatrix()); mTextView.getLocationOnScreen(mTmpIntOffset); mViewToScreenMatrix.postTranslate(mTmpIntOffset[0], mTmpIntOffset[1]); builder.setMatrix(mViewToScreenMatrix); final float viewportToContentHorizontalOffset = mTextView.viewportToContentHorizontalOffset(); final float viewportToContentVerticalOffset = mTextView.viewportToContentVerticalOffset(); if (mTextView.getText() instanceof Spannable) { final Spannable sp = (Spannable) mTextView.getText(); int compositionStart = EditableInputConnection.getComposingSpanStart(sp); int compositionEnd = EditableInputConnection.getComposingSpanEnd(sp); if (compositionEnd < compositionStart) { final int temp = compositionEnd; compositionEnd = compositionStart; compositionStart = temp; } builder.setCandidateRange(compositionStart, compositionEnd); for (int offset = compositionStart; offset < compositionEnd; offset++) { if (offset < 0) { continue; } final int line = layout.getLineForOffset(offset); final float left = layout.getPrimaryHorizontal(offset) + viewportToContentHorizontalOffset; final float top = layout.getLineTop(line) + viewportToContentVerticalOffset; // Here we are tentatively passing offset + 1 to calculate the other side of // the primary horizontal to preserve as many positions as possible so that // the IME can reconstruct the layout entirely. However, we should revisit this // to have a clear specification about the relationship between the index of // the character and its bounding box. See also the TODO comment below. final float right = layout.getPrimaryHorizontal(offset + 1) + viewportToContentHorizontalOffset; final float bottom = layout.getLineBottom(line) + viewportToContentVerticalOffset; // Take TextView's padding and scroll into account. if (isPositionVisible(left, top) && isPositionVisible(right, bottom)) { // Here offset is the index in Java chars. // TODO: We must have a well-defined specification. For example, how // RTL, surrogate pairs, and composition letters are handled must be // documented. builder.addCharacterRect(offset, left, top, right, bottom); } } } // 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.getLineBottom(line) + viewportToContentVerticalOffset; // Take TextView's padding and scroll into account. if (isPositionVisible(insertionMarkerX, insertionMarkerTop) && isPositionVisible(insertionMarkerX, insertionMarkerBottom)) { builder.setInsertionMarkerLocation(insertionMarkerX, insertionMarkerTop, insertionMarkerBaseline, insertionMarkerBottom); } } imm.updateCursorAnchorInfo(mTextView, builder.build()); } } private abstract class HandleView extends View implements TextViewPositionListener { protected Drawable mDrawable; protected Drawable mDrawableLtr; Loading