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