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

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

Issue 759033002: Handle accent keys from physical keyboards. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: addressed review comments and moved common dead-key code to public static method Created 6 years 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
« no previous file with comments | « no previous file | content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 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.SystemClock; 7 import android.os.SystemClock;
8 import android.text.Editable; 8 import android.text.Editable;
9 import android.text.InputType; 9 import android.text.InputType;
10 import android.text.Selection; 10 import android.text.Selection;
11 import android.text.TextUtils; 11 import android.text.TextUtils;
12 import android.util.Log; 12 import android.util.Log;
13 import android.view.KeyCharacterMap;
13 import android.view.KeyEvent; 14 import android.view.KeyEvent;
14 import android.view.View; 15 import android.view.View;
15 import android.view.inputmethod.BaseInputConnection; 16 import android.view.inputmethod.BaseInputConnection;
16 import android.view.inputmethod.EditorInfo; 17 import android.view.inputmethod.EditorInfo;
17 import android.view.inputmethod.ExtractedText; 18 import android.view.inputmethod.ExtractedText;
18 import android.view.inputmethod.ExtractedTextRequest; 19 import android.view.inputmethod.ExtractedTextRequest;
19 20
20 import org.chromium.base.VisibleForTesting; 21 import org.chromium.base.VisibleForTesting;
21 22
22 /** 23 /**
23 * InputConnection is created by ContentView.onCreateInputConnection. 24 * InputConnection is created by ContentView.onCreateInputConnection.
24 * It then adapts android's IME to chrome's RenderWidgetHostView using the 25 * It then adapts android's IME to chrome's RenderWidgetHostView using the
25 * native ImeAdapterAndroid via the class ImeAdapter. 26 * native ImeAdapterAndroid via the class ImeAdapter.
26 */ 27 */
27 public class AdapterInputConnection extends BaseInputConnection { 28 public class AdapterInputConnection extends BaseInputConnection {
28 private static final String TAG = "AdapterInputConnection"; 29 private static final String TAG = "AdapterInputConnection";
29 private static final boolean DEBUG = false; 30 private static final boolean DEBUG = false;
31 private static final int NO_ACCENT = 0;
30 /** 32 /**
31 * Selection value should be -1 if not known. See EditorInfo.java for detail s. 33 * Selection value should be -1 if not known. See EditorInfo.java for detail s.
32 */ 34 */
33 public static final int INVALID_SELECTION = -1; 35 public static final int INVALID_SELECTION = -1;
34 public static final int INVALID_COMPOSITION = -1; 36 public static final int INVALID_COMPOSITION = -1;
35 37
36 private final View mInternalView; 38 private final View mInternalView;
37 private final ImeAdapter mImeAdapter; 39 private final ImeAdapter mImeAdapter;
38 private final Editable mEditable; 40 private final Editable mEditable;
39 41
40 private boolean mSingleLine; 42 private boolean mSingleLine;
41 private int mNumNestedBatchEdits = 0; 43 private int mNumNestedBatchEdits = 0;
44 private int mPendingAccent;
42 45
43 private int mLastUpdateSelectionStart = INVALID_SELECTION; 46 private int mLastUpdateSelectionStart = INVALID_SELECTION;
44 private int mLastUpdateSelectionEnd = INVALID_SELECTION; 47 private int mLastUpdateSelectionEnd = INVALID_SELECTION;
45 private int mLastUpdateCompositionStart = INVALID_COMPOSITION; 48 private int mLastUpdateCompositionStart = INVALID_COMPOSITION;
46 private int mLastUpdateCompositionEnd = INVALID_COMPOSITION; 49 private int mLastUpdateCompositionEnd = INVALID_COMPOSITION;
47 50
48 @VisibleForTesting 51 @VisibleForTesting
49 AdapterInputConnection(View view, ImeAdapter imeAdapter, Editable editable, 52 AdapterInputConnection(View view, ImeAdapter imeAdapter, Editable editable,
50 EditorInfo outAttrs) { 53 EditorInfo outAttrs) {
51 super(view, true); 54 super(view, true);
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
118 } 121 }
119 outAttrs.initialSelStart = Selection.getSelectionStart(mEditable); 122 outAttrs.initialSelStart = Selection.getSelectionStart(mEditable);
120 outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable); 123 outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable);
121 mLastUpdateSelectionStart = Selection.getSelectionStart(mEditable); 124 mLastUpdateSelectionStart = Selection.getSelectionStart(mEditable);
122 mLastUpdateSelectionEnd = Selection.getSelectionEnd(mEditable); 125 mLastUpdateSelectionEnd = Selection.getSelectionEnd(mEditable);
123 126
124 Selection.setSelection(mEditable, outAttrs.initialSelStart, outAttrs.ini tialSelEnd); 127 Selection.setSelection(mEditable, outAttrs.initialSelStart, outAttrs.ini tialSelEnd);
125 updateSelectionIfRequired(); 128 updateSelectionIfRequired();
126 } 129 }
127 130
131 public static int maybeAddAccentToCharacter(int accentChar, int unicodeChar) {
132 if (accentChar != NO_ACCENT) {
133 int combinedChar = KeyEvent.getDeadChar(accentChar, unicodeChar);
134 if (combinedChar != 0) {
135 return combinedChar;
136 }
137 }
138 return unicodeChar;
139 }
140
128 /** 141 /**
129 * Updates the AdapterInputConnection's internal representation of the text being edited and 142 * Updates the AdapterInputConnection's internal representation of the text being edited and
130 * its selection and composition properties. The resulting Editable is acces sible through the 143 * its selection and composition properties. The resulting Editable is acces sible through the
131 * getEditable() method. If the text has not changed, this also calls update Selection on the 144 * getEditable() method. If the text has not changed, this also calls update Selection on the
132 * InputMethodManager. 145 * InputMethodManager.
133 * 146 *
134 * @param text The String contents of the field being edited. 147 * @param text The String contents of the field being edited.
135 * @param selectionStart The character offset of the selection start, or the caret position if 148 * @param selectionStart The character offset of the selection start, or the caret position if
136 * there is no selection. 149 * there is no selection.
137 * @param selectionEnd The character offset of the selection end, or the car et position if there 150 * @param selectionEnd The character offset of the selection end, or the car et position if there
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
208 + compositionStart + " " + compositionEnd + "]"); 221 + compositionStart + " " + compositionEnd + "]");
209 } 222 }
210 // updateSelection should be called every time the selection or composit ion changes 223 // updateSelection should be called every time the selection or composit ion changes
211 // if it happens not within a batch edit, or at the end of each top leve l batch edit. 224 // if it happens not within a batch edit, or at the end of each top leve l batch edit.
212 getInputMethodManagerWrapper().updateSelection(mInternalView, 225 getInputMethodManagerWrapper().updateSelection(mInternalView,
213 selectionStart, selectionEnd, compositionStart, compositionEnd); 226 selectionStart, selectionEnd, compositionStart, compositionEnd);
214 mLastUpdateSelectionStart = selectionStart; 227 mLastUpdateSelectionStart = selectionStart;
215 mLastUpdateSelectionEnd = selectionEnd; 228 mLastUpdateSelectionEnd = selectionEnd;
216 mLastUpdateCompositionStart = compositionStart; 229 mLastUpdateCompositionStart = compositionStart;
217 mLastUpdateCompositionEnd = compositionEnd; 230 mLastUpdateCompositionEnd = compositionEnd;
231 // Change in selection or cursor position invalidates any pending accent .
232 mPendingAccent = NO_ACCENT;
218 } 233 }
219 234
220 /** 235 /**
221 * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int) 236 * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int)
222 */ 237 */
223 @Override 238 @Override
224 public boolean setComposingText(CharSequence text, int newCursorPosition) { 239 public boolean setComposingText(CharSequence text, int newCursorPosition) {
225 if (DEBUG) Log.w(TAG, "setComposingText [" + text + "] [" + newCursorPos ition + "]"); 240 if (DEBUG) Log.w(TAG, "setComposingText [" + text + "] [" + newCursorPos ition + "]");
226 if (maybePerformEmptyCompositionWorkaround(text)) return true; 241 if (maybePerformEmptyCompositionWorkaround(text)) return true;
227 super.setComposingText(text, newCursorPosition); 242 super.setComposingText(text, newCursorPosition);
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after
361 } 376 }
362 377
363 /** 378 /**
364 * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) 379 * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent)
365 */ 380 */
366 @Override 381 @Override
367 public boolean sendKeyEvent(KeyEvent event) { 382 public boolean sendKeyEvent(KeyEvent event) {
368 if (DEBUG) { 383 if (DEBUG) {
369 Log.w(TAG, "sendKeyEvent [" + event.getAction() + "] [" + event.getK eyCode() + "]"); 384 Log.w(TAG, "sendKeyEvent [" + event.getAction() + "] [" + event.getK eyCode() + "]");
370 } 385 }
386
387 // Short-cut modifier keys so they're not affected by accents.
388 if (KeyEvent.isModifierKey(event.getKeyCode())) {
389 return mImeAdapter.translateAndSendNativeEvents(event, NO_ACCENT);
390 }
391
392 int unicodeChar = event.getUnicodeChar();
393
371 // If this is a key-up, and backspace/del or if the key has a character representation, 394 // If this is a key-up, and backspace/del or if the key has a character representation,
372 // need to update the underlying Editable (i.e. the local representation of the text 395 // need to update the underlying Editable (i.e. the local representation of the text
373 // being edited). 396 // being edited).
374 if (event.getAction() == KeyEvent.ACTION_UP) { 397 if (event.getAction() == KeyEvent.ACTION_UP) {
375 if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { 398 if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
376 deleteSurroundingText(1, 0); 399 deleteSurroundingText(1, 0);
377 return true; 400 return true;
378 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { 401 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
379 deleteSurroundingText(0, 1); 402 deleteSurroundingText(0, 1);
380 return true; 403 return true;
381 } else { 404 } else if (unicodeChar != 0) {
382 int unicodeChar = event.getUnicodeChar(); 405 int selectionStart = Selection.getSelectionStart(mEditable);
383 if (unicodeChar != 0) { 406 int selectionEnd = Selection.getSelectionEnd(mEditable);
384 int selectionStart = Selection.getSelectionStart(mEditable); 407 if (selectionStart > selectionEnd) {
385 int selectionEnd = Selection.getSelectionEnd(mEditable); 408 int temp = selectionStart;
386 if (selectionStart > selectionEnd) { 409 selectionStart = selectionEnd;
387 int temp = selectionStart; 410 selectionEnd = temp;
388 selectionStart = selectionEnd;
389 selectionEnd = temp;
390 }
391 mEditable.replace(selectionStart, selectionEnd,
392 Character.toString((char) unicodeChar));
393 } 411 }
412 int combinedChar = maybeAddAccentToCharacter(mPendingAccent, uni codeChar);
413 mEditable.replace(selectionStart, selectionEnd,
414 Character.toString((char) combinedChar));
394 } 415 }
395 } else if (event.getAction() == KeyEvent.ACTION_DOWN) { 416 } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
396 // TODO(aurimas): remove this workaround when crbug.com/278584 is fi xed. 417 // TODO(aurimas): remove this workaround when crbug.com/278584 is fi xed.
397 if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { 418 if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
398 beginBatchEdit(); 419 beginBatchEdit();
399 finishComposingText(); 420 finishComposingText();
400 mImeAdapter.translateAndSendNativeEvents(event); 421 mImeAdapter.translateAndSendNativeEvents(event, 0);
401 endBatchEdit(); 422 endBatchEdit();
402 return true; 423 return true;
403 } else if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { 424 } else if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
404 return true; 425 return true;
405 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { 426 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) {
406 return true; 427 return true;
407 } 428 }
408 } 429 }
409 mImeAdapter.translateAndSendNativeEvents(event); 430 mImeAdapter.translateAndSendNativeEvents(event, mPendingAccent);
431
432 // Physical keyboards also have their events come through here though no t
433 // by BaseInputConnection. In order to support "accent" key sequences
434 // such as "~n" or "^o" we have to record that one has been pressed
435 // and, if an accentable letter follows, delete the accent glyph and
436 // insert the composed character.
437
438 // Copy class variable to local because class version may get indirectly
439 // cleared by the deleteSurroundingText() call below.
440 int pendingAccent = mPendingAccent;
441
442 if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) != 0) {
443 pendingAccent = unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK;
444 } else if (pendingAccent != NO_ACCENT) {
445 if (event.getAction() == KeyEvent.ACTION_DOWN) {
446 int combined = KeyEvent.getDeadChar(pendingAccent, unicodeChar);
447 if (combined != 0) {
448 // Previous accent combines with new character to create
449 // a new accented character. First delete the displayed
450 // accent so it appears overwritten by the composition.
451 super.deleteSurroundingText(1, 0);
452 mImeAdapter.deleteSurroundingText(1, 0);
453 } else {
454 // Previous accent doesn't combine with this character
455 // so assume both are completely independent.
456 pendingAccent = NO_ACCENT;
457 }
458 }
459
460 if (event.getAction() == KeyEvent.ACTION_UP) {
461 // Forget accent after release of key being accented.
462 pendingAccent = NO_ACCENT;
463 }
464 }
465
466 mPendingAccent = pendingAccent;
410 return true; 467 return true;
411 } 468 }
412 469
413 /** 470 /**
414 * @see BaseInputConnection#finishComposingText() 471 * @see BaseInputConnection#finishComposingText()
415 */ 472 */
416 @Override 473 @Override
417 public boolean finishComposingText() { 474 public boolean finishComposingText() {
418 if (DEBUG) Log.w(TAG, "finishComposingText"); 475 if (DEBUG) Log.w(TAG, "finishComposingText");
419 if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable)) { 476 if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable)) {
(...skipping 21 matching lines...) Expand all
441 } 498 }
442 499
443 /** 500 /**
444 * Informs the InputMethodManager and InputMethodSession (i.e. the IME) that the text 501 * Informs the InputMethodManager and InputMethodSession (i.e. the IME) that the text
445 * state is no longer what the IME has and that it needs to be updated. 502 * state is no longer what the IME has and that it needs to be updated.
446 */ 503 */
447 void restartInput() { 504 void restartInput() {
448 if (DEBUG) Log.w(TAG, "restartInput"); 505 if (DEBUG) Log.w(TAG, "restartInput");
449 getInputMethodManagerWrapper().restartInput(mInternalView); 506 getInputMethodManagerWrapper().restartInput(mInternalView);
450 mNumNestedBatchEdits = 0; 507 mNumNestedBatchEdits = 0;
508 mPendingAccent = NO_ACCENT;
451 } 509 }
452 510
453 /** 511 /**
454 * @see BaseInputConnection#setComposingRegion(int, int) 512 * @see BaseInputConnection#setComposingRegion(int, int)
455 */ 513 */
456 @Override 514 @Override
457 public boolean setComposingRegion(int start, int end) { 515 public boolean setComposingRegion(int start, int end) {
458 if (DEBUG) Log.w(TAG, "setComposingRegion [" + start + " " + end + "]"); 516 if (DEBUG) Log.w(TAG, "setComposingRegion [" + start + " " + end + "]");
459 int textLength = mEditable.length(); 517 int textLength = mEditable.length();
460 int a = Math.min(start, end); 518 int a = Math.min(start, end);
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after
534 @VisibleForTesting 592 @VisibleForTesting
535 ImeState getImeStateForTesting() { 593 ImeState getImeStateForTesting() {
536 String text = mEditable.toString(); 594 String text = mEditable.toString();
537 int selectionStart = Selection.getSelectionStart(mEditable); 595 int selectionStart = Selection.getSelectionStart(mEditable);
538 int selectionEnd = Selection.getSelectionEnd(mEditable); 596 int selectionEnd = Selection.getSelectionEnd(mEditable);
539 int compositionStart = getComposingSpanStart(mEditable); 597 int compositionStart = getComposingSpanStart(mEditable);
540 int compositionEnd = getComposingSpanEnd(mEditable); 598 int compositionEnd = getComposingSpanEnd(mEditable);
541 return new ImeState(text, selectionStart, selectionEnd, compositionStart , compositionEnd); 599 return new ImeState(text, selectionStart, selectionEnd, compositionStart , compositionEnd);
542 } 600 }
543 } 601 }
OLDNEW
« no previous file with comments | « no previous file | content/public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698