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 */ onImeEvent()58 void onImeEvent(); 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 long mNativeImeAdapter; 78 DelayedDismissInput(long nativeImeAdapter)79 DelayedDismissInput(long nativeImeAdapter) { 80 mNativeImeAdapter = nativeImeAdapter; 81 } 82 83 // http://crbug.com/413744 detach()84 void detach() { 85 mNativeImeAdapter = 0; 86 } 87 88 @Override run()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 */ ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embedder)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 { get(View view, ImeAdapter imeAdapter, Editable editable, EditorInfo outAttrs)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 setInputMethodManagerWrapper(InputMethodManagerWrapper immw)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 */ getInputMethodManagerWrapper()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 */ setInputConnection(AdapterInputConnection inputConnection)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 */ getTextInputType()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 */ getTextInputFlags()213 int getTextInputFlags() { 214 return mTextInputFlags; 215 } 216 217 /** 218 * @return Constant representing that a focused node is not an input field. 219 */ getTextInputTypeNone()220 public static int getTextInputTypeNone() { 221 return sTextInputTypeNone; 222 } 223 getModifiers(int metaState)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 */ updateKeyboardVisibility(long nativeImeAdapter, int textInputType, int textInputFlags, boolean showIfNeeded)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 attach(long nativeImeAdapter, int textInputType, int textInputFlags)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 */ attach(long nativeImeAdapter)301 public void attach(long nativeImeAdapter) { 302 attach(nativeImeAdapter, sTextInputTypeNone, sTextInputFlagNone); 303 } 304 showKeyboard()305 private void showKeyboard() { 306 mIsShowWithoutHideOutstanding = true; 307 mInputMethodManagerWrapper.showSoftInput(mViewEmbedder.getAttachedView(), 0, 308 mViewEmbedder.getNewShowKeyboardReceiver()); 309 } 310 dismissInput(boolean unzoomIfNeeded)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 hasInputType()321 private boolean hasInputType() { 322 return mTextInputType != sTextInputTypeNone; 323 } 324 isTextInputType(int type)325 private static boolean isTextInputType(int type) { 326 return type != sTextInputTypeNone && !InputDialogContainer.isDialogInputType(type); 327 } 328 hasTextInputType()329 public boolean hasTextInputType() { 330 return isTextInputType(mTextInputType); 331 } 332 333 /** 334 * @return true if the selected text is of password. 335 */ isSelectionPassword()336 public boolean isSelectionPassword() { 337 return mTextInputType == sTextInputTypePassword; 338 } 339 dispatchKeyEvent(KeyEvent event)340 public boolean dispatchKeyEvent(KeyEvent event) { 341 return translateAndSendNativeEvents(event); 342 } 343 shouldSendKeyEventWithKeyCode(String text)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 */ androidKeyEventForCharacter(char chr)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 getTypedKeyEventGuess(String oldtext, String newtext)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 sendKeyEventWithKeyCode(int keyCode, int flags)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 checkCompositionQueueAndCallNative(CharSequence text, int newCursorPosition, boolean isCommit)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 finishComposingText()496 void finishComposingText() { 497 mLastComposeText = null; 498 if (mNativeImeAdapterAndroid == 0) return; 499 nativeFinishComposingText(mNativeImeAdapterAndroid); 500 } 501 translateAndSendNativeEvents(KeyEvent event)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 sendSyntheticKeyEvent(int eventType, long timestampMs, int keyCode, int modifiers, int unicodeChar)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 */ deleteSurroundingText(int beforeLength, int afterLength)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 */ setEditableSelectionOffsets(int start, int end)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 */ setComposingRegion(CharSequence text, int start, int end)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 */ unselect()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 */ selectAll()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 */ cut()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 */ copy()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 */ paste()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 initializeWebInputEvents(int eventTypeRawKeyDown, int eventTypeKeyUp, int eventTypeChar, int modifierShift, int modifierAlt, int modifierCtrl, int modifierCapsLockOn, int modifierNumLockOn)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 initializeTextInputTypes(int textInputTypeNone, int textInputTypeText, int textInputTypeTextArea, int textInputTypePassword, int textInputTypeSearch, int textInputTypeUrl, int textInputTypeEmail, int textInputTypeTel, int textInputTypeNumber, int textInputTypeContentEditable)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 initializeTextInputFlags( int textInputFlagAutocompleteOn, int textInputFlagAutocompleteOff, int textInputFlagAutocorrectOn, int textInputFlagAutocorrectOff, int textInputFlagSpellcheckOn, int textInputFlagSpellcheckOff)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 focusedNodeChanged(boolean isEditable)672 private void focusedNodeChanged(boolean isEditable) { 673 if (mInputConnection != null && isEditable) mInputConnection.restartInput(); 674 } 675 676 @CalledByNative populateUnderlinesFromSpans(CharSequence text, long underlines)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 cancelComposition()696 private void cancelComposition() { 697 if (mInputConnection != null) mInputConnection.restartInput(); 698 mLastComposeText = null; 699 } 700 701 @CalledByNative detach()702 void detach() { 703 if (mDismissInput != null) { 704 mHandler.removeCallbacks(mDismissInput); 705 mDismissInput.detach(); 706 } 707 mNativeImeAdapterAndroid = 0; 708 mTextInputType = 0; 709 } 710 nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid, int eventType, long timestampMs, int keyCode, int modifiers, int unicodeChar)711 private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid, 712 int eventType, long timestampMs, int keyCode, int modifiers, int unicodeChar); 713 nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event, int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey, int unicodeChar)714 private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event, 715 int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey, 716 int unicodeChar); 717 nativeAppendUnderlineSpan(long underlinePtr, int start, int end)718 private static native void nativeAppendUnderlineSpan(long underlinePtr, int start, int end); 719 nativeAppendBackgroundColorSpan(long underlinePtr, int start, int end, int backgroundColor)720 private static native void nativeAppendBackgroundColorSpan(long underlinePtr, int start, 721 int end, int backgroundColor); 722 nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text, String textStr, int newCursorPosition)723 private native void nativeSetComposingText(long nativeImeAdapterAndroid, CharSequence text, 724 String textStr, int newCursorPosition); 725 nativeCommitText(long nativeImeAdapterAndroid, String textStr)726 private native void nativeCommitText(long nativeImeAdapterAndroid, String textStr); 727 nativeFinishComposingText(long nativeImeAdapterAndroid)728 private native void nativeFinishComposingText(long nativeImeAdapterAndroid); 729 nativeAttachImeAdapter(long nativeImeAdapterAndroid)730 private native void nativeAttachImeAdapter(long nativeImeAdapterAndroid); 731 nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid, int start, int end)732 private native void nativeSetEditableSelectionOffsets(long nativeImeAdapterAndroid, 733 int start, int end); 734 nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end)735 private native void nativeSetComposingRegion(long nativeImeAdapterAndroid, int start, int end); 736 nativeDeleteSurroundingText(long nativeImeAdapterAndroid, int before, int after)737 private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid, 738 int before, int after); 739 nativeUnselect(long nativeImeAdapterAndroid)740 private native void nativeUnselect(long nativeImeAdapterAndroid); nativeSelectAll(long nativeImeAdapterAndroid)741 private native void nativeSelectAll(long nativeImeAdapterAndroid); nativeCut(long nativeImeAdapterAndroid)742 private native void nativeCut(long nativeImeAdapterAndroid); nativeCopy(long nativeImeAdapterAndroid)743 private native void nativeCopy(long nativeImeAdapterAndroid); nativePaste(long nativeImeAdapterAndroid)744 private native void nativePaste(long nativeImeAdapterAndroid); nativeResetImeAdapter(long nativeImeAdapterAndroid)745 private native void nativeResetImeAdapter(long nativeImeAdapterAndroid); 746 } 747