| 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 55746b956cbb1d2904fb20f3a6a9328116e619ed..8de6adea10a9918720e32d561705eb96d057134d 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,12 +4,8 @@
|
|
|
| package org.chromium.content.browser.input;
|
|
|
| -import android.annotation.SuppressLint;
|
| import android.content.res.Configuration;
|
| -import android.graphics.Rect;
|
| import android.os.Build;
|
| -import android.os.Bundle;
|
| -import android.os.Handler;
|
| import android.os.ResultReceiver;
|
| import android.os.SystemClock;
|
| import android.text.SpannableString;
|
| @@ -23,10 +19,8 @@
|
| import android.view.inputmethod.BaseInputConnection;
|
| import android.view.inputmethod.EditorInfo;
|
| import android.view.inputmethod.InputConnection;
|
| -import android.view.inputmethod.InputMethodManager;
|
|
|
| import org.chromium.base.Log;
|
| -import org.chromium.base.TraceEvent;
|
| import org.chromium.base.VisibleForTesting;
|
| import org.chromium.base.annotations.CalledByNative;
|
| import org.chromium.base.annotations.JNINamespace;
|
| @@ -34,15 +28,9 @@
|
| import org.chromium.blink_public.web.WebInputEventType;
|
| import org.chromium.blink_public.web.WebTextInputMode;
|
| import org.chromium.content.browser.RenderCoordinates;
|
| -import org.chromium.content.browser.ViewUtils;
|
| import org.chromium.content.browser.picker.InputDialogContainer;
|
| -import org.chromium.content_public.browser.ImeEventObserver;
|
| import org.chromium.content_public.browser.WebContents;
|
| import org.chromium.ui.base.ime.TextInputType;
|
| -
|
| -import java.lang.ref.WeakReference;
|
| -import java.util.ArrayList;
|
| -import java.util.List;
|
|
|
| /**
|
| * Adapts and plumbs android IME service onto the chrome text input API.
|
| @@ -72,6 +60,36 @@
|
|
|
| public static final int COMPOSITION_KEY_CODE = 229;
|
|
|
| + /**
|
| + * Interface for the delegate that needs to be notified of IME changes.
|
| + */
|
| + public interface ImeAdapterDelegate {
|
| + /**
|
| + * Called to notify the delegate about synthetic/real key events before sending to renderer.
|
| + */
|
| + void onImeEvent();
|
| +
|
| + /**
|
| + * Called when the keyboard could not be shown due to the hardware keyboard being present.
|
| + */
|
| + void onKeyboardBoundsUnchanged();
|
| +
|
| + /**
|
| + * @see BaseInputConnection#performContextMenuAction(int)
|
| + */
|
| + boolean performContextMenuAction(int id);
|
| +
|
| + /**
|
| + * @return View that the keyboard should be attached to.
|
| + */
|
| + View getAttachedView();
|
| +
|
| + /**
|
| + * @return Object that should be called for all keyboard show and hide requests.
|
| + */
|
| + ResultReceiver getNewShowKeyboardReceiver();
|
| + }
|
| +
|
| static char[] sSingleCharArray = new char[1];
|
| static KeyCharacterMap sKeyCharacterMap;
|
|
|
| @@ -80,29 +98,16 @@
|
| private ChromiumBaseInputConnection mInputConnection;
|
| private ChromiumBaseInputConnection.Factory mInputConnectionFactory;
|
|
|
| - // NOTE: This object will not be released by Android framework until the matching
|
| - // ResultReceiver in the InputMethodService (IME app) gets gc'ed.
|
| - private ShowKeyboardResultReceiver mShowKeyboardResultReceiver;
|
| -
|
| - private final WebContents mWebContents;
|
| - private View mContainerView;
|
| -
|
| + 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 final List<ImeEventObserver> mEventObservers = new ArrayList<>();
|
| -
|
| private int mTextInputType = TextInputType.NONE;
|
| private int mTextInputFlags;
|
| private int mTextInputMode = WebTextInputMode.kDefault;
|
| - private boolean mNodeEditable;
|
| -
|
| - // Viewport rect before the OSK was brought up.
|
| - // Used to tell View#onSizeChanged to focus a form element.
|
| - private final Rect mFocusPreOSKViewportRect = new Rect();
|
|
|
| // Keep the current configuration to detect the change when onConfigurationChanged() is called.
|
| private Configuration mCurrentConfig;
|
| @@ -118,45 +123,18 @@
|
| private boolean mIsConnected;
|
|
|
| /**
|
| - * {@ResultReceiver} passed in InputMethodManager#showSoftInput}. We need this to scroll to the
|
| - * editable node at the right timing, which is after input method window shows up.
|
| - */
|
| - // TODO(crbug.com/635567): Fix this properly.
|
| - @SuppressLint("ParcelCreator")
|
| - private static class ShowKeyboardResultReceiver extends ResultReceiver {
|
| - // Unfortunately, the memory life cycle of ResultReceiver object, once passed in
|
| - // showSoftInput(), is in the control of Android's input method framework and IME app,
|
| - // so we use a weakref to avoid tying ImeAdapter's lifetime to that of ResultReceiver
|
| - // object.
|
| - private final WeakReference<ImeAdapter> mImeAdapter;
|
| -
|
| - public ShowKeyboardResultReceiver(ImeAdapter imeAdapter, Handler handler) {
|
| - super(handler);
|
| - mImeAdapter = new WeakReference<>(imeAdapter);
|
| - }
|
| -
|
| - @Override
|
| - public void onReceiveResult(int resultCode, Bundle resultData) {
|
| - ImeAdapter imeAdapter = mImeAdapter.get();
|
| - if (imeAdapter == null) return;
|
| - imeAdapter.onShowKeyboardReceiveResult(resultCode);
|
| - }
|
| - }
|
| -
|
| - /**
|
| + * @param initialConfiguration The initial configuration of the embedder's attached view.
|
| * @param webContents WebContents instance with which this ImeAdapter is associated.
|
| - * @param containerView {@link View} instance which input events are posted on.
|
| * @param wrapper InputMethodManagerWrapper that should receive all the call directed to
|
| * InputMethodManager.
|
| - */
|
| - public ImeAdapter(
|
| - WebContents webContents, View containerView, InputMethodManagerWrapper wrapper) {
|
| - mWebContents = webContents;
|
| - mContainerView = containerView;
|
| + * @param embedder The view that is used for callbacks from ImeAdapter.
|
| + */
|
| + public ImeAdapter(Configuration initialConfiguration, WebContents webContents,
|
| + InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder) {
|
| mInputMethodManagerWrapper = wrapper;
|
| -
|
| + mViewEmbedder = embedder;
|
| // Deep copy newConfig so that we can notice the difference.
|
| - mCurrentConfig = new Configuration(mContainerView.getResources().getConfiguration());
|
| + mCurrentConfig = new Configuration(initialConfiguration);
|
| // CursorAnchroInfo is supported only after L.
|
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
| mCursorAnchorInfoController = CursorAnchorInfoController.create(wrapper,
|
| @@ -188,18 +166,6 @@
|
| mNativeImeAdapterAndroid = nativeInit(webContents);
|
| }
|
|
|
| - /**
|
| - * Set the container view.
|
| - * @param containerView {@link View} which this ImeAdapter works on.
|
| - */
|
| - public void setContainerView(View containerView) {
|
| - mContainerView = containerView;
|
| - }
|
| -
|
| - public void addEventObserver(ImeEventObserver eventObserver) {
|
| - mEventObservers.add(eventObserver);
|
| - }
|
| -
|
| private void createInputConnectionFactory() {
|
| if (mInputConnectionFactory != null) return;
|
| mInputConnectionFactory = new ThreadedInputConnectionFactory(mInputMethodManagerWrapper);
|
| @@ -228,14 +194,15 @@
|
| return null;
|
| }
|
| if (mInputConnectionFactory == null) return null;
|
| - setInputConnection(mInputConnectionFactory.initializeAndGet(mContainerView, this,
|
| - mTextInputType, mTextInputFlags, mTextInputMode, mLastSelectionStart,
|
| + setInputConnection(mInputConnectionFactory.initializeAndGet(mViewEmbedder.getAttachedView(),
|
| + this, mTextInputType, mTextInputFlags, mTextInputMode, mLastSelectionStart,
|
| mLastSelectionEnd, outAttrs));
|
| if (DEBUG_LOGS) Log.i(TAG, "onCreateInputConnection: " + mInputConnection);
|
|
|
| if (mCursorAnchorInfoController != null) {
|
| - mCursorAnchorInfoController.onRequestCursorUpdates(false /* not an immediate request */,
|
| - false /* disable monitoring */, mContainerView);
|
| + mCursorAnchorInfoController.onRequestCursorUpdates(
|
| + false /* not an immediate request */, false /* disable monitoring */,
|
| + mViewEmbedder.getAttachedView());
|
| }
|
| if (isValid()) {
|
| nativeRequestCursorUpdate(mNativeImeAdapterAndroid,
|
| @@ -321,71 +288,54 @@
|
| * selection.
|
| * @param replyToRequest True when the update was requested by IME.
|
| */
|
| - @CalledByNative
|
| - private void updateState(int textInputType, int textInputFlags, int textInputMode,
|
| + public void updateState(int textInputType, int textInputFlags, int textInputMode,
|
| boolean showIfNeeded, String text, int selectionStart, int selectionEnd,
|
| int compositionStart, int compositionEnd, boolean replyToRequest) {
|
| - TraceEvent.begin("ImeAdapter.updateState");
|
| - try {
|
| - if (DEBUG_LOGS) {
|
| - Log.i(TAG, "updateState: type [%d->%d], flags [%d], show [%b], ", mTextInputType,
|
| - textInputType, textInputFlags, showIfNeeded);
|
| - }
|
| - boolean needsRestart = false;
|
| - if (mRestartInputOnNextStateUpdate) {
|
| - needsRestart = true;
|
| - mRestartInputOnNextStateUpdate = false;
|
| - }
|
| -
|
| - mTextInputFlags = textInputFlags;
|
| - if (mTextInputMode != textInputMode) {
|
| - boolean editable = textInputMode != TextInputType.NONE;
|
| - if (mNodeEditable != editable) {
|
| - boolean password = textInputType == TextInputType.PASSWORD;
|
| - for (ImeEventObserver observer : mEventObservers) {
|
| - observer.onNodeAttributeUpdated(editable, password);
|
| - }
|
| - mNodeEditable = editable;
|
| - }
|
| - mTextInputMode = textInputMode;
|
| - needsRestart = true;
|
| - }
|
| - if (mTextInputType != textInputType) {
|
| - mTextInputType = textInputType;
|
| - needsRestart = true;
|
| - }
|
| - 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 (textInputType == TextInputType.NONE) {
|
| - hideKeyboard();
|
| - } else {
|
| - if (needsRestart) restartInput();
|
| - // There is no API for us to get notified of user's dismissal of keyboard.
|
| - // Therefore, we should try to show keyboard even when text input type hasn't
|
| - // changed.
|
| - if (showIfNeeded) showSoftKeyboard();
|
| - }
|
| -
|
| - if (mInputConnection != null) {
|
| - boolean singleLine = mTextInputType != TextInputType.TEXT_AREA
|
| - && mTextInputType != TextInputType.CONTENT_EDITABLE;
|
| - mInputConnection.updateStateOnUiThread(text, selectionStart, selectionEnd,
|
| - compositionStart, compositionEnd, singleLine, replyToRequest);
|
| - }
|
| - } finally {
|
| - TraceEvent.end("ImeAdapter.updateState");
|
| - }
|
| + if (DEBUG_LOGS) {
|
| + Log.i(TAG, "updateState: type [%d->%d], flags [%d], show [%b], ", mTextInputType,
|
| + textInputType, textInputFlags, showIfNeeded);
|
| + }
|
| + boolean needsRestart = false;
|
| + if (mRestartInputOnNextStateUpdate) {
|
| + needsRestart = true;
|
| + mRestartInputOnNextStateUpdate = false;
|
| + }
|
| +
|
| + mTextInputFlags = textInputFlags;
|
| + if (mTextInputMode != textInputMode) {
|
| + mTextInputMode = textInputMode;
|
| + needsRestart = true;
|
| + }
|
| + if (mTextInputType != textInputType) {
|
| + mTextInputType = textInputType;
|
| + needsRestart = true;
|
| + }
|
| + 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 (textInputType == TextInputType.NONE) {
|
| + hideKeyboard();
|
| + } else {
|
| + if (needsRestart) restartInput();
|
| + // There is no API for us to get notified of user's dismissal of keyboard.
|
| + // Therefore, we should try to show keyboard even when text input type hasn't changed.
|
| + if (showIfNeeded) showSoftKeyboard();
|
| + }
|
| +
|
| + if (mInputConnection == null) return;
|
| + boolean singleLine = mTextInputType != TextInputType.TEXT_AREA
|
| + && mTextInputType != TextInputType.CONTENT_EDITABLE;
|
| + mInputConnection.updateStateOnUiThread(text, selectionStart, selectionEnd, compositionStart,
|
| + compositionEnd, singleLine, replyToRequest);
|
| }
|
|
|
| /**
|
| @@ -393,43 +343,12 @@
|
| */
|
| private void showSoftKeyboard() {
|
| if (DEBUG_LOGS) Log.i(TAG, "showSoftKeyboard");
|
| - mInputMethodManagerWrapper.showSoftInput(mContainerView, 0, getNewShowKeyboardReceiver());
|
| - if (mContainerView.getResources().getConfiguration().keyboard
|
| + mInputMethodManagerWrapper.showSoftInput(
|
| + mViewEmbedder.getAttachedView(), 0, mViewEmbedder.getNewShowKeyboardReceiver());
|
| + if (mViewEmbedder.getAttachedView().getResources().getConfiguration().keyboard
|
| != Configuration.KEYBOARD_NOKEYS) {
|
| - mWebContents.scrollFocusedEditableNodeIntoView();
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Call this when we get result from ResultReceiver passed in calling showSoftInput().
|
| - * @param resultCode The result of showSoftInput() as defined in InputMethodManager.
|
| - */
|
| - public void onShowKeyboardReceiveResult(int resultCode) {
|
| - if (resultCode == InputMethodManager.RESULT_SHOWN) {
|
| - // If OSK is newly shown, delay the form focus until
|
| - // the onSizeChanged (in order to adjust relative to the
|
| - // new size).
|
| - // TODO(jdduke): We should not assume that onSizeChanged will
|
| - // always be called, crbug.com/294908.
|
| - mContainerView.getWindowVisibleDisplayFrame(mFocusPreOSKViewportRect);
|
| - } else if (ViewUtils.hasFocus(mContainerView)
|
| - && resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN) {
|
| - // If the OSK was already there, focus the form immediately.
|
| - mWebContents.scrollFocusedEditableNodeIntoView();
|
| - }
|
| - }
|
| -
|
| - public Rect getFocusPreOSKViewportRect() {
|
| - return mFocusPreOSKViewportRect;
|
| - }
|
| -
|
| - @VisibleForTesting
|
| - public ResultReceiver getNewShowKeyboardReceiver() {
|
| - if (mShowKeyboardResultReceiver == null) {
|
| - // Note: the returned object will get leaked by Android framework.
|
| - mShowKeyboardResultReceiver = new ShowKeyboardResultReceiver(this, new Handler());
|
| - }
|
| - return mShowKeyboardResultReceiver;
|
| + mViewEmbedder.onKeyboardBoundsUnchanged();
|
| + }
|
| }
|
|
|
| /**
|
| @@ -437,7 +356,7 @@
|
| */
|
| private void hideKeyboard() {
|
| if (DEBUG_LOGS) Log.i(TAG, "hideKeyboard");
|
| - View view = mContainerView;
|
| + View view = mViewEmbedder.getAttachedView();
|
| if (mInputMethodManagerWrapper.isActive(view)) {
|
| // NOTE: we should not set ResultReceiver here. Otherwise, IMM will own ContentViewCore
|
| // and ImeAdapter even after input method goes away and result gets received.
|
| @@ -579,8 +498,8 @@
|
| */
|
| void updateSelection(
|
| int selectionStart, int selectionEnd, int compositionStart, int compositionEnd) {
|
| - mInputMethodManagerWrapper.updateSelection(
|
| - mContainerView, selectionStart, selectionEnd, compositionStart, compositionEnd);
|
| + mInputMethodManagerWrapper.updateSelection(mViewEmbedder.getAttachedView(),
|
| + selectionStart, selectionEnd, compositionStart, compositionEnd);
|
| }
|
|
|
| /**
|
| @@ -588,7 +507,7 @@
|
| */
|
| void restartInput() {
|
| // This will eventually cause input method manager to call View#onCreateInputConnection().
|
| - mInputMethodManagerWrapper.restartInput(mContainerView);
|
| + mInputMethodManagerWrapper.restartInput(mViewEmbedder.getAttachedView());
|
| if (mInputConnection != null) mInputConnection.onRestartInputOnUiThread();
|
| }
|
|
|
| @@ -597,22 +516,7 @@
|
| */
|
| boolean performContextMenuAction(int id) {
|
| if (DEBUG_LOGS) Log.i(TAG, "performContextMenuAction: id [%d]", id);
|
| - switch (id) {
|
| - case android.R.id.selectAll:
|
| - mWebContents.selectAll();
|
| - return true;
|
| - case android.R.id.cut:
|
| - mWebContents.cut();
|
| - return true;
|
| - case android.R.id.copy:
|
| - mWebContents.copy();
|
| - return true;
|
| - case android.R.id.paste:
|
| - mWebContents.paste();
|
| - return true;
|
| - default:
|
| - return false;
|
| - }
|
| + return mViewEmbedder.performContextMenuAction(id);
|
| }
|
|
|
| boolean performEditorAction(int actionCode) {
|
| @@ -646,11 +550,6 @@
|
| flags));
|
| }
|
|
|
| - private void onImeEvent() {
|
| - for (ImeEventObserver observer : mEventObservers) observer.onImeEvent();
|
| - if (mNodeEditable) mWebContents.dismissTextHandles();
|
| - }
|
| -
|
| boolean sendCompositionToNative(
|
| CharSequence text, int newCursorPosition, boolean isCommit, int unicodeFromKeyEvent) {
|
| if (!isValid()) return false;
|
| @@ -662,7 +561,7 @@
|
| return true;
|
| }
|
|
|
| - onImeEvent();
|
| + mViewEmbedder.onImeEvent();
|
| long timestampMs = SystemClock.uptimeMillis();
|
| nativeSendKeyEvent(mNativeImeAdapterAndroid, null, WebInputEventType.RawKeyDown, 0,
|
| timestampMs, COMPOSITION_KEY_CODE, 0, false, unicodeFromKeyEvent);
|
| @@ -702,7 +601,7 @@
|
| // sends ACTION_DOWN), so it's fine to silently drop it.
|
| return false;
|
| }
|
| - onImeEvent();
|
| + mViewEmbedder.onImeEvent();
|
|
|
| return nativeSendKeyEvent(mNativeImeAdapterAndroid, event, type,
|
| getModifiers(event.getMetaState()), event.getEventTime(), event.getKeyCode(),
|
| @@ -718,7 +617,7 @@
|
| * @return Whether the native counterpart of ImeAdapter received the call.
|
| */
|
| boolean deleteSurroundingText(int beforeLength, int afterLength) {
|
| - onImeEvent();
|
| + mViewEmbedder.onImeEvent();
|
| if (!isValid()) return false;
|
| nativeSendKeyEvent(mNativeImeAdapterAndroid, null, WebInputEventType.RawKeyDown, 0,
|
| SystemClock.uptimeMillis(), COMPOSITION_KEY_CODE, 0, false, 0);
|
| @@ -737,7 +636,7 @@
|
| * @return Whether the native counterpart of ImeAdapter received the call.
|
| */
|
| boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
|
| - onImeEvent();
|
| + mViewEmbedder.onImeEvent();
|
| if (!isValid()) return false;
|
| nativeSendKeyEvent(mNativeImeAdapterAndroid, null, WebInputEventType.RawKeyDown, 0,
|
| SystemClock.uptimeMillis(), COMPOSITION_KEY_CODE, 0, false, 0);
|
| @@ -813,8 +712,8 @@
|
| nativeRequestCursorUpdate(mNativeImeAdapterAndroid, immediateRequest, monitorRequest);
|
| }
|
| if (mCursorAnchorInfoController == null) return false;
|
| - return mCursorAnchorInfoController.onRequestCursorUpdates(
|
| - immediateRequest, monitorRequest, mContainerView);
|
| + return mCursorAnchorInfoController.onRequestCursorUpdates(immediateRequest, monitorRequest,
|
| + mViewEmbedder.getAttachedView());
|
| }
|
|
|
| /**
|
| @@ -835,7 +734,7 @@
|
| if (mCursorAnchorInfoController == null) return;
|
| mCursorAnchorInfoController.onUpdateFrameInfo(renderCoordinates, hasInsertionMarker,
|
| isInsertionMarkerVisible, insertionMarkerHorizontal, insertionMarkerTop,
|
| - insertionMarkerBottom, mContainerView);
|
| + insertionMarkerBottom, mViewEmbedder.getAttachedView());
|
| }
|
|
|
| @CalledByNative
|
| @@ -869,7 +768,8 @@
|
| @CalledByNative
|
| private void setCharacterBounds(float[] characterBounds) {
|
| if (mCursorAnchorInfoController == null) return;
|
| - mCursorAnchorInfoController.setCompositionCharacterBounds(characterBounds, mContainerView);
|
| + mCursorAnchorInfoController.setCompositionCharacterBounds(characterBounds,
|
| + mViewEmbedder.getAttachedView());
|
| }
|
|
|
| @CalledByNative
|
|
|