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