| 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/ImeAdapter.java b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java | 
| similarity index 48% | 
| rename from content/public/android/java/src/org/chromium/content/browser/ImeAdapter.java | 
| rename to content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java | 
| index 1f73502d70a17ff4705137121f602660c23553eb..1ea57b4a105d086d732c319d68876474f895155f 100644 | 
| --- a/content/public/android/java/src/org/chromium/content/browser/ImeAdapter.java | 
| +++ b/content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java | 
| @@ -2,22 +2,15 @@ | 
| // Use of this source code is governed by a BSD-style license that can be | 
| // found in the LICENSE file. | 
|  | 
| -package org.chromium.content.browser; | 
| +package org.chromium.content.browser.input; | 
|  | 
| import android.content.Context; | 
| import android.os.Handler; | 
| import android.os.ResultReceiver; | 
| -import android.text.Editable; | 
| -import android.text.InputType; | 
| -import android.text.Selection; | 
| -import android.util.Log; | 
| import android.view.KeyCharacterMap; | 
| import android.view.KeyEvent; | 
| import android.view.View; | 
| -import android.view.inputmethod.BaseInputConnection; | 
| import android.view.inputmethod.EditorInfo; | 
| -import android.view.inputmethod.ExtractedText; | 
| -import android.view.inputmethod.ExtractedTextRequest; | 
|  | 
| import com.google.common.annotations.VisibleForTesting; | 
|  | 
| @@ -46,25 +39,44 @@ import org.chromium.base.JNINamespace; | 
| * lifetime of the object. | 
| */ | 
| @JNINamespace("content") | 
| -class ImeAdapter { | 
| -    interface ViewEmbedder { | 
| +public class ImeAdapter { | 
| +    public interface ViewEmbedder { | 
| /** | 
| * @param isFinish whether the event is occurring because input is finished. | 
| */ | 
| -        public void onImeEvent(boolean isFinish); | 
| -        public void onSetFieldValue(); | 
| -        public void onDismissInput(); | 
| -        public View getAttachedView(); | 
| -        public ResultReceiver getNewShowKeyboardReceiver(); | 
| +        void onImeEvent(boolean isFinish); | 
| +        void onSetFieldValue(); | 
| +        void onDismissInput(); | 
| +        View getAttachedView(); | 
| +        ResultReceiver getNewShowKeyboardReceiver(); | 
| } | 
|  | 
| -    static final int COMPOSITION_KEY_CODE = 229; | 
| -    /** | 
| -     * Selection value should be -1 if not known. See EditorInfo.java for details. | 
| -     */ | 
| -    static final int INVALID_SELECTION = -1; | 
| -    static final int INVALID_COMPOSITION = -1; | 
| +    private class DelayedDismissInput implements Runnable { | 
| +        private final int mNativeImeAdapter; | 
| + | 
| +        DelayedDismissInput(int nativeImeAdapter) { | 
| +            mNativeImeAdapter = nativeImeAdapter; | 
| +        } | 
| + | 
| +        @Override | 
| +        public void run() { | 
| +            attach(mNativeImeAdapter, sTextInputTypeNone, AdapterInputConnection.INVALID_SELECTION, | 
| +                    AdapterInputConnection.INVALID_SELECTION); | 
| +            dismissInput(true); | 
| +        } | 
| +    } | 
| + | 
| +    private static final int COMPOSITION_KEY_CODE = 229; | 
| + | 
| +    // Delay introduced to avoid hiding the keyboard if new show requests are received. | 
| +    // The time required by the unfocus-focus events triggered by tab has been measured in soju: | 
| +    // Mean: 18.633 ms, Standard deviation: 7.9837 ms. | 
| +    // The value here should be higher enough to cover these cases, but not too high to avoid | 
| +    // letting the user perceiving important delays. | 
| +    private static final int INPUT_DISMISS_DELAY = 150; | 
|  | 
| +    // All the constants that are retrieved from the C++ code. | 
| +    // They get set through initializeWebInputEvents and initializeTextInputTypes calls. | 
| static int sEventTypeRawKeyDown; | 
| static int sEventTypeKeyUp; | 
| static int sEventTypeChar; | 
| @@ -85,80 +97,29 @@ class ImeAdapter { | 
| static int sModifierCapsLockOn; | 
| static int sModifierNumLockOn; | 
|  | 
| -    @CalledByNative | 
| -    static void initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp, | 
| -            int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl, | 
| -            int modifierCapsLockOn, int modifierNumLockOn) { | 
| -        sEventTypeRawKeyDown = eventTypeRawKeyDown; | 
| -        sEventTypeKeyUp = eventTypeKeyUp; | 
| -        sEventTypeChar = eventTypeChar; | 
| -        sModifierShift = modifierShift; | 
| -        sModifierAlt = modifierAlt; | 
| -        sModifierCtrl = modifierCtrl; | 
| -        sModifierCapsLockOn = modifierCapsLockOn; | 
| -        sModifierNumLockOn = modifierNumLockOn; | 
| -    } | 
| - | 
| -    @CalledByNative | 
| -    static void initializeTextInputTypes(int textInputTypeNone, int textInputTypeText, | 
| -            int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch, | 
| -            int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel, | 
| -            int textInputTypeNumber, int textInputTypeDate, int textInputTypeDateTime, | 
| -            int textInputTypeDateTimeLocal, int textInputTypeMonth, int textInputTypeTime, | 
| -            int textInputTypeWeek, int textInputTypeContentEditable) { | 
| -        sTextInputTypeNone = textInputTypeNone; | 
| -        sTextInputTypeText = textInputTypeText; | 
| -        sTextInputTypeTextArea = textInputTypeTextArea; | 
| -        sTextInputTypePassword = textInputTypePassword; | 
| -        sTextInputTypeSearch = textInputTypeSearch; | 
| -        sTextInputTypeUrl = textInputTypeUrl; | 
| -        sTextInputTypeEmail = textInputTypeEmail; | 
| -        sTextInputTypeTel = textInputTypeTel; | 
| -        sTextInputTypeNumber = textInputTypeNumber; | 
| -        sTextInputTypeWeek = textInputTypeWeek; | 
| -        sTextInputTypeContentEditable = textInputTypeContentEditable; | 
| -    } | 
| - | 
| private int mNativeImeAdapterAndroid; | 
| -    private int mTextInputType; | 
| -    private int mInitialSelectionStart; | 
| -    private int mInitialSelectionEnd; | 
| - | 
| private final Context mContext; | 
| private InputMethodManagerWrapper mInputMethodManagerWrapper; | 
| -    private final SelectionHandleController mSelectionHandleController; | 
| -    private final InsertionHandleController mInsertionHandleController; | 
| private AdapterInputConnection mInputConnection; | 
| private final ViewEmbedder mViewEmbedder; | 
| private final Handler mHandler; | 
| - | 
| -    private class DelayedDismissInput implements Runnable { | 
| -        private final int mNativeImeAdapter; | 
| - | 
| -        DelayedDismissInput(int nativeImeAdapter) { | 
| -            mNativeImeAdapter = nativeImeAdapter; | 
| -        } | 
| - | 
| -        @Override | 
| -        public void run() { | 
| -            attach(mNativeImeAdapter, sTextInputTypeNone, INVALID_SELECTION, INVALID_SELECTION); | 
| -            dismissInput(true); | 
| -        } | 
| -    } | 
| - | 
| private DelayedDismissInput mDismissInput = null; | 
| +    private final SelectionHandleController mSelectionHandleController; | 
| +    private final InsertionHandleController mInsertionHandleController; | 
| +    private int mTextInputType; | 
| +    private int mInitialSelectionStart; | 
| +    private int mInitialSelectionEnd; | 
|  | 
| @VisibleForTesting | 
| -    protected boolean mIsShowWithoutHideOutstanding = false; | 
| +    boolean mIsShowWithoutHideOutstanding = false; | 
|  | 
| -    // Delay introduced to avoid hiding the keyboard if new show requests are received. | 
| -    // The time required by the unfocus-focus events triggered by tab has been measured in soju: | 
| -    // Mean: 18.633 ms, Standard deviation: 7.9837 ms. | 
| -    // The value here should be higher enough to cover these cases, but not too high to avoid | 
| -    // letting the user perceiving important delays. | 
| -    private static final int INPUT_DISMISS_DELAY = 150; | 
| - | 
| -    ImeAdapter(Context context, SelectionHandleController selectionHandleController, | 
| +    /** | 
| +     * @param context View context. | 
| +     * @param selectionHandleController The controller that handles selection. | 
| +     * @param insertionHandleController The controller that handles insertion. | 
| +     * @param embedder The view that is used for callbacks from ImeAdapter. | 
| +     */ | 
| +    public ImeAdapter(Context context, SelectionHandleController selectionHandleController, | 
| InsertionHandleController insertionHandleController, ViewEmbedder embedder) { | 
| mContext = context; | 
| mInputMethodManagerWrapper = new InputMethodManagerWrapper(context); | 
| @@ -168,9 +129,11 @@ class ImeAdapter { | 
| mHandler = new Handler(); | 
| } | 
|  | 
| -    boolean isFor(int nativeImeAdapter, int textInputType) { | 
| -        return mNativeImeAdapterAndroid == nativeImeAdapter && | 
| -               mTextInputType == textInputType; | 
| +    public static class AdapterInputConnectionFactory { | 
| +        public AdapterInputConnection get(View view, ImeAdapter imeAdapter, | 
| +                EditorInfo outAttrs) { | 
| +            return new AdapterInputConnection(view, imeAdapter, outAttrs); | 
| +        } | 
| } | 
|  | 
| @VisibleForTesting | 
| @@ -178,11 +141,86 @@ class ImeAdapter { | 
| mInputMethodManagerWrapper = immw; | 
| } | 
|  | 
| -    private InputMethodManagerWrapper getInputMethodManagerWrapper() { | 
| +    /** | 
| +     * Should be only used by AdapterInputConnection. | 
| +     * @return InputMethodManagerWrapper that should receive all the calls directed to | 
| +     *         InputMethodManager. | 
| +     */ | 
| +    InputMethodManagerWrapper getInputMethodManagerWrapper() { | 
| return mInputMethodManagerWrapper; | 
| } | 
|  | 
| -    void attachAndShowIfNeeded(int nativeImeAdapter, int textInputType, | 
| +    /** | 
| +     * 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; | 
| +    } | 
| + | 
| +    /** | 
| +     * Should be only used by AdapterInputConnection. | 
| +     * @return The input type of currently focused element. | 
| +     */ | 
| +    int getTextInputType() { | 
| +        return mTextInputType; | 
| +    } | 
| + | 
| +    /** | 
| +     * Should be only used by AdapterInputConnection. | 
| +     * @return The starting index of the initial text selection. | 
| +     */ | 
| +    int getInitialSelectionStart() { | 
| +        return mInitialSelectionStart; | 
| +    } | 
| + | 
| +    /** | 
| +     * Should be only used by AdapterInputConnection. | 
| +     * @return The ending index of the initial text selection. | 
| +     */ | 
| +    int getInitialSelectionEnd() { | 
| +        return mInitialSelectionEnd; | 
| +    } | 
| + | 
| +    public static int getTextInputTypeNone() { | 
| +        return sTextInputTypeNone; | 
| +    } | 
| + | 
| +    private static int getModifiers(int metaState) { | 
| +        int modifiers = 0; | 
| +        if ((metaState & KeyEvent.META_SHIFT_ON) != 0) { | 
| +          modifiers |= sModifierShift; | 
| +        } | 
| +        if ((metaState & KeyEvent.META_ALT_ON) != 0) { | 
| +          modifiers |= sModifierAlt; | 
| +        } | 
| +        if ((metaState & KeyEvent.META_CTRL_ON) != 0) { | 
| +          modifiers |= sModifierCtrl; | 
| +        } | 
| +        if ((metaState & KeyEvent.META_CAPS_LOCK_ON) != 0) { | 
| +          modifiers |= sModifierCapsLockOn; | 
| +        } | 
| +        if ((metaState & KeyEvent.META_NUM_LOCK_ON) != 0) { | 
| +          modifiers |= sModifierNumLockOn; | 
| +        } | 
| +        return modifiers; | 
| +    } | 
| + | 
| +    void hideSelectionAndInsertionHandleControllers() { | 
| +        mSelectionHandleController.hideAndDisallowAutomaticShowing(); | 
| +        mInsertionHandleController.hideAndDisallowAutomaticShowing(); | 
| +    } | 
| + | 
| +    public boolean isActive() { | 
| +        return mInputConnection != null && mInputConnection.isActive(); | 
| +    } | 
| + | 
| +    private boolean isFor(int nativeImeAdapter, int textInputType) { | 
| +        return mNativeImeAdapterAndroid == nativeImeAdapter && | 
| +               mTextInputType == textInputType; | 
| +    } | 
| + | 
| +    public void attachAndShowIfNeeded(int nativeImeAdapter, int textInputType, | 
| int selectionStart, int selectionEnd, boolean showIfNeeded) { | 
| mHandler.removeCallbacks(mDismissInput); | 
|  | 
| @@ -213,7 +251,8 @@ class ImeAdapter { | 
| } | 
| } | 
|  | 
| -    void attach(int nativeImeAdapter, int textInputType, int selectionStart, int selectionEnd) { | 
| +    public void attach(int nativeImeAdapter, int textInputType, int selectionStart, | 
| +            int selectionEnd) { | 
| mNativeImeAdapterAndroid = nativeImeAdapter; | 
| mTextInputType = textInputType; | 
| mInitialSelectionStart = selectionStart; | 
| @@ -226,7 +265,7 @@ class ImeAdapter { | 
| * keyboard events to WebKit. | 
| * @param nativeImeAdapter The pointer to the native ImeAdapter object. | 
| */ | 
| -    void attach(int nativeImeAdapter) { | 
| +    public void attach(int nativeImeAdapter) { | 
| mNativeImeAdapterAndroid = nativeImeAdapter; | 
| if (nativeImeAdapter != 0) { | 
| nativeAttachImeAdapter(mNativeImeAdapterAndroid); | 
| @@ -237,7 +276,7 @@ class ImeAdapter { | 
| * Used to check whether the native counterpart of the ImeAdapter has been attached yet. | 
| * @return Whether native ImeAdapter has been attached and its pointer is currently nonzero. | 
| */ | 
| -    boolean isNativeImeAdapterAttached() { | 
| +    public boolean isNativeImeAdapterAttached() { | 
| return mNativeImeAdapterAndroid != 0; | 
| } | 
|  | 
| @@ -261,13 +300,7 @@ class ImeAdapter { | 
| } | 
| } | 
|  | 
| -    @CalledByNative | 
| -    void detach() { | 
| -        mNativeImeAdapterAndroid = 0; | 
| -        mTextInputType = 0; | 
| -    } | 
| - | 
| -    boolean hasInputType() { | 
| +    private boolean hasInputType() { | 
| return mTextInputType != sTextInputTypeNone; | 
| } | 
|  | 
| @@ -275,35 +308,41 @@ class ImeAdapter { | 
| return type != sTextInputTypeNone && !InputDialogContainer.isDialogInputType(type); | 
| } | 
|  | 
| -    boolean hasTextInputType() { | 
| +    public boolean hasTextInputType() { | 
| return isTextInputType(mTextInputType); | 
| } | 
|  | 
| -    boolean dispatchKeyEvent(KeyEvent event) { | 
| +    public boolean dispatchKeyEvent(KeyEvent event) { | 
| return translateAndSendNativeEvents(event); | 
| } | 
|  | 
| -    void commitText() { | 
| -        cancelComposition(); | 
| -        if (mNativeImeAdapterAndroid != 0) { | 
| -            nativeCommitText(mNativeImeAdapterAndroid, ""); | 
| -        } | 
| +    private int shouldSendKeyEventWithKeyCode(String text) { | 
| +        if (text.length() != 1) return COMPOSITION_KEY_CODE; | 
| + | 
| +        if (text.equals("\n")) return KeyEvent.KEYCODE_ENTER; | 
| +        else if (text.equals("\t")) return KeyEvent.KEYCODE_TAB; | 
| +        else return COMPOSITION_KEY_CODE; | 
| } | 
|  | 
| -    @SuppressWarnings("unused") | 
| -    @CalledByNative | 
| -    private void cancelComposition() { | 
| -        if (mInputConnection != null) { | 
| -            mInputConnection.restartInput(); | 
| -        } | 
| +    void sendKeyEventWithKeyCode(int keyCode, int flags) { | 
| +        long eventTime = System.currentTimeMillis(); | 
| +        translateAndSendNativeEvents(new KeyEvent(eventTime, eventTime, | 
| +                KeyEvent.ACTION_DOWN, keyCode, 0, 0, | 
| +                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, | 
| +                flags)); | 
| +        translateAndSendNativeEvents(new KeyEvent(System.currentTimeMillis(), eventTime, | 
| +                KeyEvent.ACTION_UP, keyCode, 0, 0, | 
| +                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, | 
| +                flags)); | 
| } | 
|  | 
| +    // Calls from Java to C++ | 
| + | 
| @VisibleForTesting | 
| boolean checkCompositionQueueAndCallNative(String text, int newCursorPosition, | 
| boolean isCommit) { | 
| if (mNativeImeAdapterAndroid == 0) return false; | 
|  | 
| - | 
| // Committing an empty string finishes the current composition. | 
| boolean isFinish = text.isEmpty(); | 
| if (!isFinish) { | 
| @@ -332,27 +371,7 @@ class ImeAdapter { | 
| return true; | 
| } | 
|  | 
| -    private int shouldSendKeyEventWithKeyCode(String text) { | 
| -        if (text.length() != 1) return COMPOSITION_KEY_CODE; | 
| - | 
| -        if (text.equals("\n")) return KeyEvent.KEYCODE_ENTER; | 
| -        else if (text.equals("\t")) return KeyEvent.KEYCODE_TAB; | 
| -        else return COMPOSITION_KEY_CODE; | 
| -    } | 
| - | 
| -    private void sendKeyEventWithKeyCode(int keyCode, int flags) { | 
| -        long eventTime = System.currentTimeMillis(); | 
| -        translateAndSendNativeEvents(new KeyEvent(eventTime, eventTime, | 
| -                KeyEvent.ACTION_DOWN, keyCode, 0, 0, | 
| -                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, | 
| -                flags)); | 
| -        translateAndSendNativeEvents(new KeyEvent(System.currentTimeMillis(), eventTime, | 
| -                KeyEvent.ACTION_UP, keyCode, 0, 0, | 
| -                KeyCharacterMap.VIRTUAL_KEYBOARD, 0, | 
| -                flags)); | 
| -    } | 
| - | 
| -    private boolean translateAndSendNativeEvents(KeyEvent event) { | 
| +    boolean translateAndSendNativeEvents(KeyEvent event) { | 
| if (mNativeImeAdapterAndroid == 0) return false; | 
|  | 
| int action = event.getAction(); | 
| @@ -376,35 +395,7 @@ class ImeAdapter { | 
| event.isSystem(), event.getUnicodeChar()); | 
| } | 
|  | 
| -    private void setInputConnection(AdapterInputConnection inputConnection) { | 
| -        mInputConnection = inputConnection; | 
| -    } | 
| - | 
| -    private static int getModifiers(int metaState) { | 
| -        int modifiers = 0; | 
| -        if ((metaState & KeyEvent.META_SHIFT_ON) != 0) { | 
| -          modifiers |= sModifierShift; | 
| -        } | 
| -        if ((metaState & KeyEvent.META_ALT_ON) != 0) { | 
| -          modifiers |= sModifierAlt; | 
| -        } | 
| -        if ((metaState & KeyEvent.META_CTRL_ON) != 0) { | 
| -          modifiers |= sModifierCtrl; | 
| -        } | 
| -        if ((metaState & KeyEvent.META_CAPS_LOCK_ON) != 0) { | 
| -          modifiers |= sModifierCapsLockOn; | 
| -        } | 
| -        if ((metaState & KeyEvent.META_NUM_LOCK_ON) != 0) { | 
| -          modifiers |= sModifierNumLockOn; | 
| -        } | 
| -        return modifiers; | 
| -    } | 
| - | 
| -    boolean isActive() { | 
| -        return mInputConnection != null && mInputConnection.isActive(); | 
| -    } | 
| - | 
| -    private boolean sendSyntheticKeyEvent( | 
| +    boolean sendSyntheticKeyEvent( | 
| int eventType, long timestampMs, int keyCode, int unicodeChar) { | 
| if (mNativeImeAdapterAndroid == 0) return false; | 
|  | 
| @@ -413,9 +404,8 @@ class ImeAdapter { | 
| return true; | 
| } | 
|  | 
| -    private boolean deleteSurroundingText(int leftLength, int rightLength) { | 
| +    boolean deleteSurroundingText(int leftLength, int rightLength) { | 
| if (mNativeImeAdapterAndroid == 0) return false; | 
| - | 
| nativeDeleteSurroundingText(mNativeImeAdapterAndroid, leftLength, rightLength); | 
| return true; | 
| } | 
| @@ -423,407 +413,131 @@ class ImeAdapter { | 
| @VisibleForTesting | 
| protected boolean setEditableSelectionOffsets(int start, int end) { | 
| if (mNativeImeAdapterAndroid == 0) return false; | 
| - | 
| nativeSetEditableSelectionOffsets(mNativeImeAdapterAndroid, start, end); | 
| return true; | 
| } | 
|  | 
| -    private void batchStateChanged(boolean isBegin) { | 
| +    void batchStateChanged(boolean isBegin) { | 
| if (mNativeImeAdapterAndroid == 0) return; | 
| nativeImeBatchStateChanged(mNativeImeAdapterAndroid, isBegin); | 
| } | 
|  | 
| -    private boolean setComposingRegion(int start, int end) { | 
| -        if (mNativeImeAdapterAndroid == 0) return false; | 
| +    void commitText() { | 
| +        cancelComposition(); | 
| +        if (mNativeImeAdapterAndroid != 0) { | 
| +            nativeCommitText(mNativeImeAdapterAndroid, ""); | 
| +        } | 
| +    } | 
|  | 
| +    /** | 
| +     * Send a request to the native counterpart to set compositing region to given indices. | 
| +     * @param start The start of the composition. | 
| +     * @param end The end of the composition. | 
| +     * @return Whether the native counterpart of ImeAdapter received the call. | 
| +     */ | 
| +    boolean setComposingRegion(int start, int end) { | 
| +        if (mNativeImeAdapterAndroid == 0) return false; | 
| nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end); | 
| return true; | 
| } | 
|  | 
| -    boolean unselect() { | 
| +    /** | 
| +     * Send a request to the native counterpart to unselect text. | 
| +     * @return Whether the native counterpart of ImeAdapter received the call. | 
| +     */ | 
| +    public boolean unselect() { | 
| if (mNativeImeAdapterAndroid == 0) return false; | 
| - | 
| nativeUnselect(mNativeImeAdapterAndroid); | 
| return true; | 
| } | 
|  | 
| -    boolean selectAll() { | 
| +    /** | 
| +     * Send a request to the native counterpart of ImeAdapter to select all the text. | 
| +     * @return Whether the native counterpart of ImeAdapter received the call. | 
| +     */ | 
| +    public boolean selectAll() { | 
| if (mNativeImeAdapterAndroid == 0) return false; | 
| - | 
| nativeSelectAll(mNativeImeAdapterAndroid); | 
| return true; | 
| } | 
|  | 
| -    boolean cut() { | 
| +    /** | 
| +     * Send a request to the native counterpart of ImeAdapter to cut the selected text. | 
| +     * @return Whether the native counterpart of ImeAdapter received the call. | 
| +     */ | 
| +    public boolean cut() { | 
| if (mNativeImeAdapterAndroid == 0) return false; | 
| - | 
| nativeCut(mNativeImeAdapterAndroid); | 
| return true; | 
| } | 
|  | 
| -    boolean copy() { | 
| +    /** | 
| +     * Send a request to the native counterpart of ImeAdapter to copy the selected text. | 
| +     * @return Whether the native counterpart of ImeAdapter received the call. | 
| +     */ | 
| +    public boolean copy() { | 
| if (mNativeImeAdapterAndroid == 0) return false; | 
| - | 
| nativeCopy(mNativeImeAdapterAndroid); | 
| return true; | 
| } | 
|  | 
| -    boolean paste() { | 
| +    /** | 
| +     * Send a request to the native counterpart of ImeAdapter to paste the text from the clipboard. | 
| +     * @return Whether the native counterpart of ImeAdapter received the call. | 
| +     */ | 
| +    public boolean paste() { | 
| if (mNativeImeAdapterAndroid == 0) return false; | 
| - | 
| nativePaste(mNativeImeAdapterAndroid); | 
| return true; | 
| } | 
|  | 
| -    public static class AdapterInputConnectionFactory { | 
| -        public AdapterInputConnection get(View view, ImeAdapter imeAdapter, | 
| -                EditorInfo outAttrs) { | 
| -            return new AdapterInputConnection(view, imeAdapter, outAttrs); | 
| -        } | 
| -    } | 
| - | 
| -    // This InputConnection is created by ContentView.onCreateInputConnection. | 
| -    // It then adapts android's IME to chrome's RenderWidgetHostView using the | 
| -    // native ImeAdapterAndroid via the outer class ImeAdapter. | 
| -    public static class AdapterInputConnection extends BaseInputConnection { | 
| -        private static final String TAG = | 
| -                "org.chromium.content.browser.ImeAdapter$AdapterInputConnection"; | 
| -        private static final boolean DEBUG = false; | 
| -        private final View mInternalView; | 
| -        private final ImeAdapter mImeAdapter; | 
| - | 
| -        private boolean mSingleLine; | 
| -        private int mNumNestedBatchEdits = 0; | 
| -        private boolean mIgnoreTextInputStateUpdates = false; | 
| - | 
| -        private int mLastUpdateSelectionStart = INVALID_SELECTION; | 
| -        private int mLastUpdateSelectionEnd = INVALID_SELECTION; | 
| -        private int mLastUpdateCompositionStart = INVALID_COMPOSITION; | 
| -        private int mLastUpdateCompositionEnd = INVALID_COMPOSITION; | 
| - | 
| -        /** | 
| -         * Updates the AdapterInputConnection's internal representation of the text | 
| -         * being edited and its selection and composition properties. The resulting | 
| -         * Editable is accessible through the getEditable() method. | 
| -         * If the text has not changed, this also calls updateSelection on the InputMethodManager. | 
| -         * @param text The String contents of the field being edited | 
| -         * @param selectionStart The character offset of the selection start, or the caret | 
| -         * position if there is no selection | 
| -         * @param selectionEnd The character offset of the selection end, or the caret | 
| -         * position if there is no selection | 
| -         * @param compositionStart The character offset of the composition start, or -1 | 
| -         * if there is no composition | 
| -         * @param compositionEnd The character offset of the composition end, or -1 | 
| -         * if there is no selection | 
| -         */ | 
| -        public void setEditableText(String text, int selectionStart, int selectionEnd, | 
| -                int compositionStart, int compositionEnd) { | 
| -            if (DEBUG) { | 
| -                Log.w(TAG, "setEditableText [" + text + "] [" + selectionStart + " " + selectionEnd | 
| -                        + "] [" + compositionStart + " " + compositionEnd + "]"); | 
| -            } | 
| -            Editable editable = getEditable(); | 
| - | 
| -            int prevSelectionStart = Selection.getSelectionStart(editable); | 
| -            int prevSelectionEnd = Selection.getSelectionEnd(editable); | 
| -            int prevCompositionStart = getComposingSpanStart(editable); | 
| -            int prevCompositionEnd = getComposingSpanEnd(editable); | 
| -            String prevText = editable.toString(); | 
| - | 
| -            selectionStart = Math.min(selectionStart, text.length()); | 
| -            selectionEnd = Math.min(selectionEnd, text.length()); | 
| -            compositionStart = Math.min(compositionStart, text.length()); | 
| -            compositionEnd = Math.min(compositionEnd, text.length()); | 
| - | 
| -            boolean textUnchanged = prevText.equals(text); | 
| - | 
| -            if (!textUnchanged) { | 
| -                editable.replace(0, editable.length(), text); | 
| -            } | 
| - | 
| -            if (prevSelectionStart == selectionStart && prevSelectionEnd == selectionEnd | 
| -                    && prevCompositionStart == compositionStart | 
| -                    && prevCompositionEnd == compositionEnd) { | 
| -                // Nothing has changed; don't need to do anything | 
| -                return; | 
| -            } | 
| - | 
| -            Selection.setSelection(editable, selectionStart, selectionEnd); | 
| -            super.setComposingRegion(compositionStart, compositionEnd); | 
| - | 
| -            if (mIgnoreTextInputStateUpdates) return; | 
| -            updateSelection(selectionStart, selectionEnd, compositionStart, compositionEnd); | 
| -        } | 
| - | 
| -        @VisibleForTesting | 
| -        protected void updateSelection( | 
| -                int selectionStart, int selectionEnd, | 
| -                int compositionStart, int compositionEnd) { | 
| -            // Avoid sending update if we sent an exact update already previously. | 
| -            if (mLastUpdateSelectionStart == selectionStart && | 
| -                    mLastUpdateSelectionEnd == selectionEnd && | 
| -                    mLastUpdateCompositionStart == compositionStart && | 
| -                    mLastUpdateCompositionEnd == compositionEnd) { | 
| -                return; | 
| -            } | 
| -            if (DEBUG) { | 
| -                Log.w(TAG, "updateSelection [" + selectionStart + " " + selectionEnd + "] [" | 
| -                        + compositionStart + " " + compositionEnd + "]"); | 
| -            } | 
| -            // updateSelection should be called every time the selection or composition changes | 
| -            // if it happens not within a batch edit, or at the end of each top level batch edit. | 
| -            getInputMethodManagerWrapper().updateSelection(mInternalView, | 
| -                    selectionStart, selectionEnd, compositionStart, compositionEnd); | 
| -            mLastUpdateSelectionStart = selectionStart; | 
| -            mLastUpdateSelectionEnd = selectionEnd; | 
| -            mLastUpdateCompositionStart = compositionStart; | 
| -            mLastUpdateCompositionEnd = compositionEnd; | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean setComposingText(CharSequence text, int newCursorPosition) { | 
| -            if (DEBUG) Log.w(TAG, "setComposingText [" + text + "] [" + newCursorPosition + "]"); | 
| -            super.setComposingText(text, newCursorPosition); | 
| -            return mImeAdapter.checkCompositionQueueAndCallNative(text.toString(), | 
| -                    newCursorPosition, false); | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean commitText(CharSequence text, int newCursorPosition) { | 
| -            if (DEBUG) Log.w(TAG, "commitText [" + text + "] [" + newCursorPosition + "]"); | 
| -            super.commitText(text, newCursorPosition); | 
| -            return mImeAdapter.checkCompositionQueueAndCallNative(text.toString(), | 
| -                    newCursorPosition, text.length() > 0); | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean performEditorAction(int actionCode) { | 
| -            if (DEBUG) Log.w(TAG, "performEditorAction [" + actionCode + "]"); | 
| -            if (actionCode == EditorInfo.IME_ACTION_NEXT) { | 
| -                restartInput(); | 
| -                // Send TAB key event | 
| -                long timeStampMs = System.currentTimeMillis(); | 
| -                mImeAdapter.sendSyntheticKeyEvent( | 
| -                        sEventTypeRawKeyDown, timeStampMs, KeyEvent.KEYCODE_TAB, 0); | 
| -            } else { | 
| -                mImeAdapter.sendKeyEventWithKeyCode(KeyEvent.KEYCODE_ENTER, | 
| -                        KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE | 
| -                        | KeyEvent.FLAG_EDITOR_ACTION); | 
| -            } | 
| -            return true; | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean performContextMenuAction(int id) { | 
| -            if (DEBUG) Log.w(TAG, "performContextMenuAction [" + id + "]"); | 
| -            switch (id) { | 
| -                case android.R.id.selectAll: | 
| -                    return mImeAdapter.selectAll(); | 
| -                case android.R.id.cut: | 
| -                    return mImeAdapter.cut(); | 
| -                case android.R.id.copy: | 
| -                    return mImeAdapter.copy(); | 
| -                case android.R.id.paste: | 
| -                    return mImeAdapter.paste(); | 
| -                default: | 
| -                    return false; | 
| -            } | 
| -        } | 
| - | 
| -        @Override | 
| -        public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { | 
| -            if (DEBUG) Log.w(TAG, "getExtractedText"); | 
| -            ExtractedText et = new ExtractedText(); | 
| -            Editable editable = getEditable(); | 
| -            et.text = editable.toString(); | 
| -            et.partialEndOffset = editable.length(); | 
| -            et.selectionStart = Selection.getSelectionStart(editable); | 
| -            et.selectionEnd = Selection.getSelectionEnd(editable); | 
| -            et.flags = mSingleLine ? ExtractedText.FLAG_SINGLE_LINE : 0; | 
| -            return et; | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean beginBatchEdit() { | 
| -            if (DEBUG) Log.w(TAG, "beginBatchEdit [" + (mNumNestedBatchEdits == 0) + "]"); | 
| -            if (mNumNestedBatchEdits == 0) mImeAdapter.batchStateChanged(true); | 
| - | 
| -            mNumNestedBatchEdits++; | 
| -            return false; | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean endBatchEdit() { | 
| -            if (mNumNestedBatchEdits == 0) return false; | 
| - | 
| -            --mNumNestedBatchEdits; | 
| -            if (DEBUG) Log.w(TAG, "endBatchEdit [" + (mNumNestedBatchEdits == 0) + "]"); | 
| -            if (mNumNestedBatchEdits == 0) mImeAdapter.batchStateChanged(false); | 
| -            return false; | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean deleteSurroundingText(int leftLength, int rightLength) { | 
| -            if (DEBUG) { | 
| -                Log.w(TAG, "deleteSurroundingText [" + leftLength + " " + rightLength + "]"); | 
| -            } | 
| -            if (!super.deleteSurroundingText(leftLength, rightLength)) { | 
| -                return false; | 
| -            } | 
| -            return mImeAdapter.deleteSurroundingText(leftLength, rightLength); | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean sendKeyEvent(KeyEvent event) { | 
| -            if (DEBUG) Log.w(TAG, "sendKeyEvent [" + event.getAction() + "]"); | 
| -            mImeAdapter.mSelectionHandleController.hideAndDisallowAutomaticShowing(); | 
| -            mImeAdapter.mInsertionHandleController.hideAndDisallowAutomaticShowing(); | 
| - | 
| -            // If this is a key-up, and backspace/del or if the key has a character representation, | 
| -            // need to update the underlying Editable (i.e. the local representation of the text | 
| -            // being edited). | 
| -            if (event.getAction() == KeyEvent.ACTION_UP) { | 
| -                if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { | 
| -                    super.deleteSurroundingText(1, 0); | 
| -                } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { | 
| -                    super.deleteSurroundingText(0, 1); | 
| -                } else { | 
| -                    int unicodeChar = event.getUnicodeChar(); | 
| -                    if (unicodeChar != 0) { | 
| -                        Editable editable = getEditable(); | 
| -                        int selectionStart = Selection.getSelectionStart(editable); | 
| -                        int selectionEnd = Selection.getSelectionEnd(editable); | 
| -                        if (selectionStart > selectionEnd) { | 
| -                            int temp = selectionStart; | 
| -                            selectionStart = selectionEnd; | 
| -                            selectionEnd = temp; | 
| -                        } | 
| -                        editable.replace(selectionStart, selectionEnd, | 
| -                                Character.toString((char)unicodeChar)); | 
| -                    } | 
| -                } | 
| -            } | 
| -            mImeAdapter.translateAndSendNativeEvents(event); | 
| -            return true; | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean finishComposingText() { | 
| -            if (DEBUG) Log.w(TAG, "finishComposingText"); | 
| -            Editable editable = getEditable(); | 
| -            if (getComposingSpanStart(editable) == getComposingSpanEnd(editable)) { | 
| -                return true; | 
| -            } | 
| -            super.finishComposingText(); | 
| -            return mImeAdapter.checkCompositionQueueAndCallNative("", 0, true); | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean setSelection(int start, int end) { | 
| -            if (DEBUG) Log.w(TAG, "setSelection"); | 
| -            if (start < 0 || end < 0) return true; | 
| -            super.setSelection(start, end); | 
| -            return mImeAdapter.setEditableSelectionOffsets(start, end); | 
| -        } | 
| - | 
| -        /** | 
| -         * Informs the InputMethodManager and InputMethodSession (i.e. the IME) that the text | 
| -         * state is no longer what the IME has and that it needs to be updated. | 
| -         */ | 
| -        void restartInput() { | 
| -            if (DEBUG) Log.w(TAG, "restartInput"); | 
| -            getInputMethodManagerWrapper().restartInput(mInternalView); | 
| -            mIgnoreTextInputStateUpdates = false; | 
| -            mNumNestedBatchEdits = 0; | 
| -        } | 
| - | 
| -        @Override | 
| -        public boolean setComposingRegion(int start, int end) { | 
| -            if (DEBUG) Log.w(TAG, "setComposingRegion [" + start + " " + end + "]"); | 
| -            int a = Math.min(start, end); | 
| -            int b = Math.max(start, end); | 
| -            super.setComposingRegion(a, b); | 
| -            return mImeAdapter.setComposingRegion(a, b); | 
| -        } | 
| - | 
| -        boolean isActive() { | 
| -            return getInputMethodManagerWrapper().isActive(mInternalView); | 
| -        } | 
| - | 
| -        void setIgnoreTextInputStateUpdates(boolean shouldIgnore) { | 
| -            mIgnoreTextInputStateUpdates = shouldIgnore; | 
| -            if (shouldIgnore) return; | 
| +    // Calls from C++ to Java | 
|  | 
| -            Editable editable = getEditable(); | 
| -            updateSelection(Selection.getSelectionStart(editable), | 
| -                    Selection.getSelectionEnd(editable), | 
| -                    getComposingSpanStart(editable), | 
| -                    getComposingSpanEnd(editable)); | 
| -        } | 
| +    @CalledByNative | 
| +    private static void initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp, | 
| +            int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl, | 
| +            int modifierCapsLockOn, int modifierNumLockOn) { | 
| +        sEventTypeRawKeyDown = eventTypeRawKeyDown; | 
| +        sEventTypeKeyUp = eventTypeKeyUp; | 
| +        sEventTypeChar = eventTypeChar; | 
| +        sModifierShift = modifierShift; | 
| +        sModifierAlt = modifierAlt; | 
| +        sModifierCtrl = modifierCtrl; | 
| +        sModifierCapsLockOn = modifierCapsLockOn; | 
| +        sModifierNumLockOn = modifierNumLockOn; | 
| +    } | 
|  | 
| -        @VisibleForTesting | 
| -        protected boolean isIgnoringTextInputStateUpdates() { | 
| -            return mIgnoreTextInputStateUpdates; | 
| -        } | 
| +    @CalledByNative | 
| +    private static void initializeTextInputTypes(int textInputTypeNone, int textInputTypeText, | 
| +            int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch, | 
| +            int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel, | 
| +            int textInputTypeNumber, int textInputTypeDate, int textInputTypeDateTime, | 
| +            int textInputTypeDateTimeLocal, int textInputTypeMonth, int textInputTypeTime, | 
| +            int textInputTypeWeek, int textInputTypeContentEditable) { | 
| +        sTextInputTypeNone = textInputTypeNone; | 
| +        sTextInputTypeText = textInputTypeText; | 
| +        sTextInputTypeTextArea = textInputTypeTextArea; | 
| +        sTextInputTypePassword = textInputTypePassword; | 
| +        sTextInputTypeSearch = textInputTypeSearch; | 
| +        sTextInputTypeUrl = textInputTypeUrl; | 
| +        sTextInputTypeEmail = textInputTypeEmail; | 
| +        sTextInputTypeTel = textInputTypeTel; | 
| +        sTextInputTypeNumber = textInputTypeNumber; | 
| +        sTextInputTypeWeek = textInputTypeWeek; | 
| +        sTextInputTypeContentEditable = textInputTypeContentEditable; | 
| +    } | 
|  | 
| -        private InputMethodManagerWrapper getInputMethodManagerWrapper() { | 
| -            return mImeAdapter.getInputMethodManagerWrapper(); | 
| +    @CalledByNative | 
| +    private void cancelComposition() { | 
| +        if (mInputConnection != null) { | 
| +            mInputConnection.restartInput(); | 
| } | 
| +    } | 
|  | 
| -        @VisibleForTesting | 
| -        protected AdapterInputConnection(View view, ImeAdapter imeAdapter, EditorInfo outAttrs) { | 
| -            super(view, true); | 
| -            mInternalView = view; | 
| -            mImeAdapter = imeAdapter; | 
| -            mImeAdapter.setInputConnection(this); | 
| -            mSingleLine = true; | 
| -            outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; | 
| -            outAttrs.inputType = EditorInfo.TYPE_CLASS_TEXT | 
| -                    | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT; | 
| -            if (imeAdapter.mTextInputType == ImeAdapter.sTextInputTypeText) { | 
| -                // Normal text field | 
| -                outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; | 
| -            } else if (imeAdapter.mTextInputType == ImeAdapter.sTextInputTypeTextArea || | 
| -                    imeAdapter.mTextInputType == ImeAdapter.sTextInputTypeContentEditable) { | 
| -                // TextArea or contenteditable. | 
| -                outAttrs.inputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | 
| -                        | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | 
| -                        | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT; | 
| -                outAttrs.imeOptions |= EditorInfo.IME_ACTION_NONE; | 
| -                mSingleLine = false; | 
| -            } else if (imeAdapter.mTextInputType == ImeAdapter.sTextInputTypePassword) { | 
| -                // Password | 
| -                outAttrs.inputType = InputType.TYPE_CLASS_TEXT | 
| -                        | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD; | 
| -                outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; | 
| -            } else if (imeAdapter.mTextInputType == ImeAdapter.sTextInputTypeSearch) { | 
| -                // Search | 
| -                outAttrs.imeOptions |= EditorInfo.IME_ACTION_SEARCH; | 
| -            } else if (imeAdapter.mTextInputType == ImeAdapter.sTextInputTypeUrl) { | 
| -                // Url | 
| -                // TYPE_TEXT_VARIATION_URI prevents Tab key from showing, so | 
| -                // exclude it for now. | 
| -                outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; | 
| -            } else if (imeAdapter.mTextInputType == ImeAdapter.sTextInputTypeEmail) { | 
| -                // Email | 
| -                outAttrs.inputType = InputType.TYPE_CLASS_TEXT | 
| -                        | InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; | 
| -                outAttrs.imeOptions |= EditorInfo.IME_ACTION_GO; | 
| -            } else if (imeAdapter.mTextInputType == ImeAdapter.sTextInputTypeTel) { | 
| -                // Telephone | 
| -                // Number and telephone do not have both a Tab key and an | 
| -                // action in default OSK, so set the action to NEXT | 
| -                outAttrs.inputType = InputType.TYPE_CLASS_PHONE; | 
| -                outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; | 
| -            } else if (imeAdapter.mTextInputType == ImeAdapter.sTextInputTypeNumber) { | 
| -                // Number | 
| -                outAttrs.inputType = InputType.TYPE_CLASS_NUMBER | 
| -                        | InputType.TYPE_NUMBER_VARIATION_NORMAL; | 
| -                outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; | 
| -            } | 
| -            outAttrs.initialSelStart = imeAdapter.mInitialSelectionStart; | 
| -            outAttrs.initialSelEnd = imeAdapter.mInitialSelectionStart; | 
| -        } | 
| +    @CalledByNative | 
| +    void detach() { | 
| +        mNativeImeAdapterAndroid = 0; | 
| +        mTextInputType = 0; | 
| } | 
|  | 
| private native boolean nativeSendSyntheticKeyEvent(int nativeImeAdapterAndroid, | 
|  |