• Home
  • History
  • Annotate
  • 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 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          */
onImeEvent()58         void onImeEvent();
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 long mNativeImeAdapter;
78 
DelayedDismissInput(long nativeImeAdapter)79         DelayedDismissInput(long nativeImeAdapter) {
80             mNativeImeAdapter = nativeImeAdapter;
81         }
82 
83         // http://crbug.com/413744
detach()84         void detach() {
85             mNativeImeAdapter = 0;
86         }
87 
88         @Override
run()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      */
ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder)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 {
get(View view, ImeAdapter imeAdapter, Editable editable, EditorInfo outAttrs)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
setInputMethodManagerWrapper(InputMethodManagerWrapper immw)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      */
getInputMethodManagerWrapper()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      */
setInputConnection(AdapterInputConnection inputConnection)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      */
getTextInputType()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      */
getTextInputFlags()213     int getTextInputFlags() {
214         return mTextInputFlags;
215     }
216 
217     /**
218      * @return Constant representing that a focused node is not an input field.
219      */
getTextInputTypeNone()220     public static int getTextInputTypeNone() {
221         return sTextInputTypeNone;
222     }
223 
getModifiers(int metaState)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      */
updateKeyboardVisibility(long nativeImeAdapter, int textInputType, int textInputFlags, boolean showIfNeeded)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 
attach(long nativeImeAdapter, int textInputType, int textInputFlags)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      */
attach(long nativeImeAdapter)301     public void attach(long nativeImeAdapter) {
302         attach(nativeImeAdapter, sTextInputTypeNone, sTextInputFlagNone);
303     }
304 
showKeyboard()305     private void showKeyboard() {
306         mIsShowWithoutHideOutstanding = true;
307         mInputMethodManagerWrapper.showSoftInput(mViewEmbedder.getAttachedView(), 0,
308                 mViewEmbedder.getNewShowKeyboardReceiver());
309     }
310 
dismissInput(boolean unzoomIfNeeded)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 
hasInputType()321     private boolean hasInputType() {
322         return mTextInputType != sTextInputTypeNone;
323     }
324 
isTextInputType(int type)325     private static boolean isTextInputType(int type) {
326         return type != sTextInputTypeNone && !InputDialogContainer.isDialogInputType(type);
327     }
328 
hasTextInputType()329     public boolean hasTextInputType() {
330         return isTextInputType(mTextInputType);
331     }
332 
333     /**
334      * @return true if the selected text is of password.
335      */
isSelectionPassword()336     public boolean isSelectionPassword() {
337         return mTextInputType == sTextInputTypePassword;
338     }
339 
dispatchKeyEvent(KeyEvent event)340     public boolean dispatchKeyEvent(KeyEvent event) {
341         return translateAndSendNativeEvents(event);
342     }
343 
shouldSendKeyEventWithKeyCode(String text)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      */
androidKeyEventForCharacter(char chr)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
getTypedKeyEventGuess(String oldtext, String newtext)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 
sendKeyEventWithKeyCode(int keyCode, int flags)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 
checkCompositionQueueAndCallNative(CharSequence text, int newCursorPosition, boolean isCommit)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 
finishComposingText()496     void finishComposingText() {
497         mLastComposeText = null;
498         if (mNativeImeAdapterAndroid == 0) return;
499         nativeFinishComposingText(mNativeImeAdapterAndroid);
500     }
501 
translateAndSendNativeEvents(KeyEvent event)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 
sendSyntheticKeyEvent(int eventType, long timestampMs, int keyCode, int modifiers, int unicodeChar)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      */
deleteSurroundingText(int beforeLength, int afterLength)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      */
setEditableSelectionOffsets(int start, int end)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      */
setComposingRegion(CharSequence text, int start, int end)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      */
unselect()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      */
selectAll()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      */
cut()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      */
copy()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      */
paste()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
initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp, int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl, int modifierCapsLockOn, int modifierNumLockOn)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
initializeTextInputTypes(int textInputTypeNone, int textInputTypeText, int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch, int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel, int textInputTypeNumber, int textInputTypeContentEditable)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
initializeTextInputFlags( int textInputFlagAutocompleteOn, int textInputFlagAutocompleteOff, int textInputFlagAutocorrectOn, int textInputFlagAutocorrectOff, int textInputFlagSpellcheckOn, int textInputFlagSpellcheckOff)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
focusedNodeChanged(boolean isEditable)672     private void focusedNodeChanged(boolean isEditable) {
673         if (mInputConnection != null && isEditable) mInputConnection.restartInput();
674     }
675 
676     @CalledByNative
populateUnderlinesFromSpans(CharSequence text, long underlines)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
cancelComposition()696     private void cancelComposition() {
697         if (mInputConnection != null) mInputConnection.restartInput();
698         mLastComposeText = null;
699     }
700 
701     @CalledByNative
detach()702     void detach() {
703         if (mDismissInput != null) {
704             mHandler.removeCallbacks(mDismissInput);
705             mDismissInput.detach();
706         }
707         mNativeImeAdapterAndroid = 0;
708         mTextInputType = 0;
709     }
710 
nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid, int eventType, long timestampMs, int keyCode, int modifiers, int unicodeChar)711     private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid,
712             int eventType, long timestampMs, int keyCode, int modifiers, int unicodeChar);
713 
nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event, int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey, int unicodeChar)714     private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event,
715             int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey,
716             int unicodeChar);
717 
nativeAppendUnderlineSpan(long underlinePtr, int start, int end)718     private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end);
719 
nativeAppendBackgroundColorSpan(long underlinePtr, int start, int end, int backgroundColor)720     private static native void nativeAppendBackgroundColorSpan(long underlinePtr, int start,
721             int end, int backgroundColor);
722 
nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text, String textStr, int newCursorPosition)723     private native void nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text,
724             String textStr, int newCursorPosition);
725 
nativeCommitText(long nativeImeAdapterAndroid, String textStr)726     private native void nativeCommitText(long nativeImeAdapterAndroid, String textStr);
727 
nativeFinishComposingText(long nativeImeAdapterAndroid)728     private native void nativeFinishComposingText(long nativeImeAdapterAndroid);
729 
nativeAttachImeAdapter(long nativeImeAdapterAndroid)730     private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid);
731 
nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid, int start, int end)732     private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid,
733             int start, int end);
734 
nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end)735     private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end);
736 
nativeDeleteSurroundingText(long nativeImeAdapterAndroid, int before, int after)737     private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid,
738             int before, int after);
739 
nativeUnselect(long nativeImeAdapterAndroid)740     private native void nativeUnselect(long nativeImeAdapterAndroid);
nativeSelectAll(long nativeImeAdapterAndroid)741     private native void nativeSelectAll(long nativeImeAdapterAndroid);
nativeCut(long nativeImeAdapterAndroid)742     private native void nativeCut(long nativeImeAdapterAndroid);
nativeCopy(long nativeImeAdapterAndroid)743     private native void nativeCopy(long nativeImeAdapterAndroid);
nativePaste(long nativeImeAdapterAndroid)744     private native void nativePaste(long nativeImeAdapterAndroid);
nativeResetImeAdapter(long nativeImeAdapterAndroid)745     private native void nativeResetImeAdapter(long nativeImeAdapterAndroid);
746 }
747