Home | History | Annotate | Download | only in input
      1 // Copyright 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.content.browser.input;
      6 
      7 import android.os.Handler;
      8 import android.os.ResultReceiver;
      9 import android.os.SystemClock;
     10 import android.text.Editable;
     11 import android.text.SpannableString;
     12 import android.text.style.BackgroundColorSpan;
     13 import android.text.style.CharacterStyle;
     14 import android.text.style.UnderlineSpan;
     15 import android.view.KeyCharacterMap;
     16 import android.view.KeyEvent;
     17 import android.view.View;
     18 import android.view.inputmethod.EditorInfo;
     19 
     20 import org.chromium.base.CalledByNative;
     21 import org.chromium.base.JNINamespace;
     22 import org.chromium.base.VisibleForTesting;
     23 import org.chromium.ui.picker.InputDialogContainer;
     24 
     25 import java.lang.CharSequence;
     26 
     27 /**
     28  * Adapts and plumbs android IME service onto the chrome text input API.
     29  * ImeAdapter provides an interface in both ways native <-> java:
     30  * 1. InputConnectionAdapter notifies native code of text composition state and
     31  *    dispatch key events from java -> WebKit.
     32  * 2. Native ImeAdapter notifies java side to clear composition text.
     33  *
     34  * The basic flow is:
     35  * 1. When InputConnectionAdapter gets called with composition or result text:
     36  *    If we receive a composition text or a result text, then we just need to
     37  *    dispatch a synthetic key event with special keycode 229, and then dispatch
     38  *    the composition or result text.
     39  * 2. Intercept dispatchKeyEvent() method for key events not handled by IME, we
     40  *   need to dispatch them to webkit and check webkit's reply. Then inject a
     41  *   new key event for further processing if webkit didn't handle it.
     42  *
     43  * Note that the native peer object does not take any strong reference onto the
     44  * instance of this java object, hence it is up to the client of this class (e.g.
     45  * the ViewEmbedder implementor) to hold a strong reference to it for the required
     46  * lifetime of the object.
     47  */
     48 @JNINamespace("content")
     49 public class ImeAdapter {
     50 
     51     /**
     52      * Interface for the delegate that needs to be notified of IME changes.
     53      */
     54     public interface ImeAdapterDelegate {
     55         /**
     56          * Called to notify the delegate about synthetic/real key events before sending to renderer.
     57          */
     58         void onImeEvent();
     59 
     60         /**
     61          * Called when a request to hide the keyboard is sent to InputMethodManager.
     62          */
     63         void onDismissInput();
     64 
     65         /**
     66          * @return View that the keyboard should be attached to.
     67          */
     68         View getAttachedView();
     69 
     70         /**
     71          * @return Object that should be called for all keyboard show and hide requests.
     72          */
     73         ResultReceiver getNewShowKeyboardReceiver();
     74     }
     75 
     76     private class DelayedDismissInput implements Runnable {
     77         private long mNativeImeAdapter;
     78 
     79         DelayedDismissInput(long nativeImeAdapter) {
     80             mNativeImeAdapter = nativeImeAdapter;
     81         }
     82 
     83         // http://crbug.com/413744
     84         void detach() {
     85             mNativeImeAdapter = 0;
     86         }
     87 
     88         @Override
     89         public void run() {
     90             if (mNativeImeAdapter != 0) {
     91                 attach(mNativeImeAdapter, sTextInputTypeNone, sTextInputFlagNone);
     92             }
     93             dismissInput(true);
     94         }
     95     }
     96 
     97     private static final int COMPOSITION_KEY_CODE = 229;
     98 
     99     // Delay introduced to avoid hiding the keyboard if new show requests are received.
    100     // The time required by the unfocus-focus events triggered by tab has been measured in soju:
    101     // Mean: 18.633 ms, Standard deviation: 7.9837 ms.
    102     // The value here should be higher enough to cover these cases, but not too high to avoid
    103     // letting the user perceiving important delays.
    104     private static final int INPUT_DISMISS_DELAY = 150;
    105 
    106     // All the constants that are retrieved from the C++ code.
    107     // They get set through initializeWebInputEvents and initializeTextInputTypes calls.
    108     static int sEventTypeRawKeyDown;
    109     static int sEventTypeKeyUp;
    110     static int sEventTypeChar;
    111     static int sTextInputTypeNone;
    112     static int sTextInputTypeText;
    113     static int sTextInputTypeTextArea;
    114     static int sTextInputTypePassword;
    115     static int sTextInputTypeSearch;
    116     static int sTextInputTypeUrl;
    117     static int sTextInputTypeEmail;
    118     static int sTextInputTypeTel;
    119     static int sTextInputTypeNumber;
    120     static int sTextInputTypeContentEditable;
    121     static int sTextInputFlagNone = 0;
    122     static int sTextInputFlagAutocompleteOn;
    123     static int sTextInputFlagAutocompleteOff;
    124     static int sTextInputFlagAutocorrectOn;
    125     static int sTextInputFlagAutocorrectOff;
    126     static int sTextInputFlagSpellcheckOn;
    127     static int sTextInputFlagSpellcheckOff;
    128     static int sModifierShift;
    129     static int sModifierAlt;
    130     static int sModifierCtrl;
    131     static int sModifierCapsLockOn;
    132     static int sModifierNumLockOn;
    133     static char[] sSingleCharArray = new char[1];
    134     static KeyCharacterMap sKeyCharacterMap;
    135 
    136     private long mNativeImeAdapterAndroid;
    137     private InputMethodManagerWrapper mInputMethodManagerWrapper;
    138     private AdapterInputConnection mInputConnection;
    139     private final ImeAdapterDelegate mViewEmbedder;
    140     private final Handler mHandler;
    141     private DelayedDismissInput mDismissInput = null;
    142     private int mTextInputType;
    143     private int mTextInputFlags;
    144     private String mLastComposeText;
    145 
    146     @VisibleForTesting
    147     int mLastSyntheticKeyCode;
    148 
    149     @VisibleForTesting
    150     boolean mIsShowWithoutHideOutstanding = false;
    151 
    152     /**
    153      * @param wrapper InputMethodManagerWrapper that should receive all the call directed to
    154      *                InputMethodManager.
    155      * @param embedder The view that is used for callbacks from ImeAdapter.
    156      */
    157     public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder) {
    158         mInputMethodManagerWrapper = wrapper;
    159         mViewEmbedder = embedder;
    160         mHandler = new Handler();
    161     }
    162 
    163     /**
    164      * Default factory for AdapterInputConnection classes.
    165      */
    166     public static class AdapterInputConnectionFactory {
    167         public AdapterInputConnection get(View view, ImeAdapter imeAdapter,
    168                 Editable editable, EditorInfo outAttrs) {
    169             return new AdapterInputConnection(view, imeAdapter, editable, outAttrs);
    170         }
    171     }
    172 
    173     /**
    174      * Overrides the InputMethodManagerWrapper that ImeAdapter uses to make calls to
    175      * InputMethodManager.
    176      * @param immw InputMethodManagerWrapper that should be used to call InputMethodManager.
    177      */
    178     @VisibleForTesting
    179     public void setInputMethodManagerWrapper(InputMethodManagerWrapper immw) {
    180         mInputMethodManagerWrapper = immw;
    181     }
    182 
    183     /**
    184      * Should be only used by AdapterInputConnection.
    185      * @return InputMethodManagerWrapper that should receive all the calls directed to
    186      *         InputMethodManager.
    187      */
    188     InputMethodManagerWrapper getInputMethodManagerWrapper() {
    189         return mInputMethodManagerWrapper;
    190     }
    191 
    192     /**
    193      * Set the current active InputConnection when a new InputConnection is constructed.
    194      * @param inputConnection The input connection that is currently used with IME.
    195      */
    196     void setInputConnection(AdapterInputConnection inputConnection) {
    197         mInputConnection = inputConnection;
    198         mLastComposeText = null;
    199     }
    200 
    201     /**
    202      * Should be used only by AdapterInputConnection.
    203      * @return The input type of currently focused element.
    204      */
    205     int getTextInputType() {
    206         return mTextInputType;
    207     }
    208 
    209     /**
    210      * Should be used only by AdapterInputConnection.
    211      * @return The input flags of the currently focused element.
    212      */
    213     int getTextInputFlags() {
    214         return mTextInputFlags;
    215     }
    216 
    217     /**
    218      * @return Constant representing that a focused node is not an input field.
    219      */
    220     public static int getTextInputTypeNone() {
    221         return sTextInputTypeNone;
    222     }
    223 
    224     private static int getModifiers(int metaState) {
    225         int modifiers = 0;
    226         if ((metaState & KeyEvent.META_SHIFT_ON) != 0) {
    227             modifiers |= sModifierShift;
    228         }
    229         if ((metaState & KeyEvent.META_ALT_ON) != 0) {
    230             modifiers |= sModifierAlt;
    231         }
    232         if ((metaState & KeyEvent.META_CTRL_ON) != 0) {
    233             modifiers |= sModifierCtrl;
    234         }
    235         if ((metaState & KeyEvent.META_CAPS_LOCK_ON) != 0) {
    236             modifiers |= sModifierCapsLockOn;
    237         }
    238         if ((metaState & KeyEvent.META_NUM_LOCK_ON) != 0) {
    239             modifiers |= sModifierNumLockOn;
    240         }
    241         return modifiers;
    242     }
    243 
    244     /**
    245      * Shows or hides the keyboard based on passed parameters.
    246      * @param nativeImeAdapter Pointer to the ImeAdapterAndroid object that is sending the update.
    247      * @param textInputType Text input type for the currently focused field in renderer.
    248      * @param showIfNeeded Whether the keyboard should be shown if it is currently hidden.
    249      */
    250     public void updateKeyboardVisibility(long nativeImeAdapter, int textInputType,
    251             int textInputFlags, boolean showIfNeeded) {
    252         mHandler.removeCallbacks(mDismissInput);
    253 
    254         // If current input type is none and showIfNeeded is false, IME should not be shown
    255         // and input type should remain as none.
    256         if (mTextInputType == sTextInputTypeNone && !showIfNeeded) {
    257             return;
    258         }
    259 
    260         if (mNativeImeAdapterAndroid != nativeImeAdapter || mTextInputType != textInputType) {
    261             // Set a delayed task to perform unfocus. This avoids hiding the keyboard when tabbing
    262             // through text inputs or when JS rapidly changes focus to another text element.
    263             if (textInputType == sTextInputTypeNone) {
    264                 mDismissInput = new DelayedDismissInput(nativeImeAdapter);
    265                 mHandler.postDelayed(mDismissInput, INPUT_DISMISS_DELAY);
    266                 return;
    267             }
    268 
    269             attach(nativeImeAdapter, textInputType, textInputFlags);
    270 
    271             mInputMethodManagerWrapper.restartInput(mViewEmbedder.getAttachedView());
    272             if (showIfNeeded) {
    273                 showKeyboard();
    274             }
    275         } else if (hasInputType() && showIfNeeded) {
    276             showKeyboard();
    277         }
    278     }
    279 
    280     public void attach(long nativeImeAdapter, int textInputType, int textInputFlags) {
    281         if (mNativeImeAdapterAndroid != 0) {
    282             nativeResetImeAdapter(mNativeImeAdapterAndroid);
    283         }
    284         mNativeImeAdapterAndroid = nativeImeAdapter;
    285         mTextInputType = textInputType;
    286         mTextInputFlags = textInputFlags;
    287         mLastComposeText = null;
    288         if (nativeImeAdapter != 0) {
    289             nativeAttachImeAdapter(mNativeImeAdapterAndroid);
    290         }
    291         if (mTextInputType == sTextInputTypeNone) {
    292             dismissInput(false);
    293         }
    294     }
    295 
    296     /**
    297      * Attaches the imeAdapter to its native counterpart. This is needed to start forwarding
    298      * keyboard events to WebKit.
    299      * @param nativeImeAdapter The pointer to the native ImeAdapter object.
    300      */
    301     public void attach(long nativeImeAdapter) {
    302         attach(nativeImeAdapter, sTextInputTypeNone, sTextInputFlagNone);
    303     }
    304 
    305     private void showKeyboard() {
    306         mIsShowWithoutHideOutstanding = true;
    307         mInputMethodManagerWrapper.showSoftInput(mViewEmbedder.getAttachedView(), 0,
    308                 mViewEmbedder.getNewShowKeyboardReceiver());
    309     }
    310 
    311     private void dismissInput(boolean unzoomIfNeeded) {
    312         mIsShowWithoutHideOutstanding  = false;
    313         View view = mViewEmbedder.getAttachedView();
    314         if (mInputMethodManagerWrapper.isActive(view)) {
    315             mInputMethodManagerWrapper.hideSoftInputFromWindow(view.getWindowToken(), 0,
    316                     unzoomIfNeeded ? mViewEmbedder.getNewShowKeyboardReceiver() : null);
    317         }
    318         mViewEmbedder.onDismissInput();
    319     }
    320 
    321     private boolean hasInputType() {
    322         return mTextInputType != sTextInputTypeNone;
    323     }
    324 
    325     private static boolean isTextInputType(int type) {
    326         return type != sTextInputTypeNone && !InputDialogContainer.isDialogInputType(type);
    327     }
    328 
    329     public boolean hasTextInputType() {
    330         return isTextInputType(mTextInputType);
    331     }
    332 
    333     /**
    334      * @return true if the selected text is of password.
    335      */
    336     public boolean isSelectionPassword() {
    337         return mTextInputType == sTextInputTypePassword;
    338     }
    339 
    340     public boolean dispatchKeyEvent(KeyEvent event) {
    341         return translateAndSendNativeEvents(event);
    342     }
    343 
    344     private int shouldSendKeyEventWithKeyCode(String text) {
    345         if (text.length() != 1) return COMPOSITION_KEY_CODE;
    346 
    347         if (text.equals("\n")) return KeyEvent.KEYCODE_ENTER;
    348         else if (text.equals("\t")) return KeyEvent.KEYCODE_TAB;
    349         else return COMPOSITION_KEY_CODE;
    350     }
    351 
    352     /**
    353      * @return Android KeyEvent for a single unicode character.  Only one KeyEvent is returned
    354      * even if the system determined that various modifier keys (like Shift) would also have
    355      * been pressed.
    356      */
    357     private static KeyEvent androidKeyEventForCharacter(char chr) {
    358         if (sKeyCharacterMap == null) {
    359             sKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
    360         }
    361         sSingleCharArray[0] = chr;
    362         // TODO: Evaluate cost of this system call.
    363         KeyEvent[] events = sKeyCharacterMap.getEvents(sSingleCharArray);
    364         if (events == null) {  // No known key sequence will create that character.
    365             return null;
    366         }
    367 
    368         for (int i = 0; i < events.length; ++i) {
    369             if (events[i].getAction() == KeyEvent.ACTION_DOWN &&
    370                     !KeyEvent.isModifierKey(events[i].getKeyCode())) {
    371                 return events[i];
    372             }
    373         }
    374 
    375         return null;  // No printing characters were found.
    376     }
    377 
    378     @VisibleForTesting
    379     public static KeyEvent getTypedKeyEventGuess(String oldtext, String newtext) {
    380         // Starting typing a new composition should add only a single character.  Any composition
    381         // beginning with text longer than that must come from something other than typing so
    382         // return 0.
    383         if (oldtext == null) {
    384             if (newtext.length() == 1) {
    385                 return androidKeyEventForCharacter(newtext.charAt(0));
    386             } else {
    387                 return null;
    388             }
    389         }
    390 
    391         // The content has grown in length: assume the last character is the key that caused it.
    392         if (newtext.length() > oldtext.length() && newtext.startsWith(oldtext))
    393             return androidKeyEventForCharacter(newtext.charAt(newtext.length() - 1));
    394 
    395         // The content has shrunk in length: assume that backspace was pressed.
    396         if (oldtext.length() > newtext.length() && oldtext.startsWith(newtext))
    397             return new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
    398 
    399         // The content is unchanged or has undergone a complex change (i.e. not a simple tail
    400         // modification) so return an unknown key-code.
    401         return null;
    402     }
    403 
    404     void sendKeyEventWithKeyCode(int keyCode, int flags) {
    405         long eventTime = SystemClock.uptimeMillis();
    406         mLastSyntheticKeyCode = keyCode;
    407         translateAndSendNativeEvents(new KeyEvent(eventTime, eventTime,
    408                 KeyEvent.ACTION_DOWN, keyCode, 0, 0,
    409                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
    410                 flags));
    411         translateAndSendNativeEvents(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
    412                 KeyEvent.ACTION_UP, keyCode, 0, 0,
    413                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
    414                 flags));
    415     }
    416 
    417     // Calls from Java to C++
    418     // TODO: Add performance tracing to more complicated functions.
    419 
    420     boolean checkCompositionQueueAndCallNative(CharSequence text, int newCursorPosition,
    421             boolean isCommit) {
    422         if (mNativeImeAdapterAndroid == 0) return false;
    423         mViewEmbedder.onImeEvent();
    424 
    425         String textStr = text.toString();
    426         int keyCode = shouldSendKeyEventWithKeyCode(textStr);
    427         long timeStampMs = SystemClock.uptimeMillis();
    428 
    429         if (keyCode != COMPOSITION_KEY_CODE) {
    430             sendKeyEventWithKeyCode(keyCode,
    431                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
    432         } else {
    433             KeyEvent keyEvent = getTypedKeyEventGuess(mLastComposeText, textStr);
    434             int modifiers = 0;
    435             if (keyEvent != null) {
    436                 keyCode = keyEvent.getKeyCode();
    437                 modifiers = getModifiers(keyEvent.getMetaState());
    438             } else if (!textStr.equals(mLastComposeText)) {
    439                 keyCode = KeyEvent.KEYCODE_UNKNOWN;
    440             } else {
    441                 keyCode = -1;
    442             }
    443 
    444             // If this is a commit with no previous composition, then treat it as a native
    445             // KeyDown/KeyUp pair with no composition rather than a synthetic pair with
    446             // composition below.
    447             if (keyCode > 0 && isCommit && mLastComposeText == null) {
    448                 mLastSyntheticKeyCode = keyCode;
    449                 return translateAndSendNativeEvents(keyEvent) &&
    450                        translateAndSendNativeEvents(KeyEvent.changeAction(
    451                                keyEvent, KeyEvent.ACTION_UP));
    452             }
    453 
    454             // When typing, there is no issue sending KeyDown and KeyUp events around the
    455             // composition event because those key events do nothing (other than call JS
    456             // handlers).  Typing does not cause changes outside of a KeyPress event which
    457             // we don't call here.  However, if the key-code is a control key such as
    458             // KEYCODE_DEL then there never is an associated KeyPress event and the KeyDown
    459             // event itself causes the action.  The net result below is that the Renderer calls
    460             // cancelComposition() and then Android starts anew with setComposingRegion().
    461             // This stopping and restarting of composition could be a source of problems
    462             // with 3rd party keyboards.
    463             //
    464             // An alternative is to *not* call nativeSetComposingText() in the non-commit case
    465             // below.  This avoids the restart of composition described above but fails to send
    466             // an update to the composition while in composition which, strictly speaking,
    467             // does not match the spec.
    468             //
    469             // For now, the solution is to endure the restarting of composition and only dive
    470             // into the alternate solution should there be problems in the field.  --bcwhite
    471 
    472             if (keyCode >= 0) {
    473                 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawKeyDown,
    474                         timeStampMs, keyCode, modifiers, 0);
    475             }
    476 
    477             if (isCommit) {
    478                 nativeCommitText(mNativeImeAdapterAndroid, textStr);
    479                 textStr = null;
    480             } else {
    481                 nativeSetComposingText(mNativeImeAdapterAndroid, text, textStr, newCursorPosition);
    482             }
    483 
    484             if (keyCode >= 0) {
    485                 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyUp,
    486                         timeStampMs, keyCode, modifiers, 0);
    487             }
    488 
    489             mLastSyntheticKeyCode = keyCode;
    490         }
    491 
    492         mLastComposeText = textStr;
    493         return true;
    494     }
    495 
    496     void finishComposingText() {
    497         mLastComposeText = null;
    498         if (mNativeImeAdapterAndroid == 0) return;
    499         nativeFinishComposingText(mNativeImeAdapterAndroid);
    500     }
    501 
    502     boolean translateAndSendNativeEvents(KeyEvent event) {
    503         if (mNativeImeAdapterAndroid == 0) return false;
    504 
    505         int action = event.getAction();
    506         if (action != KeyEvent.ACTION_DOWN &&
    507             action != KeyEvent.ACTION_UP) {
    508             // action == KeyEvent.ACTION_MULTIPLE
    509             // TODO(bulach): confirm the actual behavior. Apparently:
    510             // If event.getKeyCode() == KEYCODE_UNKNOWN, we can send a
    511             // composition key down (229) followed by a commit text with the
    512             // string from event.getUnicodeChars().
    513             // Otherwise, we'd need to send an event with a
    514             // WebInputEvent::IsAutoRepeat modifier. We also need to verify when
    515             // we receive ACTION_MULTIPLE: we may receive it after an ACTION_DOWN,
    516             // and if that's the case, we'll need to review when to send the Char
    517             // event.
    518             return false;
    519         }
    520         mViewEmbedder.onImeEvent();
    521         return nativeSendKeyEvent(mNativeImeAdapterAndroid, event, event.getAction(),
    522                 getModifiers(event.getMetaState()), event.getEventTime(), event.getKeyCode(),
    523                              /*isSystemKey=*/false, event.getUnicodeChar());
    524     }
    525 
    526     boolean sendSyntheticKeyEvent(int eventType, long timestampMs, int keyCode, int modifiers,
    527             int unicodeChar) {
    528         if (mNativeImeAdapterAndroid == 0) return false;
    529 
    530         nativeSendSyntheticKeyEvent(
    531                 mNativeImeAdapterAndroid, eventType, timestampMs, keyCode, modifiers, unicodeChar);
    532         return true;
    533     }
    534 
    535     /**
    536      * Send a request to the native counterpart to delete a given range of characters.
    537      * @param beforeLength Number of characters to extend the selection by before the existing
    538      *                     selection.
    539      * @param afterLength Number of characters to extend the selection by after the existing
    540      *                    selection.
    541      * @return Whether the native counterpart of ImeAdapter received the call.
    542      */
    543     boolean deleteSurroundingText(int beforeLength, int afterLength) {
    544         mViewEmbedder.onImeEvent();
    545         if (mNativeImeAdapterAndroid == 0) return false;
    546         nativeDeleteSurroundingText(mNativeImeAdapterAndroid, beforeLength, afterLength);
    547         return true;
    548     }
    549 
    550     /**
    551      * Send a request to the native counterpart to set the selection to given range.
    552      * @param start Selection start index.
    553      * @param end Selection end index.
    554      * @return Whether the native counterpart of ImeAdapter received the call.
    555      */
    556     boolean setEditableSelectionOffsets(int start, int end) {
    557         if (mNativeImeAdapterAndroid == 0) return false;
    558         nativeSetEditableSelectionOffsets(mNativeImeAdapterAndroid, start, end);
    559         return true;
    560     }
    561 
    562     /**
    563      * Send a request to the native counterpart to set composing region to given indices.
    564      * @param start The start of the composition.
    565      * @param end The end of the composition.
    566      * @return Whether the native counterpart of ImeAdapter received the call.
    567      */
    568     boolean setComposingRegion(CharSequence text, int start, int end) {
    569         if (mNativeImeAdapterAndroid == 0) return false;
    570         nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end);
    571         mLastComposeText = text != null ? text.toString() : null;
    572         return true;
    573     }
    574 
    575     /**
    576      * Send a request to the native counterpart to unselect text.
    577      * @return Whether the native counterpart of ImeAdapter received the call.
    578      */
    579     public boolean unselect() {
    580         if (mNativeImeAdapterAndroid == 0) return false;
    581         nativeUnselect(mNativeImeAdapterAndroid);
    582         return true;
    583     }
    584 
    585     /**
    586      * Send a request to the native counterpart of ImeAdapter to select all the text.
    587      * @return Whether the native counterpart of ImeAdapter received the call.
    588      */
    589     public boolean selectAll() {
    590         if (mNativeImeAdapterAndroid == 0) return false;
    591         nativeSelectAll(mNativeImeAdapterAndroid);
    592         return true;
    593     }
    594 
    595     /**
    596      * Send a request to the native counterpart of ImeAdapter to cut the selected text.
    597      * @return Whether the native counterpart of ImeAdapter received the call.
    598      */
    599     public boolean cut() {
    600         if (mNativeImeAdapterAndroid == 0) return false;
    601         nativeCut(mNativeImeAdapterAndroid);
    602         return true;
    603     }
    604 
    605     /**
    606      * Send a request to the native counterpart of ImeAdapter to copy the selected text.
    607      * @return Whether the native counterpart of ImeAdapter received the call.
    608      */
    609     public boolean copy() {
    610         if (mNativeImeAdapterAndroid == 0) return false;
    611         nativeCopy(mNativeImeAdapterAndroid);
    612         return true;
    613     }
    614 
    615     /**
    616      * Send a request to the native counterpart of ImeAdapter to paste the text from the clipboard.
    617      * @return Whether the native counterpart of ImeAdapter received the call.
    618      */
    619     public boolean paste() {
    620         if (mNativeImeAdapterAndroid == 0) return false;
    621         nativePaste(mNativeImeAdapterAndroid);
    622         return true;
    623     }
    624 
    625     // Calls from C++ to Java
    626 
    627     @CalledByNative
    628     private static void initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp,
    629             int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl,
    630             int modifierCapsLockOn, int modifierNumLockOn) {
    631         sEventTypeRawKeyDown = eventTypeRawKeyDown;
    632         sEventTypeKeyUp = eventTypeKeyUp;
    633         sEventTypeChar = eventTypeChar;
    634         sModifierShift = modifierShift;
    635         sModifierAlt = modifierAlt;
    636         sModifierCtrl = modifierCtrl;
    637         sModifierCapsLockOn = modifierCapsLockOn;
    638         sModifierNumLockOn = modifierNumLockOn;
    639     }
    640 
    641     @CalledByNative
    642     private static void initializeTextInputTypes(int textInputTypeNone, int textInputTypeText,
    643             int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch,
    644             int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel,
    645             int textInputTypeNumber, int textInputTypeContentEditable) {
    646         sTextInputTypeNone = textInputTypeNone;
    647         sTextInputTypeText = textInputTypeText;
    648         sTextInputTypeTextArea = textInputTypeTextArea;
    649         sTextInputTypePassword = textInputTypePassword;
    650         sTextInputTypeSearch = textInputTypeSearch;
    651         sTextInputTypeUrl = textInputTypeUrl;
    652         sTextInputTypeEmail = textInputTypeEmail;
    653         sTextInputTypeTel = textInputTypeTel;
    654         sTextInputTypeNumber = textInputTypeNumber;
    655         sTextInputTypeContentEditable = textInputTypeContentEditable;
    656     }
    657 
    658     @CalledByNative
    659     private static void initializeTextInputFlags(
    660             int textInputFlagAutocompleteOn, int textInputFlagAutocompleteOff,
    661             int textInputFlagAutocorrectOn, int textInputFlagAutocorrectOff,
    662             int textInputFlagSpellcheckOn, int textInputFlagSpellcheckOff) {
    663         sTextInputFlagAutocompleteOn = textInputFlagAutocompleteOn;
    664         sTextInputFlagAutocompleteOff = textInputFlagAutocompleteOff;
    665         sTextInputFlagAutocorrectOn = textInputFlagAutocorrectOn;
    666         sTextInputFlagAutocorrectOff = textInputFlagAutocorrectOff;
    667         sTextInputFlagSpellcheckOn = textInputFlagSpellcheckOn;
    668         sTextInputFlagSpellcheckOff = textInputFlagSpellcheckOff;
    669     }
    670 
    671     @CalledByNative
    672     private void focusedNodeChanged(boolean isEditable) {
    673         if (mInputConnection != null && isEditable) mInputConnection.restartInput();
    674     }
    675 
    676     @CalledByNative
    677     private void populateUnderlinesFromSpans(CharSequence text, long underlines) {
    678         if (!(text instanceof SpannableString)) return;
    679 
    680         SpannableString spannableString = ((SpannableString) text);
    681         CharacterStyle spans[] =
    682                 spannableString.getSpans(0, text.length(), CharacterStyle.class);
    683         for (CharacterStyle span : spans) {
    684             if (span instanceof BackgroundColorSpan) {
    685                 nativeAppendBackgroundColorSpan(underlines, spannableString.getSpanStart(span),
    686                         spannableString.getSpanEnd(span),
    687                         ((BackgroundColorSpan) span).getBackgroundColor());
    688             } else if (span instanceof UnderlineSpan) {
    689                 nativeAppendUnderlineSpan(underlines, spannableString.getSpanStart(span),
    690                         spannableString.getSpanEnd(span));
    691             }
    692         }
    693     }
    694 
    695     @CalledByNative
    696     private void cancelComposition() {
    697         if (mInputConnection != null) mInputConnection.restartInput();
    698         mLastComposeText = null;
    699     }
    700 
    701     @CalledByNative
    702     void detach() {
    703         if (mDismissInput != null) {
    704             mHandler.removeCallbacks(mDismissInput);
    705             mDismissInput.detach();
    706         }
    707         mNativeImeAdapterAndroid = 0;
    708         mTextInputType = 0;
    709     }
    710 
    711     private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid,
    712             int eventType, long timestampMs, int keyCode, int modifiers, int unicodeChar);
    713 
    714     private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event,
    715             int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey,
    716             int unicodeChar);
    717 
    718     private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end);
    719 
    720     private static native void nativeAppendBackgroundColorSpan(long underlinePtr, int start,
    721             int end, int backgroundColor);
    722 
    723     private native void nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text,
    724             String textStr, int newCursorPosition);
    725 
    726     private native void nativeCommitText(long nativeImeAdapterAndroid, String textStr);
    727 
    728     private native void nativeFinishComposingText(long nativeImeAdapterAndroid);
    729 
    730     private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid);
    731 
    732     private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid,
    733             int start, int end);
    734 
    735     private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end);
    736 
    737     private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid,
    738             int before, int after);
    739 
    740     private native void nativeUnselect(long nativeImeAdapterAndroid);
    741     private native void nativeSelectAll(long nativeImeAdapterAndroid);
    742     private native void nativeCut(long nativeImeAdapterAndroid);
    743     private native void nativeCopy(long nativeImeAdapterAndroid);
    744     private native void nativePaste(long nativeImeAdapterAndroid);
    745     private native void nativeResetImeAdapter(long nativeImeAdapterAndroid);
    746 }
    747