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.
5 package org.chromium.content.browser.input;
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;
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;
25 import java.lang.CharSequence;
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.
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.
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.
48 @JNINamespace("content")
49 public class ImeAdapter {
52 * Interface for the delegate that needs to be notified of IME changes.
54 public interface ImeAdapterDelegate {
56 * Called to notify the delegate about synthetic/real key events before sending to renderer.
61 * Called when a request to hide the keyboard is sent to InputMethodManager.
63 void onDismissInput();
66 * @return View that the keyboard should be attached to.
68 View getAttachedView();
71 * @return Object that should be called for all keyboard show and hide requests.
73 ResultReceiver getNewShowKeyboardReceiver();
76 private class DelayedDismissInput implements Runnable {
77 private long mNativeImeAdapter;
79 DelayedDismissInput(long nativeImeAdapter) {
80 mNativeImeAdapter = nativeImeAdapter;
83 // http://crbug.com/413744
85 mNativeImeAdapter = 0;
90 if (mNativeImeAdapter != 0) {
91 attach(mNativeImeAdapter, sTextInputTypeNone, sTextInputFlagNone);
97 private static final int COMPOSITION_KEY_CODE = 229;
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;
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;
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;
147 int mLastSyntheticKeyCode;
150 boolean mIsShowWithoutHideOutstanding = false;
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.
157 public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder) {
158 mInputMethodManagerWrapper = wrapper;
159 mViewEmbedder = embedder;
160 mHandler = new Handler();
164 * Default factory for AdapterInputConnection classes.
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);
174 * Overrides the InputMethodManagerWrapper that ImeAdapter uses to make calls to
175 * InputMethodManager.
176 * @param immw InputMethodManagerWrapper that should be used to call InputMethodManager.
179 public void setInputMethodManagerWrapper(InputMethodManagerWrapper immw) {
180 mInputMethodManagerWrapper = immw;
184 * Should be only used by AdapterInputConnection.
185 * @return InputMethodManagerWrapper that should receive all the calls directed to
186 * InputMethodManager.
188 InputMethodManagerWrapper getInputMethodManagerWrapper() {
189 return mInputMethodManagerWrapper;
193 * Set the current active InputConnection when a new InputConnection is constructed.
194 * @param inputConnection The input connection that is currently used with IME.
196 void setInputConnection(AdapterInputConnection inputConnection) {
197 mInputConnection = inputConnection;
198 mLastComposeText = null;
202 * Should be used only by AdapterInputConnection.
203 * @return The input type of currently focused element.
205 int getTextInputType() {
206 return mTextInputType;
210 * Should be used only by AdapterInputConnection.
211 * @return The input flags of the currently focused element.
213 int getTextInputFlags() {
214 return mTextInputFlags;
218 * @return Constant representing that a focused node is not an input field.
220 public static int getTextInputTypeNone() {
221 return sTextInputTypeNone;
224 private static int getModifiers(int metaState) {
226 if ((metaState & KeyEvent.META_SHIFT_ON) != 0) {
227 modifiers |= sModifierShift;
229 if ((metaState & KeyEvent.META_ALT_ON) != 0) {
230 modifiers |= sModifierAlt;
232 if ((metaState & KeyEvent.META_CTRL_ON) != 0) {
233 modifiers |= sModifierCtrl;
235 if ((metaState & KeyEvent.META_CAPS_LOCK_ON) != 0) {
236 modifiers |= sModifierCapsLockOn;
238 if ((metaState & KeyEvent.META_NUM_LOCK_ON) != 0) {
239 modifiers |= sModifierNumLockOn;
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.
250 public void updateKeyboardVisibility(long nativeImeAdapter, int textInputType,
251 int textInputFlags, boolean showIfNeeded) {
252 mHandler.removeCallbacks(mDismissInput);
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) {
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);
269 attach(nativeImeAdapter, textInputType, textInputFlags);
271 mInputMethodManagerWrapper.restartInput(mViewEmbedder.getAttachedView());
275 } else if (hasInputType() && showIfNeeded) {
280 public void attach(long nativeImeAdapter, int textInputType, int textInputFlags) {
281 if (mNativeImeAdapterAndroid != 0) {
282 nativeResetImeAdapter(mNativeImeAdapterAndroid);
284 mNativeImeAdapterAndroid = nativeImeAdapter;
285 mTextInputType = textInputType;
286 mTextInputFlags = textInputFlags;
287 mLastComposeText = null;
288 if (nativeImeAdapter != 0) {
289 nativeAttachImeAdapter(mNativeImeAdapterAndroid);
291 if (mTextInputType == sTextInputTypeNone) {
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.
301 public void attach(long nativeImeAdapter) {
302 attach(nativeImeAdapter, sTextInputTypeNone, sTextInputFlagNone);
305 private void showKeyboard() {
306 mIsShowWithoutHideOutstanding = true;
307 mInputMethodManagerWrapper.showSoftInput(mViewEmbedder.getAttachedView(), 0,
308 mViewEmbedder.getNewShowKeyboardReceiver());
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);
318 mViewEmbedder.onDismissInput();
321 private boolean hasInputType() {
322 return mTextInputType != sTextInputTypeNone;
325 private static boolean isTextInputType(int type) {
326 return type != sTextInputTypeNone && !InputDialogContainer.isDialogInputType(type);
329 public boolean hasTextInputType() {
330 return isTextInputType(mTextInputType);
334 * @return true if the selected text is of password.
336 public boolean isSelectionPassword() {
337 return mTextInputType == sTextInputTypePassword;
340 public boolean dispatchKeyEvent(KeyEvent event) {
341 return translateAndSendNativeEvents(event);
344 private int shouldSendKeyEventWithKeyCode(String text) {
345 if (text.length() != 1) return COMPOSITION_KEY_CODE;
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;
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
357 private static KeyEvent androidKeyEventForCharacter(char chr) {
358 if (sKeyCharacterMap == null) {
359 sKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
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.
368 for (int i = 0; i < events.length; ++i) {
369 if (events[i].getAction() == KeyEvent.ACTION_DOWN &&
370 !KeyEvent.isModifierKey(events[i].getKeyCode())) {
375 return null; // No printing characters were found.
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
383 if (oldtext == null) {
384 if (newtext.length() == 1) {
385 return androidKeyEventForCharacter(newtext.charAt(0));
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));
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);
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.
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,
411 translateAndSendNativeEvents(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
412 KeyEvent.ACTION_UP, keyCode, 0, 0,
413 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
417 // Calls from Java to C++
418 // TODO: Add performance tracing to more complicated functions.
420 boolean checkCompositionQueueAndCallNative(CharSequence text, int newCursorPosition,
422 if (mNativeImeAdapterAndroid == 0) return false;
423 mViewEmbedder.onImeEvent();
425 String textStr = text.toString();
426 int keyCode = shouldSendKeyEventWithKeyCode(textStr);
427 long timeStampMs = SystemClock.uptimeMillis();
429 if (keyCode != COMPOSITION_KEY_CODE) {
430 sendKeyEventWithKeyCode(keyCode,
431 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
433 KeyEvent keyEvent = getTypedKeyEventGuess(mLastComposeText, textStr);
435 if (keyEvent != null) {
436 keyCode = keyEvent.getKeyCode();
437 modifiers = getModifiers(keyEvent.getMetaState());
438 } else if (!textStr.equals(mLastComposeText)) {
439 keyCode = KeyEvent.KEYCODE_UNKNOWN;
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));
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.
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.
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
473 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawKeyDown,
474 timeStampMs, keyCode, modifiers, 0);
478 nativeCommitText(mNativeImeAdapterAndroid, textStr);
481 nativeSetComposingText(mNativeImeAdapterAndroid, text, textStr, newCursorPosition);
485 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyUp,
486 timeStampMs, keyCode, modifiers, 0);
489 mLastSyntheticKeyCode = keyCode;
492 mLastComposeText = textStr;
496 void finishComposingText() {
497 mLastComposeText = null;
498 if (mNativeImeAdapterAndroid == 0) return;
499 nativeFinishComposingText(mNativeImeAdapterAndroid);
502 boolean translateAndSendNativeEvents(KeyEvent event) {
503 if (mNativeImeAdapterAndroid == 0) return false;
505 int action = event.getAction();
506 if (action != KeyEvent.ACTION_DOWN && action != KeyEvent.ACTION_UP) {
507 // action == KeyEvent.ACTION_MULTIPLE
508 // TODO(bulach): confirm the actual behavior. Apparently:
509 // If event.getKeyCode() == KEYCODE_UNKNOWN, we can send a
510 // composition key down (229) followed by a commit text with the
511 // string from event.getUnicodeChars().
512 // Otherwise, we'd need to send an event with a
513 // WebInputEvent::IsAutoRepeat modifier. We also need to verify when
514 // we receive ACTION_MULTIPLE: we may receive it after an ACTION_DOWN,
515 // and if that's the case, we'll need to review when to send the Char
519 mViewEmbedder.onImeEvent();
520 return nativeSendKeyEvent(mNativeImeAdapterAndroid, event, event.getAction(),
521 getModifiers(event.getMetaState()), event.getEventTime(), event.getKeyCode(),
522 /*isSystemKey=*/false, event.getUnicodeChar());
525 boolean sendSyntheticKeyEvent(int eventType, long timestampMs, int keyCode, int modifiers,
527 if (mNativeImeAdapterAndroid == 0) return false;
529 nativeSendSyntheticKeyEvent(
530 mNativeImeAdapterAndroid, eventType, timestampMs, keyCode, modifiers, unicodeChar);
535 * Send a request to the native counterpart to delete a given range of characters.
536 * @param beforeLength Number of characters to extend the selection by before the existing
538 * @param afterLength Number of characters to extend the selection by after the existing
540 * @return Whether the native counterpart of ImeAdapter received the call.
542 boolean deleteSurroundingText(int beforeLength, int afterLength) {
543 mViewEmbedder.onImeEvent();
544 if (mNativeImeAdapterAndroid == 0) return false;
545 nativeDeleteSurroundingText(mNativeImeAdapterAndroid, beforeLength, afterLength);
550 * Send a request to the native counterpart to set the selection to given range.
551 * @param start Selection start index.
552 * @param end Selection end index.
553 * @return Whether the native counterpart of ImeAdapter received the call.
555 boolean setEditableSelectionOffsets(int start, int end) {
556 if (mNativeImeAdapterAndroid == 0) return false;
557 nativeSetEditableSelectionOffsets(mNativeImeAdapterAndroid, start, end);
562 * Send a request to the native counterpart to set composing region to given indices.
563 * @param start The start of the composition.
564 * @param end The end of the composition.
565 * @return Whether the native counterpart of ImeAdapter received the call.
567 boolean setComposingRegion(CharSequence text, int start, int end) {
568 if (mNativeImeAdapterAndroid == 0) return false;
569 nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end);
570 mLastComposeText = text != null ? text.toString() : null;
575 * Send a request to the native counterpart to unselect text.
576 * @return Whether the native counterpart of ImeAdapter received the call.
578 public boolean unselect() {
579 if (mNativeImeAdapterAndroid == 0) return false;
580 nativeUnselect(mNativeImeAdapterAndroid);
585 * Send a request to the native counterpart of ImeAdapter to select all the text.
586 * @return Whether the native counterpart of ImeAdapter received the call.
588 public boolean selectAll() {
589 if (mNativeImeAdapterAndroid == 0) return false;
590 nativeSelectAll(mNativeImeAdapterAndroid);
595 * Send a request to the native counterpart of ImeAdapter to cut the selected text.
596 * @return Whether the native counterpart of ImeAdapter received the call.
598 public boolean cut() {
599 if (mNativeImeAdapterAndroid == 0) return false;
600 nativeCut(mNativeImeAdapterAndroid);
605 * Send a request to the native counterpart of ImeAdapter to copy the selected text.
606 * @return Whether the native counterpart of ImeAdapter received the call.
608 public boolean copy() {
609 if (mNativeImeAdapterAndroid == 0) return false;
610 nativeCopy(mNativeImeAdapterAndroid);
615 * Send a request to the native counterpart of ImeAdapter to paste the text from the clipboard.
616 * @return Whether the native counterpart of ImeAdapter received the call.
618 public boolean paste() {
619 if (mNativeImeAdapterAndroid == 0) return false;
620 nativePaste(mNativeImeAdapterAndroid);
624 // Calls from C++ to Java
627 private static void initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp,
628 int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl,
629 int modifierCapsLockOn, int modifierNumLockOn) {
630 sEventTypeRawKeyDown = eventTypeRawKeyDown;
631 sEventTypeKeyUp = eventTypeKeyUp;
632 sEventTypeChar = eventTypeChar;
633 sModifierShift = modifierShift;
634 sModifierAlt = modifierAlt;
635 sModifierCtrl = modifierCtrl;
636 sModifierCapsLockOn = modifierCapsLockOn;
637 sModifierNumLockOn = modifierNumLockOn;
641 private static void initializeTextInputTypes(int textInputTypeNone, int textInputTypeText,
642 int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch,
643 int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel,
644 int textInputTypeNumber, int textInputTypeContentEditable) {
645 sTextInputTypeNone = textInputTypeNone;
646 sTextInputTypeText = textInputTypeText;
647 sTextInputTypeTextArea = textInputTypeTextArea;
648 sTextInputTypePassword = textInputTypePassword;
649 sTextInputTypeSearch = textInputTypeSearch;
650 sTextInputTypeUrl = textInputTypeUrl;
651 sTextInputTypeEmail = textInputTypeEmail;
652 sTextInputTypeTel = textInputTypeTel;
653 sTextInputTypeNumber = textInputTypeNumber;
654 sTextInputTypeContentEditable = textInputTypeContentEditable;
658 private static void initializeTextInputFlags(
659 int textInputFlagAutocompleteOn, int textInputFlagAutocompleteOff,
660 int textInputFlagAutocorrectOn, int textInputFlagAutocorrectOff,
661 int textInputFlagSpellcheckOn, int textInputFlagSpellcheckOff) {
662 sTextInputFlagAutocompleteOn = textInputFlagAutocompleteOn;
663 sTextInputFlagAutocompleteOff = textInputFlagAutocompleteOff;
664 sTextInputFlagAutocorrectOn = textInputFlagAutocorrectOn;
665 sTextInputFlagAutocorrectOff = textInputFlagAutocorrectOff;
666 sTextInputFlagSpellcheckOn = textInputFlagSpellcheckOn;
667 sTextInputFlagSpellcheckOff = textInputFlagSpellcheckOff;
671 private void focusedNodeChanged(boolean isEditable) {
672 if (mInputConnection != null && isEditable) mInputConnection.restartInput();
676 private void populateUnderlinesFromSpans(CharSequence text, long underlines) {
677 if (!(text instanceof SpannableString)) return;
679 SpannableString spannableString = ((SpannableString) text);
680 CharacterStyle spans[] =
681 spannableString.getSpans(0, text.length(), CharacterStyle.class);
682 for (CharacterStyle span : spans) {
683 if (span instanceof BackgroundColorSpan) {
684 nativeAppendBackgroundColorSpan(underlines, spannableString.getSpanStart(span),
685 spannableString.getSpanEnd(span),
686 ((BackgroundColorSpan) span).getBackgroundColor());
687 } else if (span instanceof UnderlineSpan) {
688 nativeAppendUnderlineSpan(underlines, spannableString.getSpanStart(span),
689 spannableString.getSpanEnd(span));
695 private void cancelComposition() {
696 if (mInputConnection != null) mInputConnection.restartInput();
697 mLastComposeText = null;
701 private void setCharacterBounds(float[] characterBounds) {
702 // TODO(yukawa): Implement this.
707 if (mDismissInput != null) {
708 mHandler.removeCallbacks(mDismissInput);
709 mDismissInput.detach();
711 mNativeImeAdapterAndroid = 0;
715 private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid,
716 int eventType, long timestampMs, int keyCode, int modifiers, int unicodeChar);
718 private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event,
719 int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey,
722 private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end);
724 private static native void nativeAppendBackgroundColorSpan(long underlinePtr, int start,
725 int end, int backgroundColor);
727 private native void nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text,
728 String textStr, int newCursorPosition);
730 private native void nativeCommitText(long nativeImeAdapterAndroid, String textStr);
732 private native void nativeFinishComposingText(long nativeImeAdapterAndroid);
734 private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid);
736 private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid,
739 private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end);
741 private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid,
742 int before, int after);
744 private native void nativeUnselect(long nativeImeAdapterAndroid);
745 private native void nativeSelectAll(long nativeImeAdapterAndroid);
746 private native void nativeCut(long nativeImeAdapterAndroid);
747 private native void nativeCopy(long nativeImeAdapterAndroid);
748 private native void nativePaste(long nativeImeAdapterAndroid);
749 private native void nativeResetImeAdapter(long nativeImeAdapterAndroid);