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 com.google.common.annotations.VisibleForTesting;
     21 
     22 import java.lang.CharSequence;
     23 
     24 import org.chromium.base.CalledByNative;
     25 import org.chromium.base.JNINamespace;
     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          * @param isFinish whether the event is occurring because input is finished.
     57          */
     58         void onImeEvent(boolean isFinish);
     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 final long mNativeImeAdapter;
     78 
     79         DelayedDismissInput(long nativeImeAdapter) {
     80             mNativeImeAdapter = nativeImeAdapter;
     81         }
     82 
     83         @Override
     84         public void run() {
     85             attach(mNativeImeAdapter, sTextInputTypeNone);
     86             dismissInput(true);
     87         }
     88     }
     89 
     90     private static final int COMPOSITION_KEY_CODE = 229;
     91 
     92     // Delay introduced to avoid hiding the keyboard if new show requests are received.
     93     // The time required by the unfocus-focus events triggered by tab has been measured in soju:
     94     // Mean: 18.633 ms, Standard deviation: 7.9837 ms.
     95     // The value here should be higher enough to cover these cases, but not too high to avoid
     96     // letting the user perceiving important delays.
     97     private static final int INPUT_DISMISS_DELAY = 150;
     98 
     99     // All the constants that are retrieved from the C++ code.
    100     // They get set through initializeWebInputEvents and initializeTextInputTypes calls.
    101     static int sEventTypeRawKeyDown;
    102     static int sEventTypeKeyUp;
    103     static int sEventTypeChar;
    104     static int sTextInputTypeNone;
    105     static int sTextInputTypeText;
    106     static int sTextInputTypeTextArea;
    107     static int sTextInputTypePassword;
    108     static int sTextInputTypeSearch;
    109     static int sTextInputTypeUrl;
    110     static int sTextInputTypeEmail;
    111     static int sTextInputTypeTel;
    112     static int sTextInputTypeNumber;
    113     static int sTextInputTypeContentEditable;
    114     static int sModifierShift;
    115     static int sModifierAlt;
    116     static int sModifierCtrl;
    117     static int sModifierCapsLockOn;
    118     static int sModifierNumLockOn;
    119 
    120     private long mNativeImeAdapterAndroid;
    121     private InputMethodManagerWrapper mInputMethodManagerWrapper;
    122     private AdapterInputConnection mInputConnection;
    123     private final ImeAdapterDelegate mViewEmbedder;
    124     private final Handler mHandler;
    125     private DelayedDismissInput mDismissInput = null;
    126     private int mTextInputType;
    127 
    128     @VisibleForTesting
    129     boolean mIsShowWithoutHideOutstanding = false;
    130 
    131     /**
    132      * @param wrapper InputMethodManagerWrapper that should receive all the call directed to
    133      *                InputMethodManager.
    134      * @param embedder The view that is used for callbacks from ImeAdapter.
    135      */
    136     public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder) {
    137         mInputMethodManagerWrapper = wrapper;
    138         mViewEmbedder = embedder;
    139         mHandler = new Handler();
    140     }
    141 
    142     /**
    143      * Default factory for AdapterInputConnection classes.
    144      */
    145     public static class AdapterInputConnectionFactory {
    146         public AdapterInputConnection get(View view, ImeAdapter imeAdapter,
    147                 Editable editable, EditorInfo outAttrs) {
    148             return new AdapterInputConnection(view, imeAdapter, editable, outAttrs);
    149         }
    150     }
    151 
    152     /**
    153      * Overrides the InputMethodManagerWrapper that ImeAdapter uses to make calls to
    154      * InputMethodManager.
    155      * @param immw InputMethodManagerWrapper that should be used to call InputMethodManager.
    156      */
    157     @VisibleForTesting
    158     public void setInputMethodManagerWrapper(InputMethodManagerWrapper immw) {
    159         mInputMethodManagerWrapper = immw;
    160     }
    161 
    162     /**
    163      * Should be only used by AdapterInputConnection.
    164      * @return InputMethodManagerWrapper that should receive all the calls directed to
    165      *         InputMethodManager.
    166      */
    167     InputMethodManagerWrapper getInputMethodManagerWrapper() {
    168         return mInputMethodManagerWrapper;
    169     }
    170 
    171     /**
    172      * Set the current active InputConnection when a new InputConnection is constructed.
    173      * @param inputConnection The input connection that is currently used with IME.
    174      */
    175     void setInputConnection(AdapterInputConnection inputConnection) {
    176         mInputConnection = inputConnection;
    177     }
    178 
    179     /**
    180      * Should be only used by AdapterInputConnection.
    181      * @return The input type of currently focused element.
    182      */
    183     int getTextInputType() {
    184         return mTextInputType;
    185     }
    186 
    187     /**
    188      * @return Constant representing that a focused node is not an input field.
    189      */
    190     public static int getTextInputTypeNone() {
    191         return sTextInputTypeNone;
    192     }
    193 
    194     private static int getModifiers(int metaState) {
    195         int modifiers = 0;
    196         if ((metaState & KeyEvent.META_SHIFT_ON) != 0) {
    197             modifiers |= sModifierShift;
    198         }
    199         if ((metaState & KeyEvent.META_ALT_ON) != 0) {
    200             modifiers |= sModifierAlt;
    201         }
    202         if ((metaState & KeyEvent.META_CTRL_ON) != 0) {
    203             modifiers |= sModifierCtrl;
    204         }
    205         if ((metaState & KeyEvent.META_CAPS_LOCK_ON) != 0) {
    206             modifiers |= sModifierCapsLockOn;
    207         }
    208         if ((metaState & KeyEvent.META_NUM_LOCK_ON) != 0) {
    209             modifiers |= sModifierNumLockOn;
    210         }
    211         return modifiers;
    212     }
    213 
    214     /**
    215      * Shows or hides the keyboard based on passed parameters.
    216      * @param nativeImeAdapter Pointer to the ImeAdapterAndroid object that is sending the update.
    217      * @param textInputType Text input type for the currently focused field in renderer.
    218      * @param showIfNeeded Whether the keyboard should be shown if it is currently hidden.
    219      */
    220     public void updateKeyboardVisibility(long nativeImeAdapter, int textInputType,
    221             boolean showIfNeeded) {
    222         mHandler.removeCallbacks(mDismissInput);
    223 
    224         // If current input type is none and showIfNeeded is false, IME should not be shown
    225         // and input type should remain as none.
    226         if (mTextInputType == sTextInputTypeNone && !showIfNeeded) {
    227             return;
    228         }
    229 
    230         if (mNativeImeAdapterAndroid != nativeImeAdapter || mTextInputType != textInputType) {
    231             // Set a delayed task to perform unfocus. This avoids hiding the keyboard when tabbing
    232             // through text inputs or when JS rapidly changes focus to another text element.
    233             if (textInputType == sTextInputTypeNone) {
    234                 mDismissInput = new DelayedDismissInput(nativeImeAdapter);
    235                 mHandler.postDelayed(mDismissInput, INPUT_DISMISS_DELAY);
    236                 return;
    237             }
    238 
    239             attach(nativeImeAdapter, textInputType);
    240 
    241             mInputMethodManagerWrapper.restartInput(mViewEmbedder.getAttachedView());
    242             if (showIfNeeded) {
    243                 showKeyboard();
    244             }
    245         } else if (hasInputType() && showIfNeeded) {
    246             showKeyboard();
    247         }
    248     }
    249 
    250     public void attach(long nativeImeAdapter, int textInputType) {
    251         if (mNativeImeAdapterAndroid != 0) {
    252             nativeResetImeAdapter(mNativeImeAdapterAndroid);
    253         }
    254         mNativeImeAdapterAndroid = nativeImeAdapter;
    255         mTextInputType = textInputType;
    256         if (nativeImeAdapter != 0) {
    257             nativeAttachImeAdapter(mNativeImeAdapterAndroid);
    258         }
    259         if (mTextInputType == sTextInputTypeNone) {
    260             dismissInput(false);
    261         }
    262     }
    263 
    264     /**
    265      * Attaches the imeAdapter to its native counterpart. This is needed to start forwarding
    266      * keyboard events to WebKit.
    267      * @param nativeImeAdapter The pointer to the native ImeAdapter object.
    268      */
    269     public void attach(long nativeImeAdapter) {
    270         attach(nativeImeAdapter, sTextInputTypeNone);
    271     }
    272 
    273     private void showKeyboard() {
    274         mIsShowWithoutHideOutstanding = true;
    275         mInputMethodManagerWrapper.showSoftInput(mViewEmbedder.getAttachedView(), 0,
    276                 mViewEmbedder.getNewShowKeyboardReceiver());
    277     }
    278 
    279     private void dismissInput(boolean unzoomIfNeeded) {
    280         mIsShowWithoutHideOutstanding  = false;
    281         View view = mViewEmbedder.getAttachedView();
    282         if (mInputMethodManagerWrapper.isActive(view)) {
    283             mInputMethodManagerWrapper.hideSoftInputFromWindow(view.getWindowToken(), 0,
    284                     unzoomIfNeeded ? mViewEmbedder.getNewShowKeyboardReceiver() : null);
    285         }
    286         mViewEmbedder.onDismissInput();
    287     }
    288 
    289     private boolean hasInputType() {
    290         return mTextInputType != sTextInputTypeNone;
    291     }
    292 
    293     private static boolean isTextInputType(int type) {
    294         return type != sTextInputTypeNone && !InputDialogContainer.isDialogInputType(type);
    295     }
    296 
    297     public boolean hasTextInputType() {
    298         return isTextInputType(mTextInputType);
    299     }
    300 
    301     /**
    302      * @return true if the selected text is of password.
    303      */
    304     public boolean isSelectionPassword() {
    305         return mTextInputType == sTextInputTypePassword;
    306     }
    307 
    308     public boolean dispatchKeyEvent(KeyEvent event) {
    309         return translateAndSendNativeEvents(event);
    310     }
    311 
    312     private int shouldSendKeyEventWithKeyCode(String text) {
    313         if (text.length() != 1) return COMPOSITION_KEY_CODE;
    314 
    315         if (text.equals("\n")) return KeyEvent.KEYCODE_ENTER;
    316         else if (text.equals("\t")) return KeyEvent.KEYCODE_TAB;
    317         else return COMPOSITION_KEY_CODE;
    318     }
    319 
    320     void sendKeyEventWithKeyCode(int keyCode, int flags) {
    321         long eventTime = SystemClock.uptimeMillis();
    322         translateAndSendNativeEvents(new KeyEvent(eventTime, eventTime,
    323                 KeyEvent.ACTION_DOWN, keyCode, 0, 0,
    324                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
    325                 flags));
    326         translateAndSendNativeEvents(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
    327                 KeyEvent.ACTION_UP, keyCode, 0, 0,
    328                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
    329                 flags));
    330     }
    331 
    332     // Calls from Java to C++
    333 
    334     boolean checkCompositionQueueAndCallNative(CharSequence text, int newCursorPosition,
    335             boolean isCommit) {
    336         if (mNativeImeAdapterAndroid == 0) return false;
    337         String textStr = text.toString();
    338 
    339         // Committing an empty string finishes the current composition.
    340         boolean isFinish = textStr.isEmpty();
    341         mViewEmbedder.onImeEvent(isFinish);
    342         int keyCode = shouldSendKeyEventWithKeyCode(textStr);
    343         long timeStampMs = SystemClock.uptimeMillis();
    344 
    345         if (keyCode != COMPOSITION_KEY_CODE) {
    346             sendKeyEventWithKeyCode(keyCode,
    347                     KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
    348         } else {
    349             nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawKeyDown,
    350                     timeStampMs, keyCode, 0);
    351             if (isCommit) {
    352                 nativeCommitText(mNativeImeAdapterAndroid, textStr);
    353             } else {
    354                 nativeSetComposingText(mNativeImeAdapterAndroid, text, textStr, newCursorPosition);
    355             }
    356             nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyUp,
    357                     timeStampMs, keyCode, 0);
    358         }
    359 
    360         return true;
    361     }
    362 
    363     void finishComposingText() {
    364         if (mNativeImeAdapterAndroid == 0) return;
    365         nativeFinishComposingText(mNativeImeAdapterAndroid);
    366     }
    367 
    368     boolean translateAndSendNativeEvents(KeyEvent event) {
    369         if (mNativeImeAdapterAndroid == 0) return false;
    370 
    371         int action = event.getAction();
    372         if (action != KeyEvent.ACTION_DOWN &&
    373             action != KeyEvent.ACTION_UP) {
    374             // action == KeyEvent.ACTION_MULTIPLE
    375             // TODO(bulach): confirm the actual behavior. Apparently:
    376             // If event.getKeyCode() == KEYCODE_UNKNOWN, we can send a
    377             // composition key down (229) followed by a commit text with the
    378             // string from event.getUnicodeChars().
    379             // Otherwise, we'd need to send an event with a
    380             // WebInputEvent::IsAutoRepeat modifier. We also need to verify when
    381             // we receive ACTION_MULTIPLE: we may receive it after an ACTION_DOWN,
    382             // and if that's the case, we'll need to review when to send the Char
    383             // event.
    384             return false;
    385         }
    386         mViewEmbedder.onImeEvent(false);
    387         return nativeSendKeyEvent(mNativeImeAdapterAndroid, event, event.getAction(),
    388                 getModifiers(event.getMetaState()), event.getEventTime(), event.getKeyCode(),
    389                              /*isSystemKey=*/false, event.getUnicodeChar());
    390     }
    391 
    392     boolean sendSyntheticKeyEvent(int eventType, long timestampMs, int keyCode, int unicodeChar) {
    393         if (mNativeImeAdapterAndroid == 0) return false;
    394 
    395         nativeSendSyntheticKeyEvent(
    396                 mNativeImeAdapterAndroid, eventType, timestampMs, keyCode, unicodeChar);
    397         return true;
    398     }
    399 
    400     /**
    401      * Send a request to the native counterpart to delete a given range of characters.
    402      * @param beforeLength Number of characters to extend the selection by before the existing
    403      *                     selection.
    404      * @param afterLength Number of characters to extend the selection by after the existing
    405      *                    selection.
    406      * @return Whether the native counterpart of ImeAdapter received the call.
    407      */
    408     boolean deleteSurroundingText(int beforeLength, int afterLength) {
    409         if (mNativeImeAdapterAndroid == 0) return false;
    410         nativeDeleteSurroundingText(mNativeImeAdapterAndroid, beforeLength, afterLength);
    411         return true;
    412     }
    413 
    414     /**
    415      * Send a request to the native counterpart to set the selection to given range.
    416      * @param start Selection start index.
    417      * @param end Selection end index.
    418      * @return Whether the native counterpart of ImeAdapter received the call.
    419      */
    420     boolean setEditableSelectionOffsets(int start, int end) {
    421         if (mNativeImeAdapterAndroid == 0) return false;
    422         nativeSetEditableSelectionOffsets(mNativeImeAdapterAndroid, start, end);
    423         return true;
    424     }
    425 
    426     /**
    427      * Send a request to the native counterpart to set compositing region to given indices.
    428      * @param start The start of the composition.
    429      * @param end The end of the composition.
    430      * @return Whether the native counterpart of ImeAdapter received the call.
    431      */
    432     boolean setComposingRegion(int start, int end) {
    433         if (mNativeImeAdapterAndroid == 0) return false;
    434         nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end);
    435         return true;
    436     }
    437 
    438     /**
    439      * Send a request to the native counterpart to unselect text.
    440      * @return Whether the native counterpart of ImeAdapter received the call.
    441      */
    442     public boolean unselect() {
    443         if (mNativeImeAdapterAndroid == 0) return false;
    444         nativeUnselect(mNativeImeAdapterAndroid);
    445         return true;
    446     }
    447 
    448     /**
    449      * Send a request to the native counterpart of ImeAdapter to select all the text.
    450      * @return Whether the native counterpart of ImeAdapter received the call.
    451      */
    452     public boolean selectAll() {
    453         if (mNativeImeAdapterAndroid == 0) return false;
    454         nativeSelectAll(mNativeImeAdapterAndroid);
    455         return true;
    456     }
    457 
    458     /**
    459      * Send a request to the native counterpart of ImeAdapter to cut the selected text.
    460      * @return Whether the native counterpart of ImeAdapter received the call.
    461      */
    462     public boolean cut() {
    463         if (mNativeImeAdapterAndroid == 0) return false;
    464         nativeCut(mNativeImeAdapterAndroid);
    465         return true;
    466     }
    467 
    468     /**
    469      * Send a request to the native counterpart of ImeAdapter to copy the selected text.
    470      * @return Whether the native counterpart of ImeAdapter received the call.
    471      */
    472     public boolean copy() {
    473         if (mNativeImeAdapterAndroid == 0) return false;
    474         nativeCopy(mNativeImeAdapterAndroid);
    475         return true;
    476     }
    477 
    478     /**
    479      * Send a request to the native counterpart of ImeAdapter to paste the text from the clipboard.
    480      * @return Whether the native counterpart of ImeAdapter received the call.
    481      */
    482     public boolean paste() {
    483         if (mNativeImeAdapterAndroid == 0) return false;
    484         nativePaste(mNativeImeAdapterAndroid);
    485         return true;
    486     }
    487 
    488     // Calls from C++ to Java
    489 
    490     @CalledByNative
    491     private static void initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp,
    492             int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl,
    493             int modifierCapsLockOn, int modifierNumLockOn) {
    494         sEventTypeRawKeyDown = eventTypeRawKeyDown;
    495         sEventTypeKeyUp = eventTypeKeyUp;
    496         sEventTypeChar = eventTypeChar;
    497         sModifierShift = modifierShift;
    498         sModifierAlt = modifierAlt;
    499         sModifierCtrl = modifierCtrl;
    500         sModifierCapsLockOn = modifierCapsLockOn;
    501         sModifierNumLockOn = modifierNumLockOn;
    502     }
    503 
    504     @CalledByNative
    505     private static void initializeTextInputTypes(int textInputTypeNone, int textInputTypeText,
    506             int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch,
    507             int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel,
    508             int textInputTypeNumber, int textInputTypeContentEditable) {
    509         sTextInputTypeNone = textInputTypeNone;
    510         sTextInputTypeText = textInputTypeText;
    511         sTextInputTypeTextArea = textInputTypeTextArea;
    512         sTextInputTypePassword = textInputTypePassword;
    513         sTextInputTypeSearch = textInputTypeSearch;
    514         sTextInputTypeUrl = textInputTypeUrl;
    515         sTextInputTypeEmail = textInputTypeEmail;
    516         sTextInputTypeTel = textInputTypeTel;
    517         sTextInputTypeNumber = textInputTypeNumber;
    518         sTextInputTypeContentEditable = textInputTypeContentEditable;
    519     }
    520 
    521     @CalledByNative
    522     private void focusedNodeChanged(boolean isEditable) {
    523         if (mInputConnection != null && isEditable) mInputConnection.restartInput();
    524     }
    525 
    526     @CalledByNative
    527     private void populateUnderlinesFromSpans(CharSequence text, long underlines) {
    528         if (!(text instanceof SpannableString)) return;
    529 
    530         SpannableString spannableString = ((SpannableString) text);
    531         CharacterStyle spans[] =
    532                 spannableString.getSpans(0, text.length(), CharacterStyle.class);
    533         for (CharacterStyle span : spans) {
    534             if (span instanceof BackgroundColorSpan) {
    535                 nativeAppendBackgroundColorSpan(underlines, spannableString.getSpanStart(span),
    536                         spannableString.getSpanEnd(span),
    537                         ((BackgroundColorSpan) span).getBackgroundColor());
    538             } else if (span instanceof UnderlineSpan) {
    539                 nativeAppendUnderlineSpan(underlines, spannableString.getSpanStart(span),
    540                         spannableString.getSpanEnd(span));
    541             }
    542         }
    543     }
    544 
    545     @CalledByNative
    546     private void cancelComposition() {
    547         if (mInputConnection != null) mInputConnection.restartInput();
    548     }
    549 
    550     @CalledByNative
    551     void detach() {
    552         if (mDismissInput != null) mHandler.removeCallbacks(mDismissInput);
    553         mNativeImeAdapterAndroid = 0;
    554         mTextInputType = 0;
    555     }
    556 
    557     private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid,
    558             int eventType, long timestampMs, int keyCode, int unicodeChar);
    559 
    560     private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event,
    561             int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey,
    562             int unicodeChar);
    563 
    564     private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end);
    565 
    566     private static native void nativeAppendBackgroundColorSpan(long underlinePtr, int start,
    567             int end, int backgroundColor);
    568 
    569     private native void nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text,
    570             String textStr, int newCursorPosition);
    571 
    572     private native void nativeCommitText(long nativeImeAdapterAndroid, String textStr);
    573 
    574     private native void nativeFinishComposingText(long nativeImeAdapterAndroid);
    575 
    576     private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid);
    577 
    578     private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid,
    579             int start, int end);
    580 
    581     private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end);
    582 
    583     private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid,
    584             int before, int after);
    585 
    586     private native void nativeUnselect(long nativeImeAdapterAndroid);
    587     private native void nativeSelectAll(long nativeImeAdapterAndroid);
    588     private native void nativeCut(long nativeImeAdapterAndroid);
    589     private native void nativeCopy(long nativeImeAdapterAndroid);
    590     private native void nativePaste(long nativeImeAdapterAndroid);
    591     private native void nativeResetImeAdapter(long nativeImeAdapterAndroid);
    592 }
    593