• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
onImeEvent(boolean isFinish)58         void onImeEvent(boolean isFinish);
59 
60         /**
61          * Called when a request to hide the keyboard is sent to InputMethodManager.
62          */
onDismissInput()63         void onDismissInput();
64 
65         /**
66          * @return View that the keyboard should be attached to.
67          */
getAttachedView()68         View getAttachedView();
69 
70         /**
71          * @return Object that should be called for all keyboard show and hide requests.
72          */
getNewShowKeyboardReceiver()73         ResultReceiver getNewShowKeyboardReceiver();
74     }
75 
76     private class DelayedDismissInput implements Runnable {
77         private final long mNativeImeAdapter;
78 
DelayedDismissInput(long nativeImeAdapter)79         DelayedDismissInput(long nativeImeAdapter) {
80             mNativeImeAdapter = nativeImeAdapter;
81         }
82 
83         @Override
run()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      */
ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder)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 {
get(View view, ImeAdapter imeAdapter, Editable editable, EditorInfo outAttrs)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
setInputMethodManagerWrapper(InputMethodManagerWrapper immw)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      */
getInputMethodManagerWrapper()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      */
setInputConnection(AdapterInputConnection inputConnection)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      */
getTextInputType()183     int getTextInputType() {
184         return mTextInputType;
185     }
186 
187     /**
188      * @return Constant representing that a focused node is not an input field.
189      */
getTextInputTypeNone()190     public static int getTextInputTypeNone() {
191         return sTextInputTypeNone;
192     }
193 
getModifiers(int metaState)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      */
updateKeyboardVisibility(long nativeImeAdapter, int textInputType, boolean showIfNeeded)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 
attach(long nativeImeAdapter, int textInputType)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      */
attach(long nativeImeAdapter)269     public void attach(long nativeImeAdapter) {
270         attach(nativeImeAdapter, sTextInputTypeNone);
271     }
272 
showKeyboard()273     private void showKeyboard() {
274         mIsShowWithoutHideOutstanding = true;
275         mInputMethodManagerWrapper.showSoftInput(mViewEmbedder.getAttachedView(), 0,
276                 mViewEmbedder.getNewShowKeyboardReceiver());
277     }
278 
dismissInput(boolean unzoomIfNeeded)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 
hasInputType()289     private boolean hasInputType() {
290         return mTextInputType != sTextInputTypeNone;
291     }
292 
isTextInputType(int type)293     private static boolean isTextInputType(int type) {
294         return type != sTextInputTypeNone && !InputDialogContainer.isDialogInputType(type);
295     }
296 
hasTextInputType()297     public boolean hasTextInputType() {
298         return isTextInputType(mTextInputType);
299     }
300 
301     /**
302      * @return true if the selected text is of password.
303      */
isSelectionPassword()304     public boolean isSelectionPassword() {
305         return mTextInputType == sTextInputTypePassword;
306     }
307 
dispatchKeyEvent(KeyEvent event)308     public boolean dispatchKeyEvent(KeyEvent event) {
309         return translateAndSendNativeEvents(event);
310     }
311 
shouldSendKeyEventWithKeyCode(String text)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 
sendKeyEventWithKeyCode(int keyCode, int flags)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 
checkCompositionQueueAndCallNative(CharSequence text, int newCursorPosition, boolean isCommit)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 
finishComposingText()363     void finishComposingText() {
364         if (mNativeImeAdapterAndroid == 0) return;
365         nativeFinishComposingText(mNativeImeAdapterAndroid);
366     }
367 
translateAndSendNativeEvents(KeyEvent event)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 
sendSyntheticKeyEvent(int eventType, long timestampMs, int keyCode, int unicodeChar)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      */
deleteSurroundingText(int beforeLength, int afterLength)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      */
setEditableSelectionOffsets(int start, int end)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      */
setComposingRegion(int start, int end)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      */
unselect()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      */
selectAll()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      */
cut()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      */
copy()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      */
paste()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
initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp, int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl, int modifierCapsLockOn, int modifierNumLockOn)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
initializeTextInputTypes(int textInputTypeNone, int textInputTypeText, int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch, int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel, int textInputTypeNumber, int textInputTypeContentEditable)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
focusedNodeChanged(boolean isEditable)522     private void focusedNodeChanged(boolean isEditable) {
523         if (mInputConnection != null && isEditable) mInputConnection.restartInput();
524     }
525 
526     @CalledByNative
populateUnderlinesFromSpans(CharSequence text, long underlines)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
cancelComposition()546     private void cancelComposition() {
547         if (mInputConnection != null) mInputConnection.restartInput();
548     }
549 
550     @CalledByNative
detach()551     void detach() {
552         if (mDismissInput != null) mHandler.removeCallbacks(mDismissInput);
553         mNativeImeAdapterAndroid = 0;
554         mTextInputType = 0;
555     }
556 
nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid, int eventType, long timestampMs, int keyCode, int unicodeChar)557     private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid,
558             int eventType, long timestampMs, int keyCode, int unicodeChar);
559 
nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event, int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey, int unicodeChar)560     private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event,
561             int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey,
562             int unicodeChar);
563 
nativeAppendUnderlineSpan(long underlinePtr, int start, int end)564     private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end);
565 
nativeAppendBackgroundColorSpan(long underlinePtr, int start, int end, int backgroundColor)566     private static native void nativeAppendBackgroundColorSpan(long underlinePtr, int start,
567             int end, int backgroundColor);
568 
nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text, String textStr, int newCursorPosition)569     private native void nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text,
570             String textStr, int newCursorPosition);
571 
nativeCommitText(long nativeImeAdapterAndroid, String textStr)572     private native void nativeCommitText(long nativeImeAdapterAndroid, String textStr);
573 
nativeFinishComposingText(long nativeImeAdapterAndroid)574     private native void nativeFinishComposingText(long nativeImeAdapterAndroid);
575 
nativeAttachImeAdapter(long nativeImeAdapterAndroid)576     private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid);
577 
nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid, int start, int end)578     private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid,
579             int start, int end);
580 
nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end)581     private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end);
582 
nativeDeleteSurroundingText(long nativeImeAdapterAndroid, int before, int after)583     private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid,
584             int before, int after);
585 
nativeUnselect(long nativeImeAdapterAndroid)586     private native void nativeUnselect(long nativeImeAdapterAndroid);
nativeSelectAll(long nativeImeAdapterAndroid)587     private native void nativeSelectAll(long nativeImeAdapterAndroid);
nativeCut(long nativeImeAdapterAndroid)588     private native void nativeCut(long nativeImeAdapterAndroid);
nativeCopy(long nativeImeAdapterAndroid)589     private native void nativeCopy(long nativeImeAdapterAndroid);
nativePaste(long nativeImeAdapterAndroid)590     private native void nativePaste(long nativeImeAdapterAndroid);
nativeResetImeAdapter(long nativeImeAdapterAndroid)591     private native void nativeResetImeAdapter(long nativeImeAdapterAndroid);
592 }
593