1 // Copyright 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.content.browser.input; 6 7 import android.os.Handler; 8 import android.os.ResultReceiver; 9 import android.os.SystemClock; 10 import android.text.Editable; 11 import android.text.SpannableString; 12 import android.text.style.BackgroundColorSpan; 13 import android.text.style.CharacterStyle; 14 import android.text.style.UnderlineSpan; 15 import android.view.KeyCharacterMap; 16 import android.view.KeyEvent; 17 import android.view.View; 18 import android.view.inputmethod.EditorInfo; 19 20 import com.google.common.annotations.VisibleForTesting; 21 22 import java.lang.CharSequence; 23 24 import org.chromium.base.CalledByNative; 25 import 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") 49 public class ImeAdapter { 50 51 /** 52 * Interface for the delegate that needs to be notified of IME changes. 53 */ 54 public interface ImeAdapterDelegate { 55 /** 56 * @param isFinish whether the event is occurring because input is finished. 57 */ onImeEvent(boolean isFinish)58 void onImeEvent(boolean isFinish); 59 60 /** 61 * Called when a request to hide the keyboard is sent to InputMethodManager. 62 */ onDismissInput()63 void onDismissInput(); 64 65 /** 66 * @return View that the keyboard should be attached to. 67 */ getAttachedView()68 View getAttachedView(); 69 70 /** 71 * @return Object that should be called for all keyboard show and hide requests. 72 */ getNewShowKeyboardReceiver()73 ResultReceiver getNewShowKeyboardReceiver(); 74 } 75 76 private class DelayedDismissInput implements Runnable { 77 private final long mNativeImeAdapter; 78 DelayedDismissInput(long nativeImeAdapter)79 DelayedDismissInput(long nativeImeAdapter) { 80 mNativeImeAdapter = nativeImeAdapter; 81 } 82 83 @Override run()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 120 private long mNativeImeAdapterAndroid; 121 private InputMethodManagerWrapper mInputMethodManagerWrapper; 122 private AdapterInputConnection mInputConnection; 123 private final ImeAdapterDelegate mViewEmbedder; 124 private final Handler mHandler; 125 private DelayedDismissInput mDismissInput = null; 126 private int mTextInputType; 127 128 @VisibleForTesting 129 boolean mIsShowWithoutHideOutstanding = false; 130 131 /** 132 * @param wrapper InputMethodManagerWrapper that should receive all the call directed to 133 * InputMethodManager. 134 * @param embedder The view that is used for callbacks from ImeAdapter. 135 */ ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder)136 public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder) { 137 mInputMethodManagerWrapper = wrapper; 138 mViewEmbedder = embedder; 139 mHandler = new Handler(); 140 } 141 142 /** 143 * Default factory for AdapterInputConnection classes. 144 */ 145 public static class AdapterInputConnectionFactory { get(View view, ImeAdapter imeAdapter, Editable editable, EditorInfo outAttrs)146 public AdapterInputConnection get(View view, ImeAdapter imeAdapter, 147 Editable editable, EditorInfo outAttrs) { 148 return new AdapterInputConnection(view, imeAdapter, editable, outAttrs); 149 } 150 } 151 152 /** 153 * Overrides the InputMethodManagerWrapper that ImeAdapter uses to make calls to 154 * InputMethodManager. 155 * @param immw InputMethodManagerWrapper that should be used to call InputMethodManager. 156 */ 157 @VisibleForTesting setInputMethodManagerWrapper(InputMethodManagerWrapper immw)158 public void setInputMethodManagerWrapper(InputMethodManagerWrapper immw) { 159 mInputMethodManagerWrapper = immw; 160 } 161 162 /** 163 * Should be only used by AdapterInputConnection. 164 * @return InputMethodManagerWrapper that should receive all the calls directed to 165 * InputMethodManager. 166 */ getInputMethodManagerWrapper()167 InputMethodManagerWrapper getInputMethodManagerWrapper() { 168 return mInputMethodManagerWrapper; 169 } 170 171 /** 172 * Set the current active InputConnection when a new InputConnection is constructed. 173 * @param inputConnection The input connection that is currently used with IME. 174 */ setInputConnection(AdapterInputConnection inputConnection)175 void setInputConnection(AdapterInputConnection inputConnection) { 176 mInputConnection = inputConnection; 177 } 178 179 /** 180 * Should be only used by AdapterInputConnection. 181 * @return The input type of currently focused element. 182 */ getTextInputType()183 int getTextInputType() { 184 return mTextInputType; 185 } 186 187 /** 188 * @return Constant representing that a focused node is not an input field. 189 */ getTextInputTypeNone()190 public static int getTextInputTypeNone() { 191 return sTextInputTypeNone; 192 } 193 getModifiers(int metaState)194 private static int getModifiers(int metaState) { 195 int modifiers = 0; 196 if ((metaState & KeyEvent.META_SHIFT_ON) != 0) { 197 modifiers |= sModifierShift; 198 } 199 if ((metaState & KeyEvent.META_ALT_ON) != 0) { 200 modifiers |= sModifierAlt; 201 } 202 if ((metaState & KeyEvent.META_CTRL_ON) != 0) { 203 modifiers |= sModifierCtrl; 204 } 205 if ((metaState & KeyEvent.META_CAPS_LOCK_ON) != 0) { 206 modifiers |= sModifierCapsLockOn; 207 } 208 if ((metaState & KeyEvent.META_NUM_LOCK_ON) != 0) { 209 modifiers |= sModifierNumLockOn; 210 } 211 return modifiers; 212 } 213 214 /** 215 * Shows or hides the keyboard based on passed parameters. 216 * @param nativeImeAdapter Pointer to the ImeAdapterAndroid object that is sending the update. 217 * @param textInputType Text input type for the currently focused field in renderer. 218 * @param showIfNeeded Whether the keyboard should be shown if it is currently hidden. 219 */ updateKeyboardVisibility(long nativeImeAdapter, int textInputType, boolean showIfNeeded)220 public void updateKeyboardVisibility(long nativeImeAdapter, int textInputType, 221 boolean showIfNeeded) { 222 mHandler.removeCallbacks(mDismissInput); 223 224 // If current input type is none and showIfNeeded is false, IME should not be shown 225 // and input type should remain as none. 226 if (mTextInputType == sTextInputTypeNone && !showIfNeeded) { 227 return; 228 } 229 230 if (mNativeImeAdapterAndroid != nativeImeAdapter || mTextInputType != textInputType) { 231 // Set a delayed task to perform unfocus. This avoids hiding the keyboard when tabbing 232 // through text inputs or when JS rapidly changes focus to another text element. 233 if (textInputType == sTextInputTypeNone) { 234 mDismissInput = new DelayedDismissInput(nativeImeAdapter); 235 mHandler.postDelayed(mDismissInput, INPUT_DISMISS_DELAY); 236 return; 237 } 238 239 attach(nativeImeAdapter, textInputType); 240 241 mInputMethodManagerWrapper.restartInput(mViewEmbedder.getAttachedView()); 242 if (showIfNeeded) { 243 showKeyboard(); 244 } 245 } else if (hasInputType() && showIfNeeded) { 246 showKeyboard(); 247 } 248 } 249 attach(long nativeImeAdapter, int textInputType)250 public void attach(long nativeImeAdapter, int textInputType) { 251 if (mNativeImeAdapterAndroid != 0) { 252 nativeResetImeAdapter(mNativeImeAdapterAndroid); 253 } 254 mNativeImeAdapterAndroid = nativeImeAdapter; 255 mTextInputType = textInputType; 256 if (nativeImeAdapter != 0) { 257 nativeAttachImeAdapter(mNativeImeAdapterAndroid); 258 } 259 if (mTextInputType == sTextInputTypeNone) { 260 dismissInput(false); 261 } 262 } 263 264 /** 265 * Attaches the imeAdapter to its native counterpart. This is needed to start forwarding 266 * keyboard events to WebKit. 267 * @param nativeImeAdapter The pointer to the native ImeAdapter object. 268 */ attach(long nativeImeAdapter)269 public void attach(long nativeImeAdapter) { 270 attach(nativeImeAdapter, sTextInputTypeNone); 271 } 272 showKeyboard()273 private void showKeyboard() { 274 mIsShowWithoutHideOutstanding = true; 275 mInputMethodManagerWrapper.showSoftInput(mViewEmbedder.getAttachedView(), 0, 276 mViewEmbedder.getNewShowKeyboardReceiver()); 277 } 278 dismissInput(boolean unzoomIfNeeded)279 private void dismissInput(boolean unzoomIfNeeded) { 280 mIsShowWithoutHideOutstanding = false; 281 View view = mViewEmbedder.getAttachedView(); 282 if (mInputMethodManagerWrapper.isActive(view)) { 283 mInputMethodManagerWrapper.hideSoftInputFromWindow(view.getWindowToken(), 0, 284 unzoomIfNeeded ? mViewEmbedder.getNewShowKeyboardReceiver() : null); 285 } 286 mViewEmbedder.onDismissInput(); 287 } 288 hasInputType()289 private boolean hasInputType() { 290 return mTextInputType != sTextInputTypeNone; 291 } 292 isTextInputType(int type)293 private static boolean isTextInputType(int type) { 294 return type != sTextInputTypeNone && !InputDialogContainer.isDialogInputType(type); 295 } 296 hasTextInputType()297 public boolean hasTextInputType() { 298 return isTextInputType(mTextInputType); 299 } 300 301 /** 302 * @return true if the selected text is of password. 303 */ isSelectionPassword()304 public boolean isSelectionPassword() { 305 return mTextInputType == sTextInputTypePassword; 306 } 307 dispatchKeyEvent(KeyEvent event)308 public boolean dispatchKeyEvent(KeyEvent event) { 309 return translateAndSendNativeEvents(event); 310 } 311 shouldSendKeyEventWithKeyCode(String text)312 private int shouldSendKeyEventWithKeyCode(String text) { 313 if (text.length() != 1) return COMPOSITION_KEY_CODE; 314 315 if (text.equals("\n")) return KeyEvent.KEYCODE_ENTER; 316 else if (text.equals("\t")) return KeyEvent.KEYCODE_TAB; 317 else return COMPOSITION_KEY_CODE; 318 } 319 sendKeyEventWithKeyCode(int keyCode, int flags)320 void sendKeyEventWithKeyCode(int keyCode, int flags) { 321 long eventTime = SystemClock.uptimeMillis(); 322 translateAndSendNativeEvents(new KeyEvent(eventTime, eventTime, 323 KeyEvent.ACTION_DOWN, keyCode, 0, 0, 324 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 325 flags)); 326 translateAndSendNativeEvents(new KeyEvent(SystemClock.uptimeMillis(), eventTime, 327 KeyEvent.ACTION_UP, keyCode, 0, 0, 328 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 329 flags)); 330 } 331 332 // Calls from Java to C++ 333 checkCompositionQueueAndCallNative(CharSequence text, int newCursorPosition, boolean isCommit)334 boolean checkCompositionQueueAndCallNative(CharSequence text, int newCursorPosition, 335 boolean isCommit) { 336 if (mNativeImeAdapterAndroid == 0) return false; 337 String textStr = text.toString(); 338 339 // Committing an empty string finishes the current composition. 340 boolean isFinish = textStr.isEmpty(); 341 mViewEmbedder.onImeEvent(isFinish); 342 int keyCode = shouldSendKeyEventWithKeyCode(textStr); 343 long timeStampMs = SystemClock.uptimeMillis(); 344 345 if (keyCode != COMPOSITION_KEY_CODE) { 346 sendKeyEventWithKeyCode(keyCode, 347 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE); 348 } else { 349 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawKeyDown, 350 timeStampMs, keyCode, 0); 351 if (isCommit) { 352 nativeCommitText(mNativeImeAdapterAndroid, textStr); 353 } else { 354 nativeSetComposingText(mNativeImeAdapterAndroid, text, textStr, newCursorPosition); 355 } 356 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyUp, 357 timeStampMs, keyCode, 0); 358 } 359 360 return true; 361 } 362 finishComposingText()363 void finishComposingText() { 364 if (mNativeImeAdapterAndroid == 0) return; 365 nativeFinishComposingText(mNativeImeAdapterAndroid); 366 } 367 translateAndSendNativeEvents(KeyEvent event)368 boolean translateAndSendNativeEvents(KeyEvent event) { 369 if (mNativeImeAdapterAndroid == 0) return false; 370 371 int action = event.getAction(); 372 if (action != KeyEvent.ACTION_DOWN && 373 action != KeyEvent.ACTION_UP) { 374 // action == KeyEvent.ACTION_MULTIPLE 375 // TODO(bulach): confirm the actual behavior. Apparently: 376 // If event.getKeyCode() == KEYCODE_UNKNOWN, we can send a 377 // composition key down (229) followed by a commit text with the 378 // string from event.getUnicodeChars(). 379 // Otherwise, we'd need to send an event with a 380 // WebInputEvent::IsAutoRepeat modifier. We also need to verify when 381 // we receive ACTION_MULTIPLE: we may receive it after an ACTION_DOWN, 382 // and if that's the case, we'll need to review when to send the Char 383 // event. 384 return false; 385 } 386 mViewEmbedder.onImeEvent(false); 387 return nativeSendKeyEvent(mNativeImeAdapterAndroid, event, event.getAction(), 388 getModifiers(event.getMetaState()), event.getEventTime(), event.getKeyCode(), 389 /*isSystemKey=*/false, event.getUnicodeChar()); 390 } 391 sendSyntheticKeyEvent(int eventType, long timestampMs, int keyCode, int unicodeChar)392 boolean sendSyntheticKeyEvent(int eventType, long timestampMs, int keyCode, int unicodeChar) { 393 if (mNativeImeAdapterAndroid == 0) return false; 394 395 nativeSendSyntheticKeyEvent( 396 mNativeImeAdapterAndroid, eventType, timestampMs, keyCode, unicodeChar); 397 return true; 398 } 399 400 /** 401 * Send a request to the native counterpart to delete a given range of characters. 402 * @param beforeLength Number of characters to extend the selection by before the existing 403 * selection. 404 * @param afterLength Number of characters to extend the selection by after the existing 405 * selection. 406 * @return Whether the native counterpart of ImeAdapter received the call. 407 */ deleteSurroundingText(int beforeLength, int afterLength)408 boolean deleteSurroundingText(int beforeLength, int afterLength) { 409 if (mNativeImeAdapterAndroid == 0) return false; 410 nativeDeleteSurroundingText(mNativeImeAdapterAndroid, beforeLength, afterLength); 411 return true; 412 } 413 414 /** 415 * Send a request to the native counterpart to set the selection to given range. 416 * @param start Selection start index. 417 * @param end Selection end index. 418 * @return Whether the native counterpart of ImeAdapter received the call. 419 */ setEditableSelectionOffsets(int start, int end)420 boolean setEditableSelectionOffsets(int start, int end) { 421 if (mNativeImeAdapterAndroid == 0) return false; 422 nativeSetEditableSelectionOffsets(mNativeImeAdapterAndroid, start, end); 423 return true; 424 } 425 426 /** 427 * Send a request to the native counterpart to set compositing region to given indices. 428 * @param start The start of the composition. 429 * @param end The end of the composition. 430 * @return Whether the native counterpart of ImeAdapter received the call. 431 */ setComposingRegion(int start, int end)432 boolean setComposingRegion(int start, int end) { 433 if (mNativeImeAdapterAndroid == 0) return false; 434 nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end); 435 return true; 436 } 437 438 /** 439 * Send a request to the native counterpart to unselect text. 440 * @return Whether the native counterpart of ImeAdapter received the call. 441 */ unselect()442 public boolean unselect() { 443 if (mNativeImeAdapterAndroid == 0) return false; 444 nativeUnselect(mNativeImeAdapterAndroid); 445 return true; 446 } 447 448 /** 449 * Send a request to the native counterpart of ImeAdapter to select all the text. 450 * @return Whether the native counterpart of ImeAdapter received the call. 451 */ selectAll()452 public boolean selectAll() { 453 if (mNativeImeAdapterAndroid == 0) return false; 454 nativeSelectAll(mNativeImeAdapterAndroid); 455 return true; 456 } 457 458 /** 459 * Send a request to the native counterpart of ImeAdapter to cut the selected text. 460 * @return Whether the native counterpart of ImeAdapter received the call. 461 */ cut()462 public boolean cut() { 463 if (mNativeImeAdapterAndroid == 0) return false; 464 nativeCut(mNativeImeAdapterAndroid); 465 return true; 466 } 467 468 /** 469 * Send a request to the native counterpart of ImeAdapter to copy the selected text. 470 * @return Whether the native counterpart of ImeAdapter received the call. 471 */ copy()472 public boolean copy() { 473 if (mNativeImeAdapterAndroid == 0) return false; 474 nativeCopy(mNativeImeAdapterAndroid); 475 return true; 476 } 477 478 /** 479 * Send a request to the native counterpart of ImeAdapter to paste the text from the clipboard. 480 * @return Whether the native counterpart of ImeAdapter received the call. 481 */ paste()482 public boolean paste() { 483 if (mNativeImeAdapterAndroid == 0) return false; 484 nativePaste(mNativeImeAdapterAndroid); 485 return true; 486 } 487 488 // Calls from C++ to Java 489 490 @CalledByNative initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp, int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl, int modifierCapsLockOn, int modifierNumLockOn)491 private static void initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp, 492 int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl, 493 int modifierCapsLockOn, int modifierNumLockOn) { 494 sEventTypeRawKeyDown = eventTypeRawKeyDown; 495 sEventTypeKeyUp = eventTypeKeyUp; 496 sEventTypeChar = eventTypeChar; 497 sModifierShift = modifierShift; 498 sModifierAlt = modifierAlt; 499 sModifierCtrl = modifierCtrl; 500 sModifierCapsLockOn = modifierCapsLockOn; 501 sModifierNumLockOn = modifierNumLockOn; 502 } 503 504 @CalledByNative initializeTextInputTypes(int textInputTypeNone, int textInputTypeText, int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch, int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel, int textInputTypeNumber, int textInputTypeContentEditable)505 private static void initializeTextInputTypes(int textInputTypeNone, int textInputTypeText, 506 int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch, 507 int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel, 508 int textInputTypeNumber, int textInputTypeContentEditable) { 509 sTextInputTypeNone = textInputTypeNone; 510 sTextInputTypeText = textInputTypeText; 511 sTextInputTypeTextArea = textInputTypeTextArea; 512 sTextInputTypePassword = textInputTypePassword; 513 sTextInputTypeSearch = textInputTypeSearch; 514 sTextInputTypeUrl = textInputTypeUrl; 515 sTextInputTypeEmail = textInputTypeEmail; 516 sTextInputTypeTel = textInputTypeTel; 517 sTextInputTypeNumber = textInputTypeNumber; 518 sTextInputTypeContentEditable = textInputTypeContentEditable; 519 } 520 521 @CalledByNative focusedNodeChanged(boolean isEditable)522 private void focusedNodeChanged(boolean isEditable) { 523 if (mInputConnection != null && isEditable) mInputConnection.restartInput(); 524 } 525 526 @CalledByNative populateUnderlinesFromSpans(CharSequence text, long underlines)527 private void populateUnderlinesFromSpans(CharSequence text, long underlines) { 528 if (!(text instanceof SpannableString)) return; 529 530 SpannableString spannableString = ((SpannableString) text); 531 CharacterStyle spans[] = 532 spannableString.getSpans(0, text.length(), CharacterStyle.class); 533 for (CharacterStyle span : spans) { 534 if (span instanceof BackgroundColorSpan) { 535 nativeAppendBackgroundColorSpan(underlines, spannableString.getSpanStart(span), 536 spannableString.getSpanEnd(span), 537 ((BackgroundColorSpan) span).getBackgroundColor()); 538 } else if (span instanceof UnderlineSpan) { 539 nativeAppendUnderlineSpan(underlines, spannableString.getSpanStart(span), 540 spannableString.getSpanEnd(span)); 541 } 542 } 543 } 544 545 @CalledByNative cancelComposition()546 private void cancelComposition() { 547 if (mInputConnection != null) mInputConnection.restartInput(); 548 } 549 550 @CalledByNative detach()551 void detach() { 552 if (mDismissInput != null) mHandler.removeCallbacks(mDismissInput); 553 mNativeImeAdapterAndroid = 0; 554 mTextInputType = 0; 555 } 556 nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid, int eventType, long timestampMs, int keyCode, int unicodeChar)557 private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid, 558 int eventType, long timestampMs, int keyCode, int unicodeChar); 559 nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event, int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey, int unicodeChar)560 private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event, 561 int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey, 562 int unicodeChar); 563 nativeAppendUnderlineSpan(long underlinePtr, int start, int end)564 private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end); 565 nativeAppendBackgroundColorSpan(long underlinePtr, int start, int end, int backgroundColor)566 private static native void nativeAppendBackgroundColorSpan(long underlinePtr, int start, 567 int end, int backgroundColor); 568 nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text, String textStr, int newCursorPosition)569 private native void nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text, 570 String textStr, int newCursorPosition); 571 nativeCommitText(long nativeImeAdapterAndroid, String textStr)572 private native void nativeCommitText(long nativeImeAdapterAndroid, String textStr); 573 nativeFinishComposingText(long nativeImeAdapterAndroid)574 private native void nativeFinishComposingText(long nativeImeAdapterAndroid); 575 nativeAttachImeAdapter(long nativeImeAdapterAndroid)576 private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid); 577 nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid, int start, int end)578 private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid, 579 int start, int end); 580 nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end)581 private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end); 582 nativeDeleteSurroundingText(long nativeImeAdapterAndroid, int before, int after)583 private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid, 584 int before, int after); 585 nativeUnselect(long nativeImeAdapterAndroid)586 private native void nativeUnselect(long nativeImeAdapterAndroid); nativeSelectAll(long nativeImeAdapterAndroid)587 private native void nativeSelectAll(long nativeImeAdapterAndroid); nativeCut(long nativeImeAdapterAndroid)588 private native void nativeCut(long nativeImeAdapterAndroid); nativeCopy(long nativeImeAdapterAndroid)589 private native void nativeCopy(long nativeImeAdapterAndroid); nativePaste(long nativeImeAdapterAndroid)590 private native void nativePaste(long nativeImeAdapterAndroid); nativeResetImeAdapter(long nativeImeAdapterAndroid)591 private native void nativeResetImeAdapter(long nativeImeAdapterAndroid); 592 } 593