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 |