| 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 75bb44fae0d3d228394b0e9bef71477c02904a08..7f0b61508da88746d3e11946fcdac0a1fc51c0d6 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
|
| @@ -7,8 +7,6 @@ package org.chromium.content.browser.input;
|
| import android.content.res.Configuration;
|
| import android.os.ResultReceiver;
|
| import android.os.SystemClock;
|
| -import android.text.Editable;
|
| -import android.text.Selection;
|
| import android.text.SpannableString;
|
| import android.text.TextUtils;
|
| import android.text.style.BackgroundColorSpan;
|
| @@ -20,12 +18,14 @@ import android.view.View;
|
| import android.view.inputmethod.BaseInputConnection;
|
| import android.view.inputmethod.EditorInfo;
|
|
|
| +import org.chromium.base.CommandLine;
|
| import org.chromium.base.Log;
|
| import org.chromium.base.VisibleForTesting;
|
| 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.common.ContentSwitches;
|
| import org.chromium.ui.base.ime.TextInputType;
|
| import org.chromium.ui.picker.InputDialogContainer;
|
|
|
| @@ -92,15 +92,10 @@ public class ImeAdapter {
|
|
|
| private long mNativeImeAdapterAndroid;
|
| private InputMethodManagerWrapper mInputMethodManagerWrapper;
|
| - private AdapterInputConnection mInputConnection;
|
| - private AdapterInputConnectionFactory mInputConnectionFactory;
|
| - private final ImeAdapterDelegate mViewEmbedder;
|
| + private ChromiumBaseInputConnection mInputConnection;
|
| + private ChromiumBaseInputConnection.Factory mInputConnectionFactory;
|
|
|
| - // This holds the state of editable text (e.g. contents of <input>, contenteditable) of
|
| - // a focused element.
|
| - // Every time the user, IME, javascript (Blink), autofill etc. modifies the content, the new
|
| - // state must be reflected to this to keep consistency.
|
| - private final Editable mEditable;
|
| + private final ImeAdapterDelegate mViewEmbedder;
|
|
|
| private int mTextInputType = TextInputType.NONE;
|
| private int mTextInputFlags;
|
| @@ -116,53 +111,37 @@ public class ImeAdapter {
|
| public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder) {
|
| mInputMethodManagerWrapper = wrapper;
|
| mViewEmbedder = embedder;
|
| - mInputConnectionFactory = new AdapterInputConnectionFactory();
|
| - mEditable = Editable.Factory.getInstance().newEditable("");
|
| - Selection.setSelection(mEditable, 0);
|
| + if (CommandLine.getInstance().hasSwitch(ContentSwitches.USE_IME_THREAD)) {
|
| + mInputConnectionFactory =
|
| + new ThreadedInputConnectionFactory(mInputMethodManagerWrapper);
|
| + } else {
|
| + mInputConnectionFactory = new ReplicaInputConnection.Factory();
|
| + }
|
| // Deep copy newConfig so that we can notice the difference.
|
| mCurrentConfig = new Configuration(
|
| mViewEmbedder.getAttachedView().getResources().getConfiguration());
|
| }
|
|
|
| /**
|
| - * Default factory for AdapterInputConnection classes.
|
| - */
|
| - static class AdapterInputConnectionFactory {
|
| - AdapterInputConnection get(View view, ImeAdapter imeAdapter, int initialSelStart,
|
| - int initialSelEnd, EditorInfo outAttrs) {
|
| - return new AdapterInputConnection(
|
| - view, imeAdapter, initialSelStart, initialSelEnd, outAttrs);
|
| - }
|
| - }
|
| -
|
| - /**
|
| * @see View#onCreateInputConnection(EditorInfo)
|
| */
|
| - public AdapterInputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
| + public ChromiumBaseInputConnection onCreateInputConnection(EditorInfo outAttrs) {
|
| + // InputMethodService evaluates fullscreen mode even when the new input connection is
|
| + // null. This makes sure IME doesn't enter fullscreen mode or open custom UI.
|
| + outAttrs.imeOptions =
|
| + EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
|
| // Without this line, some third-party IMEs will try to compose text even when
|
| - // not on an editable node. Even when we return null here, key events can still go through
|
| - // ImeAdapter#dispatchKeyEvent().
|
| + // not on an editable node. Even when we return null here, key events can still go
|
| + // through ImeAdapter#dispatchKeyEvent().
|
| if (mTextInputType == TextInputType.NONE) {
|
| + if (mInputConnection != null) mInputConnection.unblock();
|
| mInputConnection = null;
|
| if (DEBUG_LOGS) Log.w(TAG, "onCreateInputConnection returns null.");
|
| - // InputMethodService evaluates fullscreen mode even when the new input connection is
|
| - // null. This makes sure IME doesn't enter fullscreen mode or open custom UI.
|
| - outAttrs.imeOptions =
|
| - EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
|
| return null;
|
| }
|
| -
|
| - if (!isTextInputType(mTextInputType)) {
|
| - // Although onCheckIsTextEditor will return false in this case, the EditorInfo
|
| - // is still used by the InputMethodService. Need to make sure the IME doesn't
|
| - // enter fullscreen mode.
|
| - outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
|
| - }
|
| - int initialSelStart = Selection.getSelectionStart(mEditable);
|
| - int initialSelEnd = outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable);
|
| - mInputConnection = mInputConnectionFactory.get(
|
| - mViewEmbedder.getAttachedView(), this, initialSelStart, initialSelEnd, outAttrs);
|
| - if (DEBUG_LOGS) Log.w(TAG, "onCreateInputConnection");
|
| + mInputConnection = mInputConnectionFactory.initializeAndGet(
|
| + mViewEmbedder.getAttachedView(), this, mTextInputType, mTextInputFlags, outAttrs);
|
| + if (DEBUG_LOGS) Log.w(TAG, "onCreateInputConnection: " + mInputConnection);
|
| return mInputConnection;
|
| }
|
|
|
| @@ -177,49 +156,23 @@ public class ImeAdapter {
|
| }
|
|
|
| @VisibleForTesting
|
| - void setInputConnectionFactory(AdapterInputConnectionFactory factory) {
|
| + void setInputConnectionFactory(ChromiumBaseInputConnection.Factory factory) {
|
| mInputConnectionFactory = factory;
|
| }
|
|
|
| - /**
|
| - * Set the current active InputConnection when a new InputConnection is constructed.
|
| - * @param inputConnection The input connection that is currently used with IME.
|
| - */
|
| - void setInputConnection(AdapterInputConnection inputConnection) {
|
| - mInputConnection = inputConnection;
|
| + @VisibleForTesting
|
| + ChromiumBaseInputConnection.Factory getInputConnectionFactoryForTest() {
|
| + return mInputConnectionFactory;
|
| }
|
|
|
| /**
|
| * Get the current input connection for testing purposes.
|
| */
|
| @VisibleForTesting
|
| - public AdapterInputConnection getInputConnectionForTest() {
|
| + public ChromiumBaseInputConnection getInputConnectionForTest() {
|
| return mInputConnection;
|
| }
|
|
|
| - /**
|
| - * @return The Editable instance that will be shared across AdapterInputConnection instances.
|
| - */
|
| - Editable getEditable() {
|
| - return mEditable;
|
| - }
|
| -
|
| - /**
|
| - * Should be used only by AdapterInputConnection.
|
| - * @return The input type of currently focused element.
|
| - */
|
| - int getTextInputType() {
|
| - return mTextInputType;
|
| - }
|
| -
|
| - /**
|
| - * Should be used only by AdapterInputConnection.
|
| - * @return The input flags of the currently focused element.
|
| - */
|
| - int getTextInputFlags() {
|
| - return mTextInputFlags;
|
| - }
|
| -
|
| private static int getModifiers(int metaState) {
|
| int modifiers = 0;
|
| if ((metaState & KeyEvent.META_SHIFT_ON) != 0) {
|
| @@ -286,8 +239,10 @@ public class ImeAdapter {
|
| public void updateState(String text, int selectionStart, int selectionEnd, int compositionStart,
|
| int compositionEnd, boolean isNonImeChange) {
|
| if (mInputConnection == null) return;
|
| - mInputConnection.updateState(text, selectionStart, selectionEnd, compositionStart,
|
| - compositionEnd, isNonImeChange);
|
| + boolean singleLine = mTextInputType != TextInputType.TEXT_AREA
|
| + && mTextInputType != TextInputType.CONTENT_EDITABLE;
|
| + mInputConnection.updateStateOnUiThread(text, selectionStart, selectionEnd, compositionStart,
|
| + compositionEnd, singleLine, isNonImeChange);
|
| }
|
|
|
| /**
|
| @@ -376,8 +331,7 @@ public class ImeAdapter {
|
| public void moveCursorToSelectionEnd() {
|
| if (DEBUG_LOGS) Log.w(TAG, "movecursorToEnd");
|
| if (mInputConnection != null) {
|
| - int selectionEnd = Selection.getSelectionEnd(mEditable);
|
| - mInputConnection.setSelection(selectionEnd, selectionEnd);
|
| + mInputConnection.moveCursorToSelectionEndOnUiThread();
|
| }
|
| }
|
|
|
| @@ -394,16 +348,28 @@ public class ImeAdapter {
|
| return isTextInputType(mTextInputType);
|
| }
|
|
|
| + /**
|
| + * See {@link View#dispatchKeyEvent(KeyEvent)}
|
| + */
|
| public boolean dispatchKeyEvent(KeyEvent event) {
|
| if (DEBUG_LOGS) Log.w(TAG, "dispatchKeyEvent: action [%d], keycode [%d]", event.getAction(),
|
| event.getKeyCode());
|
| - if (mInputConnection != null) {
|
| - return mInputConnection.sendKeyEvent(event);
|
| - }
|
| + if (mInputConnection != null) return mInputConnection.sendKeyEventOnUiThread(event);
|
| return sendKeyEvent(event);
|
| }
|
|
|
| /**
|
| + * Resets IME adapter and hides keyboard. Note that this will also unblock input connection.
|
| + */
|
| + public void reset() {
|
| + if (DEBUG_LOGS) Log.w(TAG, "reset");
|
| + mTextInputType = TextInputType.NONE;
|
| + mTextInputFlags = 0;
|
| + // This will trigger unblocking if necessary.
|
| + hideKeyboard();
|
| + }
|
| +
|
| + /**
|
| * Update selection to input method manager.
|
| *
|
| * @param selectionStart The selection start.
|
| @@ -421,8 +387,9 @@ public class ImeAdapter {
|
| * Restart input (finish composition and change EditorInfo, such as input type).
|
| */
|
| void restartInput() {
|
| + // This will eventually cause input method manager to call View#onCreateInputConnection().
|
| mInputMethodManagerWrapper.restartInput(mViewEmbedder.getAttachedView());
|
| - if (mInputConnection != null) mInputConnection.onRestartInput();
|
| + if (mInputConnection != null) mInputConnection.onRestartInputOnUiThread();
|
| }
|
|
|
| /**
|
| @@ -447,6 +414,10 @@ public class ImeAdapter {
|
| return true;
|
| }
|
|
|
| + void notifyUserAction() {
|
| + mInputMethodManagerWrapper.notifyUserAction();
|
| + }
|
| +
|
| @VisibleForTesting
|
| protected void sendSyntheticKeyPress(int keyCode, int flags) {
|
| long eventTime = SystemClock.uptimeMillis();
|
| @@ -488,9 +459,10 @@ public class ImeAdapter {
|
| }
|
|
|
| @VisibleForTesting
|
| - void finishComposingText() {
|
| - if (mNativeImeAdapterAndroid == 0) return;
|
| + boolean finishComposingText() {
|
| + if (mNativeImeAdapterAndroid == 0) return false;
|
| nativeFinishComposingText(mNativeImeAdapterAndroid);
|
| + return true;
|
| }
|
|
|
| boolean sendKeyEvent(KeyEvent event) {
|
| @@ -553,7 +525,7 @@ public class ImeAdapter {
|
| * @param end The end of the composition.
|
| * @return Whether the native counterpart of ImeAdapter received the call.
|
| */
|
| - boolean setComposingRegion(CharSequence text, int start, int end) {
|
| + boolean setComposingRegion(int start, int end) {
|
| if (mNativeImeAdapterAndroid == 0) return false;
|
| nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end);
|
| return true;
|
| @@ -567,6 +539,16 @@ public class ImeAdapter {
|
| }
|
| }
|
|
|
| + /**
|
| + * Send a request to the native counterpart to give the latest text input state update.
|
| + */
|
| + boolean requestTextInputStateUpdate() {
|
| + if (mNativeImeAdapterAndroid == 0) return false;
|
| + // You won't get state update anyways.
|
| + if (mInputConnection == null) return false;
|
| + return nativeRequestTextInputStateUpdate(mNativeImeAdapterAndroid);
|
| + }
|
| +
|
| @CalledByNative
|
| private void populateUnderlinesFromSpans(CharSequence text, long underlines) {
|
| if (DEBUG_LOGS) {
|
| @@ -596,6 +578,12 @@ public class ImeAdapter {
|
| }
|
|
|
| @CalledByNative
|
| + private void unblock() {
|
| + if (DEBUG_LOGS) Log.w(TAG, "unblock");
|
| + if (mInputConnection != null) mInputConnection.unblock();
|
| + }
|
| +
|
| + @CalledByNative
|
| private void detach() {
|
| if (DEBUG_LOGS) Log.w(TAG, "detach");
|
| mNativeImeAdapterAndroid = 0;
|
| @@ -603,32 +591,22 @@ public class ImeAdapter {
|
|
|
| private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid,
|
| int eventType, long timestampMs, int keyCode, int modifiers, int unicodeChar);
|
| -
|
| private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event,
|
| int action, int modifiers, long timestampMs, int keyCode, int scanCode,
|
| boolean isSystemKey, int unicodeChar);
|
| -
|
| private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end);
|
| -
|
| private static native void nativeAppendBackgroundColorSpan(long underlinePtr, int start,
|
| int end, int backgroundColor);
|
| -
|
| private native void nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text,
|
| String textStr, int newCursorPosition);
|
| -
|
| private native void nativeCommitText(long nativeImeAdapterAndroid, String textStr);
|
| -
|
| private native void nativeFinishComposingText(long nativeImeAdapterAndroid);
|
| -
|
| private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid);
|
| -
|
| private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid,
|
| int start, int end);
|
| -
|
| private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end);
|
| -
|
| private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid,
|
| int before, int after);
|
| -
|
| private native void nativeResetImeAdapter(long nativeImeAdapterAndroid);
|
| + private native boolean nativeRequestTextInputStateUpdate(long nativeImeAdapterAndroid);
|
| }
|
|
|