OLD | NEW |
---|---|
1 // Copyright 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.content.browser.input; | 5 package org.chromium.content.browser.input; |
6 | 6 |
7 import android.os.Handler; | 7 import android.os.Handler; |
8 import android.os.ResultReceiver; | 8 import android.os.ResultReceiver; |
9 import android.os.SystemClock; | 9 import android.os.SystemClock; |
10 import android.text.Editable; | 10 import android.text.Editable; |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
109 static int sTextInputTypeUrl; | 109 static int sTextInputTypeUrl; |
110 static int sTextInputTypeEmail; | 110 static int sTextInputTypeEmail; |
111 static int sTextInputTypeTel; | 111 static int sTextInputTypeTel; |
112 static int sTextInputTypeNumber; | 112 static int sTextInputTypeNumber; |
113 static int sTextInputTypeContentEditable; | 113 static int sTextInputTypeContentEditable; |
114 static int sModifierShift; | 114 static int sModifierShift; |
115 static int sModifierAlt; | 115 static int sModifierAlt; |
116 static int sModifierCtrl; | 116 static int sModifierCtrl; |
117 static int sModifierCapsLockOn; | 117 static int sModifierCapsLockOn; |
118 static int sModifierNumLockOn; | 118 static int sModifierNumLockOn; |
119 static char[] sSingleCharArray = new char[1]; | |
120 static KeyCharacterMap sKeyCharacterMap; | |
119 | 121 |
120 private long mNativeImeAdapterAndroid; | 122 private long mNativeImeAdapterAndroid; |
121 private InputMethodManagerWrapper mInputMethodManagerWrapper; | 123 private InputMethodManagerWrapper mInputMethodManagerWrapper; |
122 private AdapterInputConnection mInputConnection; | 124 private AdapterInputConnection mInputConnection; |
123 private final ImeAdapterDelegate mViewEmbedder; | 125 private final ImeAdapterDelegate mViewEmbedder; |
124 private final Handler mHandler; | 126 private final Handler mHandler; |
125 private DelayedDismissInput mDismissInput = null; | 127 private DelayedDismissInput mDismissInput = null; |
126 private int mTextInputType; | 128 private int mTextInputType; |
129 private String mLastComposeText; | |
130 | |
131 @VisibleForTesting | |
132 int mLastSyntheticKeyCode; | |
127 | 133 |
128 @VisibleForTesting | 134 @VisibleForTesting |
129 boolean mIsShowWithoutHideOutstanding = false; | 135 boolean mIsShowWithoutHideOutstanding = false; |
130 | 136 |
131 /** | 137 /** |
132 * @param wrapper InputMethodManagerWrapper that should receive all the call directed to | 138 * @param wrapper InputMethodManagerWrapper that should receive all the call directed to |
133 * InputMethodManager. | 139 * InputMethodManager. |
134 * @param embedder The view that is used for callbacks from ImeAdapter. | 140 * @param embedder The view that is used for callbacks from ImeAdapter. |
135 */ | 141 */ |
136 public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embe dder) { | 142 public ImeAdapter(InputMethodManagerWrapper wrapper, ImeAdapterDelegate embe dder) { |
(...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
310 } | 316 } |
311 | 317 |
312 private int shouldSendKeyEventWithKeyCode(String text) { | 318 private int shouldSendKeyEventWithKeyCode(String text) { |
313 if (text.length() != 1) return COMPOSITION_KEY_CODE; | 319 if (text.length() != 1) return COMPOSITION_KEY_CODE; |
314 | 320 |
315 if (text.equals("\n")) return KeyEvent.KEYCODE_ENTER; | 321 if (text.equals("\n")) return KeyEvent.KEYCODE_ENTER; |
316 else if (text.equals("\t")) return KeyEvent.KEYCODE_TAB; | 322 else if (text.equals("\t")) return KeyEvent.KEYCODE_TAB; |
317 else return COMPOSITION_KEY_CODE; | 323 else return COMPOSITION_KEY_CODE; |
318 } | 324 } |
319 | 325 |
326 /** | |
327 * @return Android keycode for a single unicode character. | |
328 */ | |
329 private static int androidKeyCodeForCharacter(char chr) { | |
330 if (sKeyCharacterMap == null) { | |
331 sKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYB OARD); | |
332 } | |
333 sSingleCharArray[0] = chr; | |
334 KeyEvent[] events = sKeyCharacterMap.getEvents(sSingleCharArray); | |
jdduke (slow)
2014/07/23 16:59:15
Any idea what the relative cost of this query is?
bcwhite
2014/07/23 18:18:19
No. Added TODO to find out.
| |
335 if (events == null || events.length != 2) // One key-down event and one key-up event. | |
336 return KeyEvent.KEYCODE_UNKNOWN; | |
337 return events[0].getKeyCode(); | |
338 } | |
339 | |
340 @VisibleForTesting | |
341 public static int getTypedKeycodeGuess(String oldtext, String newtext) { | |
342 // Starting typing a new composition should add only a single character. Any composition | |
343 // beginning with text longer than that must come from something other t han typing so | |
344 // return 0. | |
345 if (oldtext == null) { | |
346 if (newtext.length() == 1) { | |
347 return androidKeyCodeForCharacter(newtext.charAt(0)); | |
348 } else { | |
349 return 0; | |
350 } | |
351 } | |
352 | |
353 // The content has grown in length: assume the last character is the key that caused it. | |
354 if (newtext.length() > oldtext.length() && newtext.startsWith(oldtext)) | |
355 return androidKeyCodeForCharacter(newtext.charAt(newtext.length() - 1)); | |
356 | |
357 // The content has shrunk in length: assume that backspace was pressed. | |
358 if (oldtext.length() > newtext.length() && oldtext.startsWith(newtext)) | |
359 return KeyEvent.KEYCODE_DEL; | |
360 | |
361 // The content is unchanged or has undergone a complex change (i.e. not a simple tail | |
362 // modification) so return an unknown key-code. | |
363 return 0; | |
364 } | |
365 | |
320 void sendKeyEventWithKeyCode(int keyCode, int flags) { | 366 void sendKeyEventWithKeyCode(int keyCode, int flags) { |
321 long eventTime = SystemClock.uptimeMillis(); | 367 long eventTime = SystemClock.uptimeMillis(); |
368 mLastSyntheticKeyCode = keyCode; | |
322 translateAndSendNativeEvents(new KeyEvent(eventTime, eventTime, | 369 translateAndSendNativeEvents(new KeyEvent(eventTime, eventTime, |
323 KeyEvent.ACTION_DOWN, keyCode, 0, 0, | 370 KeyEvent.ACTION_DOWN, keyCode, 0, 0, |
324 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, | 371 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, |
325 flags)); | 372 flags)); |
326 translateAndSendNativeEvents(new KeyEvent(SystemClock.uptimeMillis(), ev entTime, | 373 translateAndSendNativeEvents(new KeyEvent(SystemClock.uptimeMillis(), ev entTime, |
327 KeyEvent.ACTION_UP, keyCode, 0, 0, | 374 KeyEvent.ACTION_UP, keyCode, 0, 0, |
328 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, | 375 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, |
329 flags)); | 376 flags)); |
330 } | 377 } |
331 | 378 |
332 // Calls from Java to C++ | 379 // Calls from Java to C++ |
333 | 380 |
334 boolean checkCompositionQueueAndCallNative(CharSequence text, int newCursorP osition, | 381 boolean checkCompositionQueueAndCallNative(CharSequence text, int newCursorP osition, |
335 boolean isCommit) { | 382 boolean isCommit) { |
jdduke (slow)
2014/07/23 16:59:15
It would be fantastic if some of these more compli
bcwhite
2014/07/23 18:18:19
Done.
| |
336 if (mNativeImeAdapterAndroid == 0) return false; | 383 if (mNativeImeAdapterAndroid == 0) return false; |
337 String textStr = text.toString(); | 384 String textStr = text.toString(); |
338 | 385 |
339 // Committing an empty string finishes the current composition. | 386 // Committing an empty string finishes the current composition. |
340 boolean isFinish = textStr.isEmpty(); | 387 boolean isFinish = textStr.isEmpty(); |
341 mViewEmbedder.onImeEvent(isFinish); | 388 mViewEmbedder.onImeEvent(isFinish); |
342 int keyCode = shouldSendKeyEventWithKeyCode(textStr); | 389 int keyCode = shouldSendKeyEventWithKeyCode(textStr); |
343 long timeStampMs = SystemClock.uptimeMillis(); | 390 long timeStampMs = SystemClock.uptimeMillis(); |
344 | 391 |
345 if (keyCode != COMPOSITION_KEY_CODE) { | 392 if (keyCode != COMPOSITION_KEY_CODE) { |
346 sendKeyEventWithKeyCode(keyCode, | 393 sendKeyEventWithKeyCode(keyCode, |
347 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE) ; | 394 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE) ; |
348 } else { | 395 } else { |
396 keyCode = getTypedKeycodeGuess(mLastComposeText, textStr); | |
397 mLastComposeText = textStr; | |
398 mLastSyntheticKeyCode = keyCode; | |
399 | |
400 // When typing, there is no issue sending KeyDown and KeyUp events a round the | |
401 // composition event because those key events do nothing (other than call JS | |
402 // handlers). Typing does not cause changes outside of a KeyPress e vent which | |
403 // we don't call here. However, if the key-code is a control key su ch as | |
404 // KEYCODE_DEL then there never is an associated KeyPress event and the KeyDown | |
405 // event itself causes the action. The net result below is that the Renderer calls | |
406 // cancelComposition() and then Android starts anew with setComposin gRegion(). | |
407 // This stopping and restarting of composition could be a source of problems | |
408 // with 3rd party keyboards. | |
409 // | |
410 // An alternative is to *not* call nativeSetComposingText() in the n on-commit case | |
411 // below. This avoids the restart of composition described above bu t fails to send | |
412 // an update to the composition while in composition which, strictly speaking, | |
413 // does not match the spec. | |
414 // | |
415 // For now, the solution is to endure the restarting of composition and only dive | |
416 // into the alternate solution should there be problems in the field . --bcwhite | |
417 | |
349 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawK eyDown, | 418 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawK eyDown, |
350 timeStampMs, keyCode, 0); | 419 timeStampMs, keyCode, 0); |
420 | |
351 if (isCommit) { | 421 if (isCommit) { |
352 nativeCommitText(mNativeImeAdapterAndroid, textStr); | 422 nativeCommitText(mNativeImeAdapterAndroid, textStr); |
423 mLastComposeText = null; | |
353 } else { | 424 } else { |
354 nativeSetComposingText(mNativeImeAdapterAndroid, text, textStr, newCursorPosition); | 425 nativeSetComposingText(mNativeImeAdapterAndroid, text, textStr, newCursorPosition); |
355 } | 426 } |
427 | |
356 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyU p, | 428 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyU p, |
357 timeStampMs, keyCode, 0); | 429 timeStampMs, keyCode, 0); |
358 } | 430 } |
359 | 431 |
360 return true; | 432 return true; |
361 } | 433 } |
362 | 434 |
363 void finishComposingText() { | 435 void finishComposingText() { |
364 if (mNativeImeAdapterAndroid == 0) return; | 436 if (mNativeImeAdapterAndroid == 0) return; |
365 nativeFinishComposingText(mNativeImeAdapterAndroid); | 437 nativeFinishComposingText(mNativeImeAdapterAndroid); |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
418 * @param end Selection end index. | 490 * @param end Selection end index. |
419 * @return Whether the native counterpart of ImeAdapter received the call. | 491 * @return Whether the native counterpart of ImeAdapter received the call. |
420 */ | 492 */ |
421 boolean setEditableSelectionOffsets(int start, int end) { | 493 boolean setEditableSelectionOffsets(int start, int end) { |
422 if (mNativeImeAdapterAndroid == 0) return false; | 494 if (mNativeImeAdapterAndroid == 0) return false; |
423 nativeSetEditableSelectionOffsets(mNativeImeAdapterAndroid, start, end); | 495 nativeSetEditableSelectionOffsets(mNativeImeAdapterAndroid, start, end); |
424 return true; | 496 return true; |
425 } | 497 } |
426 | 498 |
427 /** | 499 /** |
428 * Send a request to the native counterpart to set compositing region to giv en indices. | 500 * Send a request to the native counterpart to set composing region to given indices. |
429 * @param start The start of the composition. | 501 * @param start The start of the composition. |
430 * @param end The end of the composition. | 502 * @param end The end of the composition. |
431 * @return Whether the native counterpart of ImeAdapter received the call. | 503 * @return Whether the native counterpart of ImeAdapter received the call. |
432 */ | 504 */ |
433 boolean setComposingRegion(int start, int end) { | 505 boolean setComposingRegion(CharSequence text, int start, int end) { |
434 if (mNativeImeAdapterAndroid == 0) return false; | 506 if (mNativeImeAdapterAndroid == 0) return false; |
435 nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end); | 507 nativeSetComposingRegion(mNativeImeAdapterAndroid, start, end); |
508 mLastComposeText = text != null ? text.toString() : null; | |
436 return true; | 509 return true; |
437 } | 510 } |
438 | 511 |
439 /** | 512 /** |
440 * Send a request to the native counterpart to unselect text. | 513 * Send a request to the native counterpart to unselect text. |
441 * @return Whether the native counterpart of ImeAdapter received the call. | 514 * @return Whether the native counterpart of ImeAdapter received the call. |
442 */ | 515 */ |
443 public boolean unselect() { | 516 public boolean unselect() { |
444 if (mNativeImeAdapterAndroid == 0) return false; | 517 if (mNativeImeAdapterAndroid == 0) return false; |
445 nativeUnselect(mNativeImeAdapterAndroid); | 518 nativeUnselect(mNativeImeAdapterAndroid); |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
539 } else if (span instanceof UnderlineSpan) { | 612 } else if (span instanceof UnderlineSpan) { |
540 nativeAppendUnderlineSpan(underlines, spannableString.getSpanSta rt(span), | 613 nativeAppendUnderlineSpan(underlines, spannableString.getSpanSta rt(span), |
541 spannableString.getSpanEnd(span)); | 614 spannableString.getSpanEnd(span)); |
542 } | 615 } |
543 } | 616 } |
544 } | 617 } |
545 | 618 |
546 @CalledByNative | 619 @CalledByNative |
547 private void cancelComposition() { | 620 private void cancelComposition() { |
548 if (mInputConnection != null) mInputConnection.restartInput(); | 621 if (mInputConnection != null) mInputConnection.restartInput(); |
622 mLastComposeText = null; | |
549 } | 623 } |
550 | 624 |
551 @CalledByNative | 625 @CalledByNative |
552 void detach() { | 626 void detach() { |
553 if (mDismissInput != null) mHandler.removeCallbacks(mDismissInput); | 627 if (mDismissInput != null) mHandler.removeCallbacks(mDismissInput); |
554 mNativeImeAdapterAndroid = 0; | 628 mNativeImeAdapterAndroid = 0; |
555 mTextInputType = 0; | 629 mTextInputType = 0; |
556 } | 630 } |
557 | 631 |
558 private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndr oid, | 632 private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndr oid, |
(...skipping 25 matching lines...) Expand all Loading... | |
584 private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid , | 658 private native void nativeDeleteSurroundingText(long nativeImeAdapterAndroid , |
585 int before, int after); | 659 int before, int after); |
586 | 660 |
587 private native void nativeUnselect(long nativeImeAdapterAndroid); | 661 private native void nativeUnselect(long nativeImeAdapterAndroid); |
588 private native void nativeSelectAll(long nativeImeAdapterAndroid); | 662 private native void nativeSelectAll(long nativeImeAdapterAndroid); |
589 private native void nativeCut(long nativeImeAdapterAndroid); | 663 private native void nativeCut(long nativeImeAdapterAndroid); |
590 private native void nativeCopy(long nativeImeAdapterAndroid); | 664 private native void nativeCopy(long nativeImeAdapterAndroid); |
591 private native void nativePaste(long nativeImeAdapterAndroid); | 665 private native void nativePaste(long nativeImeAdapterAndroid); |
592 private native void nativeResetImeAdapter(long nativeImeAdapterAndroid); | 666 private native void nativeResetImeAdapter(long nativeImeAdapterAndroid); |
593 } | 667 } |
OLD | NEW |