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