Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(765)

Unified Diff: content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java

Issue 699333003: Support InputMethodManager#updateCursorAnchorInfo for Android 5.0 (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address comments from jdduke@ and aelias@ Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java
index e075d8e4d400dbef2f502c3f262b3455eed249a6..9a596088a15e4fabb2104f9857870955cfdec081 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java
@@ -4,6 +4,9 @@
package org.chromium.content.browser.input;
+import android.annotation.TargetApi;
+import android.graphics.Matrix;
+import android.os.Build;
import android.os.Handler;
import android.os.ResultReceiver;
import android.os.SystemClock;
@@ -15,14 +18,22 @@ import android.text.style.UnderlineSpan;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
+import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.base.VisibleForTesting;
+import org.chromium.cc.SelectionBoundType;
import org.chromium.ui.picker.InputDialogContainer;
import java.lang.CharSequence;
+import java.util.Arrays;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
/**
* Adapts and plumbs android IME service onto the chrome text input API.
@@ -143,6 +154,302 @@ public class ImeAdapter {
private int mTextInputFlags;
private String mLastComposeText;
+ /**
+ * Minimum data set that is required to construct {@link CursorAnchorInfo} object.
jdduke (slow) 2014/11/12 19:05:06 This is a non-trivial code addition, would it make
yukawa 2014/12/05 07:42:19 Sounds reasonable. Now the logic here is complete
+ */
+ private static final class CursorAnchorInfoSource {
+ /**
+ * Special sequence ID that is never returned from {@link #getSequenceNo()}.
+ */
+ public static int INVALID_SEQUENCE_NO = 0;
+
+ private boolean mHasFrameInfo = false;
+ private String mText;
+ private int mSelectionStart;
+ private int mSelectionEnd;
+ private int mCompositionStart;
+ private int mCompositionEnd;
+ private float[] mCompositionCharacterBounds;
+ private boolean mHasInsertionMarker;
+ private float mInsertionMarkerHorizontal;
+ private float mInsertionMarkerTop;
+ private float mInsertionMarkerBottom;
+ private float mScale;
+ private float mTranslationX;
+ private float mTranslationY;
+ private int mSequenceNo = INVALID_SEQUENCE_NO + 1;
+
+
+ /**
+ * Resets the internal state.
+ */
+ public void reset() {
+ mHasFrameInfo = false;
+ mText = null;
+ mSelectionStart = -1;
+ mSelectionEnd = -1;
+ mCompositionStart = -1;
+ mCompositionEnd = -1;
+ mCompositionCharacterBounds = null;
+ mHasInsertionMarker = false;
+ mInsertionMarkerHorizontal = Float.NaN;
+ mInsertionMarkerTop = Float.NaN;
+ mInsertionMarkerBottom = Float.NaN;
+ mScale = 1.0f;
+ mTranslationX = 0.0f;
+ mTranslationY = 0.0f;
+ }
+
+ /**
+ * @return {@code true} if
+ * {@link #setFrameInfo(float, float, float, int, float, float, float)} was called after
+ * the object was instantiated or reinitialized by {@link #reset()}.
+ */
jdduke (slow) 2015/01/21 19:16:42 Do these accessors need public visibility? Where a
+ public boolean hasFrameInfo() {
+ return mHasFrameInfo;
+ }
+
+ /**
+ * @return Current composing text (if any). {@code null}.
+ */
+ @Nullable
+ public CharSequence getComposingText() {
+ if (mText == null) {
+ return null;
+ }
+ if (0 <= mCompositionStart && mCompositionStart <= mText.length()) {
+ return mText.subSequence(mCompositionStart, mCompositionEnd);
+ }
+ return "";
+ }
+
+ /**
+ * @return Selection start index (inclusive) that was specified in
+ * {@link #setTextAndSelection(String, int, int, int, int)}.
+ */
+ public int getSelectionStart() {
+ return mSelectionStart;
+ }
+
+ /**
+ * @return Selection end index (exclusive) that was specified in
+ * {@link #setTextAndSelection(String, int, int, int, int)}.
+ */
+ public int getSelectionEnd() {
+ return mSelectionEnd;
+ }
+
+ /**
+ * @return Composing text start index (inclusive) that was specified in
+ * {@link #setTextAndSelection(String, int, int, int, int)}.
+ */
+ public int getCompositionStart() {
+ return mCompositionStart;
+ }
+
+ /**
+ * @return Array of local coordinates for each character bounds in the composing text.
+ */
+ @Nullable
+ public float[] getCompositionCharacterBounds() {
+ return mCompositionCharacterBounds;
+ }
+
+ /**
+ * @return {@code true} if an insertion marker is displayed.
+ */
+ public boolean hasInsertionMarker() {
+ return mHasInsertionMarker;
+ }
+
+ /**
+ * @return Horizontal position of the text insertion marker, in local coordinates.
+ */
+ public float getInsertionMarkerHorizontal() {
+ return mInsertionMarkerHorizontal;
+ }
+
+ /**
+ * @return Top position of the text insertion marker, in local coordinates.
+ */
+ public float getInsertionMarkerTop() {
+ return mInsertionMarkerTop;
+ }
+
+ /**
+ * @return Bottom position of the text insertion marker, in local coordinates.
+ */
+ public float getInsertionMarkerBottom() {
+ return mInsertionMarkerBottom;
+ }
+
+ /**
+ * @return Scaling factor from CSS (document) coordinates to Physical (screen) coordinates.
+ */
+ public float getScale() {
+ return mScale;
+ }
+
+ /**
+ * @return Horizontal offset from CSS (document) coordinates to View-local Physical (screen)
+ * coordinates. To obtain the display coordinates, add an offset returned from
+ * {@link View#getLocationOnScreen(int[])}.
+ */
+ public float getTranslationX() {
+ return mTranslationX;
+ }
+
+ /**
+ * @return Vertical offset from CSS (document) coordinates to View-local Physical (screen)
+ * coordinates. To obtain the display coordinates, add an offset returned from
+ * {@link View#getLocationOnScreen(int[])}.
+ */
+ public float getTranslationY() {
+ return mTranslationY;
+ }
+
+ /**
+ * @return The sequence number that will be updated when any of the fields is changed.
+ * Never returns {@link #INVALID_SEQUENCE_NO}.
+ */
+ public int getSequenceNo() {
+ return mSequenceNo;
+ }
+
+ private void incrementSequenceNo() {
+ ++mSequenceNo;
+ if (mSequenceNo == INVALID_SEQUENCE_NO) {
+ ++mSequenceNo;
+ }
+ }
+
+ /**
+ * Sets positional information of composing text as an array of character bounds.
+ * @param compositionCharacterBounds Array of character bounds in local coordinates.
+ */
+ public void setCompositionCharacterBounds(float[] compositionCharacterBounds) {
+ if (!Arrays.equals(compositionCharacterBounds, mCompositionCharacterBounds)) {
+ incrementSequenceNo();
+ }
+ mCompositionCharacterBounds = compositionCharacterBounds;
+ }
+
+ /**
+ * Sets text in the focused text area, selection range, and the composing text range.
+ * @param text Text in the focused text field.
+ * @param selectionStart Index where the text selection starts. {@code -1} if there is no
+ * selection.
+ * @param selectionEnd Index where the text selection ends. {@code -1} if there is no
+ * selection.
+ * @param compositionStart Index where the text composition starts. {@code -1} if there is
+ * no selection.
+ * @param compositionEnd Index where the text composition ends. {@code -1} if there is no
+ * selection.
+ */
+ public void setTextAndSelection(String text, int selectionStart, int selectionEnd,
+ int compositionStart, int compositionEnd) {
+ if (text != mText || selectionStart != mSelectionStart || selectionEnd != mSelectionEnd
+ || compositionStart != mCompositionStart || compositionEnd != mCompositionEnd) {
+ incrementSequenceNo();
+ }
+ mText = text;
+ mSelectionStart = selectionStart;
+ mSelectionEnd = selectionEnd;
+ mCompositionStart = compositionStart;
+ mCompositionEnd = compositionEnd;
+ }
+
+ /**
+ * Sets coordinates system parameters and selection marker information.
+ * @param scale Scaling factor from CSS (document) coordinates to Physical (screen)
+ * coordinates.
+ * @param translationX Horizontal offset from CSS (document) coordinates to
+ * View-local Physical (screen) coordinates.
+ * @param translationY Vertical offset from CSS (document) coordinates to View-local
+ * Physical (screen) coordinates.
+ * @param frameMetadataSelectionStartType Type code that is defined in
+ * {@link SelectionBoundType}
+ * @param frameMetadataSelectionStartEdgeTopX X coordinate of the top of the first selection
+ * marker.
+ * @param frameMetadataSelectionStartEdgeTopY Y coordinate of the top of the first selection
+ * marker.
+ * @param frameMetadataSelectionStartEdgeBottomY Y coordinate of the bottom of the first
+ * selection marker.
+ */
+ public void setFrameInfo(float scale, float translationX, float translationY,
+ int frameMetadataSelectionStartType, float frameMetadataSelectionStartEdgeTopX,
+ float frameMetadataSelectionStartEdgeTopY,
+ float frameMetadataSelectionStartEdgeBottomY) {
+ // We are interested only in zero width selection bounds here because non-zero width
+ // selection bounds cannot be represented in CursorAnchorInfo API in Android Framework
+ // as of API Level 21. Actually supporting non-zero width selection bounds in
+ // CursorAnchorInfo API was once considered in the design phase of that API, but the
+ // idea was abandoned because the IME is still able to retrieve the same information
+ // from the following parameters in
+ // CursorAnchorInfo:
+ // - CursorAnchorInfo#getCharacterBounds and
+ // - CursorAnchorInfo#getSelection{Start, End}.
+ final boolean hasInsertionMarker =
+ frameMetadataSelectionStartType == SelectionBoundType.SELECTION_BOUND_CENTER;
+ if (scale != mScale || translationX != mTranslationX || translationY != mTranslationY) {
+ incrementSequenceNo();
+ } else if (hasInsertionMarker) {
+ if (!mHasInsertionMarker
+ || frameMetadataSelectionStartEdgeTopX != mInsertionMarkerHorizontal
+ || frameMetadataSelectionStartEdgeTopY != mInsertionMarkerTop
+ || frameMetadataSelectionStartEdgeBottomY != mInsertionMarkerBottom) {
+ incrementSequenceNo();
+ }
+ } else if (mHasInsertionMarker) {
+ incrementSequenceNo();
+ }
+ mHasInsertionMarker = hasInsertionMarker;
+ if (mHasInsertionMarker) {
+ mInsertionMarkerHorizontal = frameMetadataSelectionStartEdgeTopX;
+ mInsertionMarkerTop = frameMetadataSelectionStartEdgeTopY;
+ mInsertionMarkerBottom = frameMetadataSelectionStartEdgeBottomY;
+ }
+ mScale = scale;
+ mTranslationX = translationX;
+ mTranslationY = translationY;
+ mHasFrameInfo = true;
+ }
+ }
+ private CursorAnchorInfoSource mCursorAnchorInfoSource = new CursorAnchorInfoSource();
+
+ private static class CursorAnchorInfoBuilderWrapper {
+
+ public static boolean isSupported() {
+ return false;
+ }
+
+ public static CursorAnchorInfoBuilderWrapper create() {
+ return new CursorAnchorInfoBuilderWrapper();
+ }
+
+ protected CursorAnchorInfoBuilderWrapper() {
+ }
+
+ /**
+ * Calls {@link InputConnection#updateCursorAnchorInfo(View, CursorAnchorInfo)} iff the API
+ * is available and the current IME actually requested it.
+ * @param inputMethodManagerWrapper Compatibility wrapper of {@link InputConnection}.
+ * @param source Data source from which {@link CursorAnchorInfo} is created.
+ * @param view {@link View} object that should be passed to
+ * {@link InputConnection#updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+ */
+ @SuppressWarnings("unused")
+ public void send(@Nonnull InputMethodManagerWrapper inputMethodManagerWrapper,
jdduke (slow) 2015/01/21 19:16:41 I don't quite understand this function?
+ @Nonnull CursorAnchorInfoSource source, @Nonnull View view) {
+ // This is a stub for platforms that do not support CursorAnchorInfo.
+ }
+ }
+
+ private final CursorAnchorInfoBuilderWrapper mCursorAnchorInfoBuilder =
+ CursorAnchorInfoBuilderWrapper.create();
+ private boolean mCursorAnchorInfoMonitorEnabled = false;
+ private boolean mOneshotUpdateCursorAnchorInfoEnabled = false;
+
@VisibleForTesting
int mLastSyntheticKeyCode;
@@ -621,6 +928,75 @@ public class ImeAdapter {
return true;
}
+ /**
+ * @return Whether the {@link CursorAnchorInfo} is available or not on this device.
+ */
+ public boolean isCursorAnchorInfoSupported() {
+ return CursorAnchorInfoBuilderWrapper.isSupported();
+ }
+
+ /**
+ * Start or stop calling
+ * {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+ * @param enabled {@code true} if {@link InputConnection#requestCursorUpdates(int)} is called
+ * with {@link InputConnection#CURSOR_UPDATE_MONITOR} bit.
+ */
+ public void setCursorAnchorInfoMonitorEnabled(boolean enabled) {
+ mCursorAnchorInfoMonitorEnabled = enabled;
+ }
+
+ /**
+ * Make sure {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)} is called
+ * at least once immediately when the latest position becomes available.
+ * @param view The view to be passed to
+ * {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)} if the latest
+ * position is already available. Otherwise ignored.
+ */
+ public void scheduleOneshotUpdateCursorAnchorInfo(View view) {
+ if (!mCursorAnchorInfoSource.hasFrameInfo()) {
jdduke (slow) 2015/01/21 19:16:41 Where is this method called?
+ // If there is no frame info, send the data later when it becomes available.
+ mOneshotUpdateCursorAnchorInfoEnabled = true;
+ return;
+ }
+ mCursorAnchorInfoBuilder.send(mInputMethodManagerWrapper, mCursorAnchorInfoSource,
+ mViewEmbedder.getAttachedView());
+ mOneshotUpdateCursorAnchorInfoEnabled = false;
+ }
+
+ /**
+ * Update the parameters that are to be passed to the IME through
+ * {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+ * @param text Text in the focused text field.
+ * @param selectionStart Index where the text selection starts. {@code -1} if there is no
+ * selection.
+ * @param selectionEnd Index where the text selection ends. {@code -1} if there is no
+ * selection.
+ * @param compositionStart Index where the text composition starts. {@code -1} if there is no
+ * selection.
+ * @param compositionEnd Index where the text composition ends. {@code -1} if there is no
+ * selection.
+ */
+ public void updateTextAndSelection(String text, int selectionStart, int selectionEnd,
+ int compositionStart, int compositionEnd) {
+ mCursorAnchorInfoSource.setTextAndSelection(text, selectionStart, selectionEnd,
+ compositionStart, compositionEnd);
+ }
+
+ public void onUpdateFrameInfo(float scale, float translationX, float translationY,
+ int frameMetadataSelectionStartType, float frameMetadataSelectionStartEdgeTopX,
+ float frameMetadataSelectionStartEdgeTopY,
+ float frameMetadataSelectionStartEdgeBottomY) {
+ mCursorAnchorInfoSource.setFrameInfo(scale, translationX, translationY,
+ frameMetadataSelectionStartType, frameMetadataSelectionStartEdgeTopX,
+ frameMetadataSelectionStartEdgeTopY, frameMetadataSelectionStartEdgeBottomY);
+ if (!mCursorAnchorInfoMonitorEnabled && !mOneshotUpdateCursorAnchorInfoEnabled) {
+ return;
+ }
+ mCursorAnchorInfoBuilder.send(mInputMethodManagerWrapper, mCursorAnchorInfoSource,
+ mViewEmbedder.getAttachedView());
+ mOneshotUpdateCursorAnchorInfoEnabled = false;
+ }
+
// Calls from C++ to Java
@CalledByNative
@@ -699,7 +1075,7 @@ public class ImeAdapter {
@CalledByNative
private void setCharacterBounds(float[] characterBounds) {
- // TODO(yukawa): Implement this.
+ mCursorAnchorInfoSource.setCompositionCharacterBounds(characterBounds);
}
@CalledByNative
@@ -710,6 +1086,7 @@ public class ImeAdapter {
}
mNativeImeAdapterAndroid = 0;
mTextInputType = 0;
+ mCursorAnchorInfoSource.reset();
}
private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid,

Powered by Google App Engine
This is Rietveld 408576698