Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(18)

Side by Side Diff: content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java

Issue 373523002: Send correct key-codes when doing composition events instead of always 0. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: restored MaybePerformEmptyCompositionWorkaround patch Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698