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 a1bf512541883a0f92238bd21d01574a8a03e924..f00e77085e78bef527b756d08495b780ff824b23 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 |
@@ -5,6 +5,7 @@ |
package org.chromium.content.browser.input; |
import android.content.res.Configuration; |
+import android.os.Build; |
import android.os.ResultReceiver; |
import android.os.SystemClock; |
import android.text.SpannableString; |
@@ -24,6 +25,7 @@ import org.chromium.base.annotations.CalledByNative; |
import org.chromium.base.annotations.JNINamespace; |
import org.chromium.blink_public.web.WebInputEventModifier; |
import org.chromium.blink_public.web.WebInputEventType; |
+import org.chromium.content.browser.RenderCoordinates; |
import org.chromium.ui.base.ime.TextInputType; |
import org.chromium.ui.picker.InputDialogContainer; |
@@ -94,6 +96,11 @@ public class ImeAdapter { |
private ChromiumBaseInputConnection.Factory mInputConnectionFactory; |
private final ImeAdapterDelegate mViewEmbedder; |
+ // This holds the information necessary for constructing CursorAnchorInfo, and notifies to |
+ // InputMethodManager on appropriate timing, depending on how IME requested the information |
+ // via InputConnection. The update request is per InputConnection, hence for each time it is |
+ // re-created, the monitoring status will be reset. |
+ private final CursorAnchorInfoController mCursorAnchorInfoController; |
private int mTextInputType = TextInputType.NONE; |
private int mTextInputFlags; |
@@ -103,6 +110,9 @@ public class ImeAdapter { |
private int mLastSelectionStart; |
private int mLastSelectionEnd; |
+ private String mLastText; |
+ private int mLastCompositionStart; |
+ private int mLastCompositionEnd; |
/** |
* @param wrapper InputMethodManagerWrapper that should receive all the call directed to |
@@ -116,6 +126,34 @@ public class ImeAdapter { |
// Deep copy newConfig so that we can notice the difference. |
mCurrentConfig = new Configuration( |
mViewEmbedder.getAttachedView().getResources().getConfiguration()); |
+ // CursorAnchroInfo is supported only after L. |
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
+ mCursorAnchorInfoController = CursorAnchorInfoController.create(wrapper, |
+ new CursorAnchorInfoController.ComposingTextDelegate() { |
+ @Override |
+ public CharSequence getText() { |
+ return mLastText; |
+ } |
+ @Override |
+ public int getSelectionStart() { |
+ return mLastSelectionStart; |
+ } |
+ @Override |
+ public int getSelectionEnd() { |
+ return mLastSelectionEnd; |
+ } |
+ @Override |
+ public int getComposingTextStart() { |
+ return mLastCompositionStart; |
+ } |
+ @Override |
+ public int getComposingTextEnd() { |
+ return mLastCompositionEnd; |
+ } |
+ }); |
+ } else { |
+ mCursorAnchorInfoController = null; |
+ } |
} |
private boolean isImeThreadEnabled() { |
@@ -155,6 +193,9 @@ public class ImeAdapter { |
mViewEmbedder.getAttachedView(), this, mTextInputType, mTextInputFlags, |
mLastSelectionStart, mLastSelectionEnd, outAttrs); |
if (DEBUG_LOGS) Log.w(TAG, "onCreateInputConnection: " + mInputConnection); |
+ if (mCursorAnchorInfoController != null) { |
+ mCursorAnchorInfoController.resetMonitoringState(); |
+ } |
return mInputConnection; |
} |
@@ -166,6 +207,9 @@ public class ImeAdapter { |
@VisibleForTesting |
public void setInputMethodManagerWrapperForTest(InputMethodManagerWrapper immw) { |
mInputMethodManagerWrapper = immw; |
+ if (mCursorAnchorInfoController != null) { |
+ mCursorAnchorInfoController.setInputMethodManagerWrapperForTest(immw); |
+ } |
} |
@VisibleForTesting |
@@ -251,8 +295,18 @@ public class ImeAdapter { |
*/ |
public void updateState(String text, int selectionStart, int selectionEnd, int compositionStart, |
int compositionEnd, boolean isNonImeChange) { |
+ if (mCursorAnchorInfoController != null && (!TextUtils.equals(mLastText, text) |
+ || mLastSelectionStart != selectionStart || mLastSelectionEnd != selectionEnd |
+ || mLastCompositionStart != compositionStart |
+ || mLastCompositionEnd != compositionEnd)) { |
+ mCursorAnchorInfoController.invalidateLastCursorAnchorInfo(); |
+ } |
+ mLastText = text; |
mLastSelectionStart = selectionStart; |
mLastSelectionEnd = selectionEnd; |
+ mLastCompositionStart = compositionStart; |
+ mLastCompositionEnd = compositionEnd; |
+ |
if (mInputConnection == null) return; |
boolean singleLine = mTextInputType != TextInputType.TEXT_AREA |
&& mTextInputType != TextInputType.CONTENT_EDITABLE; |
@@ -549,6 +603,12 @@ public class ImeAdapter { |
@CalledByNative |
private void focusedNodeChanged(boolean isEditable) { |
if (DEBUG_LOGS) Log.w(TAG, "focusedNodeChanged: isEditable [%b]", isEditable); |
+ |
+ // Update controller before the connection is restarted. |
+ if (mCursorAnchorInfoController != null) { |
+ mCursorAnchorInfoController.focusedNodeChanged(isEditable); |
+ } |
+ |
if (mTextInputType != TextInputType.NONE && mInputConnection != null && isEditable) { |
restartInput(); |
} |
@@ -564,6 +624,36 @@ public class ImeAdapter { |
return nativeRequestTextInputStateUpdate(mNativeImeAdapterAndroid); |
} |
+ /** |
+ * Notified when IME requested Chrome to change the cursor update mode. |
+ */ |
+ public boolean onRequestCursorUpdates(int cursorUpdateMode) { |
+ if (mCursorAnchorInfoController == null) return false; |
+ return mCursorAnchorInfoController.onRequestCursorUpdates(cursorUpdateMode, |
+ mViewEmbedder.getAttachedView()); |
+ } |
+ |
+ /** |
+ * Notified when a frame has been produced by the renderer and all the associated metadata. |
+ * @param renderCoordinates coordinate information to convert CSS (document) coordinates to |
+ * View-local Physical (screen) coordinates |
+ * @param hasInsertionMarker Whether the insertion marker is visible or not. |
+ * @param insertionMarkerHorizontal X coordinates (in view-local DIP pixels) of the insertion |
+ * marker if it exists. Will be ignored otherwise. |
+ * @param insertionMarkerTop Y coordinates (in view-local DIP pixels) of the top of the |
+ * insertion marker if it exists. Will be ignored otherwise. |
+ * @param insertionMarkerBottom Y coordinates (in view-local DIP pixels) of the bottom of |
+ * the insertion marker if it exists. Will be ignored otherwise. |
+ */ |
+ public void onUpdateFrameInfo(RenderCoordinates renderCoordinates, boolean hasInsertionMarker, |
+ boolean isInsertionMarkerVisible, float insertionMarkerHorizontal, |
+ float insertionMarkerTop, float insertionMarkerBottom) { |
+ if (mCursorAnchorInfoController == null) return; |
+ mCursorAnchorInfoController.onUpdateFrameInfo(renderCoordinates, hasInsertionMarker, |
+ isInsertionMarkerVisible, insertionMarkerHorizontal, insertionMarkerTop, |
+ insertionMarkerBottom, mViewEmbedder.getAttachedView()); |
+ } |
+ |
@CalledByNative |
private void populateUnderlinesFromSpans(CharSequence text, long underlines) { |
if (DEBUG_LOGS) { |
@@ -593,9 +683,18 @@ public class ImeAdapter { |
} |
@CalledByNative |
+ private void setCharacterBounds(float[] characterBounds) { |
+ if (mCursorAnchorInfoController == null) return; |
+ mCursorAnchorInfoController.setCompositionCharacterBounds(characterBounds); |
+ } |
+ |
+ @CalledByNative |
private void detach() { |
if (DEBUG_LOGS) Log.w(TAG, "detach"); |
mNativeImeAdapterAndroid = 0; |
+ if (mCursorAnchorInfoController != null) { |
+ mCursorAnchorInfoController.focusedNodeChanged(false); |
+ } |
} |
private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid, |