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 org.chromium.base.CalledByNative; 21 import org.chromium.base.JNINamespace; 22 import org.chromium.base.VisibleForTesting; 23 import org.chromium.ui.picker.InputDialogContainer; 24 25 import 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") 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 * 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