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