OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 // Some keys we just want to pass events straight through. This allows |
| 393 // proper "repeating key" behavior with physical keyboards. |
| 394 int eventKeyCode = event.getKeyCode(); |
| 395 if (eventKeyCode == KeyEvent.KEYCODE_DEL || eventKeyCode == KeyEvent.KEY
CODE_FORWARD_DEL) { |
| 396 mPendingAccent = 0; |
| 397 return mImeAdapter.translateAndSendNativeEvents(event, NO_ACCENT); |
| 398 } |
| 399 |
| 400 int unicodeChar = event.getUnicodeChar(); |
| 401 |
371 // If this is a key-up, and backspace/del or if the key has a character
representation, | 402 // 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 | 403 // need to update the underlying Editable (i.e. the local representation
of the text |
373 // being edited). | 404 // being edited). |
374 if (event.getAction() == KeyEvent.ACTION_UP) { | 405 if (event.getAction() == KeyEvent.ACTION_UP) { |
375 if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { | 406 if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { |
376 deleteSurroundingText(1, 0); | 407 deleteSurroundingText(1, 0); |
377 return true; | 408 return true; |
378 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { | 409 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { |
379 deleteSurroundingText(0, 1); | 410 deleteSurroundingText(0, 1); |
380 return true; | 411 return true; |
381 } else { | 412 } else if (unicodeChar != 0) { |
382 int unicodeChar = event.getUnicodeChar(); | 413 int selectionStart = Selection.getSelectionStart(mEditable); |
383 if (unicodeChar != 0) { | 414 int selectionEnd = Selection.getSelectionEnd(mEditable); |
384 int selectionStart = Selection.getSelectionStart(mEditable); | 415 if (selectionStart > selectionEnd) { |
385 int selectionEnd = Selection.getSelectionEnd(mEditable); | 416 int temp = selectionStart; |
386 if (selectionStart > selectionEnd) { | 417 selectionStart = selectionEnd; |
387 int temp = selectionStart; | 418 selectionEnd = temp; |
388 selectionStart = selectionEnd; | |
389 selectionEnd = temp; | |
390 } | |
391 mEditable.replace(selectionStart, selectionEnd, | |
392 Character.toString((char) unicodeChar)); | |
393 } | 419 } |
| 420 int combinedChar = maybeAddAccentToCharacter(mPendingAccent, uni
codeChar); |
| 421 mEditable.replace(selectionStart, selectionEnd, |
| 422 Character.toString((char) combinedChar)); |
394 } | 423 } |
395 } else if (event.getAction() == KeyEvent.ACTION_DOWN) { | 424 } else if (event.getAction() == KeyEvent.ACTION_DOWN) { |
396 // TODO(aurimas): remove this workaround when crbug.com/278584 is fi
xed. | 425 // TODO(aurimas): remove this workaround when crbug.com/278584 is fi
xed. |
397 if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { | 426 if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { |
398 beginBatchEdit(); | 427 beginBatchEdit(); |
399 finishComposingText(); | 428 finishComposingText(); |
400 mImeAdapter.translateAndSendNativeEvents(event); | 429 mImeAdapter.translateAndSendNativeEvents(event, 0); |
401 endBatchEdit(); | 430 endBatchEdit(); |
402 return true; | 431 return true; |
403 } else if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { | 432 } else if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { |
404 return true; | 433 return true; |
405 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { | 434 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { |
406 return true; | 435 return true; |
407 } | 436 } |
408 } | 437 } |
409 mImeAdapter.translateAndSendNativeEvents(event); | 438 |
| 439 // Physical keyboards also have their events come through here though no
t |
| 440 // by BaseInputConnection. In order to support "accent" key sequences |
| 441 // such as "~n" or "^o" we have to record that one has been pressed |
| 442 // and, if an accentable letter follows, delete the accent glyph and |
| 443 // insert the composed character. |
| 444 |
| 445 // Copy class variable to local because class version may get indirectly |
| 446 // cleared by the deleteSurroundingText() call below. |
| 447 int pendingAccent = mPendingAccent; |
| 448 int nextAccent = mPendingAccent; |
| 449 |
| 450 if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) != 0) { |
| 451 pendingAccent = NO_ACCENT; |
| 452 nextAccent = unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK; |
| 453 } else if (pendingAccent != NO_ACCENT) { |
| 454 if (event.getAction() == KeyEvent.ACTION_DOWN) { |
| 455 int combined = KeyEvent.getDeadChar(pendingAccent, unicodeChar); |
| 456 if (combined != 0) { |
| 457 // Previous accent combines with new character to create |
| 458 // a new accented character. First delete the displayed |
| 459 // accent so it appears overwritten by the composition. |
| 460 super.deleteSurroundingText(1, 0); |
| 461 mImeAdapter.deleteSurroundingText(1, 0); |
| 462 } else { |
| 463 // Previous accent doesn't combine with this character |
| 464 // so assume both are completely independent. |
| 465 pendingAccent = NO_ACCENT; |
| 466 nextAccent = NO_ACCENT; |
| 467 } |
| 468 } |
| 469 |
| 470 if (event.getAction() == KeyEvent.ACTION_UP) { |
| 471 // Forget accent after release of key being accented. |
| 472 nextAccent = NO_ACCENT; |
| 473 } |
| 474 } |
| 475 |
| 476 mImeAdapter.translateAndSendNativeEvents(event, pendingAccent); |
| 477 mPendingAccent = nextAccent; |
410 return true; | 478 return true; |
411 } | 479 } |
412 | 480 |
413 /** | 481 /** |
414 * @see BaseInputConnection#finishComposingText() | 482 * @see BaseInputConnection#finishComposingText() |
415 */ | 483 */ |
416 @Override | 484 @Override |
417 public boolean finishComposingText() { | 485 public boolean finishComposingText() { |
418 if (DEBUG) Log.w(TAG, "finishComposingText"); | 486 if (DEBUG) Log.w(TAG, "finishComposingText"); |
419 if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable))
{ | 487 if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable))
{ |
(...skipping 21 matching lines...) Expand all Loading... |
441 } | 509 } |
442 | 510 |
443 /** | 511 /** |
444 * Informs the InputMethodManager and InputMethodSession (i.e. the IME) that
the text | 512 * 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. | 513 * state is no longer what the IME has and that it needs to be updated. |
446 */ | 514 */ |
447 void restartInput() { | 515 void restartInput() { |
448 if (DEBUG) Log.w(TAG, "restartInput"); | 516 if (DEBUG) Log.w(TAG, "restartInput"); |
449 getInputMethodManagerWrapper().restartInput(mInternalView); | 517 getInputMethodManagerWrapper().restartInput(mInternalView); |
450 mNumNestedBatchEdits = 0; | 518 mNumNestedBatchEdits = 0; |
| 519 mPendingAccent = NO_ACCENT; |
451 } | 520 } |
452 | 521 |
453 /** | 522 /** |
454 * @see BaseInputConnection#setComposingRegion(int, int) | 523 * @see BaseInputConnection#setComposingRegion(int, int) |
455 */ | 524 */ |
456 @Override | 525 @Override |
457 public boolean setComposingRegion(int start, int end) { | 526 public boolean setComposingRegion(int start, int end) { |
458 if (DEBUG) Log.w(TAG, "setComposingRegion [" + start + " " + end + "]"); | 527 if (DEBUG) Log.w(TAG, "setComposingRegion [" + start + " " + end + "]"); |
459 int textLength = mEditable.length(); | 528 int textLength = mEditable.length(); |
460 int a = Math.min(start, end); | 529 int a = Math.min(start, end); |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
534 @VisibleForTesting | 603 @VisibleForTesting |
535 ImeState getImeStateForTesting() { | 604 ImeState getImeStateForTesting() { |
536 String text = mEditable.toString(); | 605 String text = mEditable.toString(); |
537 int selectionStart = Selection.getSelectionStart(mEditable); | 606 int selectionStart = Selection.getSelectionStart(mEditable); |
538 int selectionEnd = Selection.getSelectionEnd(mEditable); | 607 int selectionEnd = Selection.getSelectionEnd(mEditable); |
539 int compositionStart = getComposingSpanStart(mEditable); | 608 int compositionStart = getComposingSpanStart(mEditable); |
540 int compositionEnd = getComposingSpanEnd(mEditable); | 609 int compositionEnd = getComposingSpanEnd(mEditable); |
541 return new ImeState(text, selectionStart, selectionEnd, compositionStart
, compositionEnd); | 610 return new ImeState(text, selectionStart, selectionEnd, compositionStart
, compositionEnd); |
542 } | 611 } |
543 } | 612 } |
OLD | NEW |