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; |
30 /** | 31 /** |
31 * Selection value should be -1 if not known. See EditorInfo.java for detail s. | 32 * Selection value should be -1 if not known. See EditorInfo.java for detail s. |
32 */ | 33 */ |
33 public static final int INVALID_SELECTION = -1; | 34 public static final int INVALID_SELECTION = -1; |
34 public static final int INVALID_COMPOSITION = -1; | 35 public static final int INVALID_COMPOSITION = -1; |
35 | 36 |
36 private final View mInternalView; | 37 private final View mInternalView; |
37 private final ImeAdapter mImeAdapter; | 38 private final ImeAdapter mImeAdapter; |
38 private final Editable mEditable; | 39 private final Editable mEditable; |
39 | 40 |
40 private boolean mSingleLine; | 41 private boolean mSingleLine; |
41 private int mNumNestedBatchEdits = 0; | 42 private int mNumNestedBatchEdits = 0; |
43 private int mPendingAccent; | |
42 | 44 |
43 private int mLastUpdateSelectionStart = INVALID_SELECTION; | 45 private int mLastUpdateSelectionStart = INVALID_SELECTION; |
44 private int mLastUpdateSelectionEnd = INVALID_SELECTION; | 46 private int mLastUpdateSelectionEnd = INVALID_SELECTION; |
45 private int mLastUpdateCompositionStart = INVALID_COMPOSITION; | 47 private int mLastUpdateCompositionStart = INVALID_COMPOSITION; |
46 private int mLastUpdateCompositionEnd = INVALID_COMPOSITION; | 48 private int mLastUpdateCompositionEnd = INVALID_COMPOSITION; |
47 | 49 |
48 @VisibleForTesting | 50 @VisibleForTesting |
49 AdapterInputConnection(View view, ImeAdapter imeAdapter, Editable editable, | 51 AdapterInputConnection(View view, ImeAdapter imeAdapter, Editable editable, |
50 EditorInfo outAttrs) { | 52 EditorInfo outAttrs) { |
51 super(view, true); | 53 super(view, true); |
(...skipping 156 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
208 + compositionStart + " " + compositionEnd + "]"); | 210 + compositionStart + " " + compositionEnd + "]"); |
209 } | 211 } |
210 // updateSelection should be called every time the selection or composit ion changes | 212 // 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. | 213 // if it happens not within a batch edit, or at the end of each top leve l batch edit. |
212 getInputMethodManagerWrapper().updateSelection(mInternalView, | 214 getInputMethodManagerWrapper().updateSelection(mInternalView, |
213 selectionStart, selectionEnd, compositionStart, compositionEnd); | 215 selectionStart, selectionEnd, compositionStart, compositionEnd); |
214 mLastUpdateSelectionStart = selectionStart; | 216 mLastUpdateSelectionStart = selectionStart; |
215 mLastUpdateSelectionEnd = selectionEnd; | 217 mLastUpdateSelectionEnd = selectionEnd; |
216 mLastUpdateCompositionStart = compositionStart; | 218 mLastUpdateCompositionStart = compositionStart; |
217 mLastUpdateCompositionEnd = compositionEnd; | 219 mLastUpdateCompositionEnd = compositionEnd; |
220 // Change in selection or cursor position invalidates any pending accent . | |
221 mPendingAccent = 0; | |
Ted C
2014/12/03 02:30:14
do we need to do this as well on restartInput (or
bcwhite
2014/12/03 15:49:09
Seems the smart thing to do. Done.
| |
218 } | 222 } |
219 | 223 |
220 /** | 224 /** |
221 * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int) | 225 * @see BaseInputConnection#setComposingText(java.lang.CharSequence, int) |
222 */ | 226 */ |
223 @Override | 227 @Override |
224 public boolean setComposingText(CharSequence text, int newCursorPosition) { | 228 public boolean setComposingText(CharSequence text, int newCursorPosition) { |
225 if (DEBUG) Log.w(TAG, "setComposingText [" + text + "] [" + newCursorPos ition + "]"); | 229 if (DEBUG) Log.w(TAG, "setComposingText [" + text + "] [" + newCursorPos ition + "]"); |
226 if (maybePerformEmptyCompositionWorkaround(text)) return true; | 230 if (maybePerformEmptyCompositionWorkaround(text)) return true; |
227 super.setComposingText(text, newCursorPosition); | 231 super.setComposingText(text, newCursorPosition); |
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
361 } | 365 } |
362 | 366 |
363 /** | 367 /** |
364 * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) | 368 * @see BaseInputConnection#sendKeyEvent(android.view.KeyEvent) |
365 */ | 369 */ |
366 @Override | 370 @Override |
367 public boolean sendKeyEvent(KeyEvent event) { | 371 public boolean sendKeyEvent(KeyEvent event) { |
368 if (DEBUG) { | 372 if (DEBUG) { |
369 Log.w(TAG, "sendKeyEvent [" + event.getAction() + "] [" + event.getK eyCode() + "]"); | 373 Log.w(TAG, "sendKeyEvent [" + event.getAction() + "] [" + event.getK eyCode() + "]"); |
370 } | 374 } |
375 | |
376 if (KeyEvent.isModifierKey(event.getKeyCode())) { | |
377 return mImeAdapter.translateAndSendNativeEvents(event, 0); | |
378 } | |
379 | |
380 // Physical keyboards also have their events come through here though no t | |
381 // by BaseInputConnection. In order to support "accent" key sequences | |
382 // such as "~n" or "^o" we have to record that one has been pressed | |
383 // and, if an accentable letter follows, delete the accent glyph and | |
384 // insert the composed character. | |
385 | |
386 int unicodeChar = event.getUnicodeChar(); | |
387 int pendingAccent = mPendingAccent; | |
388 int nextAccent = pendingAccent; | |
Ted C
2014/12/03 02:30:14
In what cases is this used? Just wondering what c
bcwhite
2014/12/03 15:49:09
It's used to keep all the accent-processing code a
| |
389 | |
390 if ((unicodeChar & KeyCharacterMap.COMBINING_ACCENT) != 0) { | |
391 pendingAccent = 0; | |
392 nextAccent = unicodeChar & KeyCharacterMap.COMBINING_ACCENT_MASK; | |
393 } else if (pendingAccent != 0) { | |
394 if (event.getAction() == KeyEvent.ACTION_DOWN) { | |
395 int combined = KeyEvent.getDeadChar(pendingAccent, unicodeChar); | |
396 if (combined != 0) { | |
397 // Previous accent combines with new character to create | |
398 // a new accented character. First delete the displayed | |
399 // accent so it appears overwritten by the composition. | |
400 // Note that deleting the displayed accent will cause an | |
401 // update and clearing of mPendingAccent which is why we've | |
402 // copied it to a local variable above. | |
403 super.deleteSurroundingText(1, 0); | |
404 mImeAdapter.deleteSurroundingText(1, 0); | |
405 } else { | |
406 // Previous accent doesn't combine with this character | |
407 // so assume both are completely independent. | |
408 pendingAccent = 0; | |
409 nextAccent = 0; | |
410 } | |
411 } | |
412 | |
413 if (event.getAction() == KeyEvent.ACTION_UP) { | |
414 // Forget accent after release of key being accented. | |
415 nextAccent = 0; | |
416 } | |
417 } | |
418 | |
371 // If this is a key-up, and backspace/del or if the key has a character representation, | 419 // 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 | 420 // need to update the underlying Editable (i.e. the local representation of the text |
373 // being edited). | 421 // being edited). |
374 if (event.getAction() == KeyEvent.ACTION_UP) { | 422 if (event.getAction() == KeyEvent.ACTION_UP) { |
375 if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { | 423 if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { |
376 deleteSurroundingText(1, 0); | 424 deleteSurroundingText(1, 0); |
377 return true; | 425 return true; |
378 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { | 426 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { |
379 deleteSurroundingText(0, 1); | 427 deleteSurroundingText(0, 1); |
380 return true; | 428 return true; |
381 } else { | 429 } else if (unicodeChar != 0) { |
382 int unicodeChar = event.getUnicodeChar(); | 430 int combinedChar = unicodeChar; |
383 if (unicodeChar != 0) { | 431 if (pendingAccent != 0) { |
384 int selectionStart = Selection.getSelectionStart(mEditable); | 432 combinedChar = KeyEvent.getDeadChar(pendingAccent, unicodeCh ar); |
385 int selectionEnd = Selection.getSelectionEnd(mEditable); | 433 if (combinedChar == 0) { |
386 if (selectionStart > selectionEnd) { | 434 combinedChar = unicodeChar; |
387 int temp = selectionStart; | |
388 selectionStart = selectionEnd; | |
389 selectionEnd = temp; | |
390 } | 435 } |
391 mEditable.replace(selectionStart, selectionEnd, | |
392 Character.toString((char) unicodeChar)); | |
393 } | 436 } |
437 int selectionStart = Selection.getSelectionStart(mEditable); | |
438 int selectionEnd = Selection.getSelectionEnd(mEditable); | |
439 if (selectionStart > selectionEnd) { | |
440 int temp = selectionStart; | |
441 selectionStart = selectionEnd; | |
442 selectionEnd = temp; | |
443 } | |
444 mEditable.replace(selectionStart, selectionEnd, | |
445 Character.toString((char) combinedChar)); | |
394 } | 446 } |
395 } else if (event.getAction() == KeyEvent.ACTION_DOWN) { | 447 } else if (event.getAction() == KeyEvent.ACTION_DOWN) { |
396 // TODO(aurimas): remove this workaround when crbug.com/278584 is fi xed. | 448 // TODO(aurimas): remove this workaround when crbug.com/278584 is fi xed. |
397 if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { | 449 if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { |
398 beginBatchEdit(); | 450 beginBatchEdit(); |
399 finishComposingText(); | 451 finishComposingText(); |
400 mImeAdapter.translateAndSendNativeEvents(event); | 452 mImeAdapter.translateAndSendNativeEvents(event, 0); |
401 endBatchEdit(); | 453 endBatchEdit(); |
402 return true; | 454 return true; |
403 } else if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { | 455 } else if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { |
404 return true; | 456 return true; |
405 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { | 457 } else if (event.getKeyCode() == KeyEvent.KEYCODE_FORWARD_DEL) { |
406 return true; | 458 return true; |
407 } | 459 } |
408 } | 460 } |
409 mImeAdapter.translateAndSendNativeEvents(event); | 461 mImeAdapter.translateAndSendNativeEvents(event, pendingAccent); |
462 mPendingAccent = nextAccent; | |
410 return true; | 463 return true; |
411 } | 464 } |
412 | 465 |
413 /** | 466 /** |
414 * @see BaseInputConnection#finishComposingText() | 467 * @see BaseInputConnection#finishComposingText() |
415 */ | 468 */ |
416 @Override | 469 @Override |
417 public boolean finishComposingText() { | 470 public boolean finishComposingText() { |
418 if (DEBUG) Log.w(TAG, "finishComposingText"); | 471 if (DEBUG) Log.w(TAG, "finishComposingText"); |
419 if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable)) { | 472 if (getComposingSpanStart(mEditable) == getComposingSpanEnd(mEditable)) { |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
534 @VisibleForTesting | 587 @VisibleForTesting |
535 ImeState getImeStateForTesting() { | 588 ImeState getImeStateForTesting() { |
536 String text = mEditable.toString(); | 589 String text = mEditable.toString(); |
537 int selectionStart = Selection.getSelectionStart(mEditable); | 590 int selectionStart = Selection.getSelectionStart(mEditable); |
538 int selectionEnd = Selection.getSelectionEnd(mEditable); | 591 int selectionEnd = Selection.getSelectionEnd(mEditable); |
539 int compositionStart = getComposingSpanStart(mEditable); | 592 int compositionStart = getComposingSpanStart(mEditable); |
540 int compositionEnd = getComposingSpanEnd(mEditable); | 593 int compositionEnd = getComposingSpanEnd(mEditable); |
541 return new ImeState(text, selectionStart, selectionEnd, compositionStart , compositionEnd); | 594 return new ImeState(text, selectionStart, selectionEnd, compositionStart , compositionEnd); |
542 } | 595 } |
543 } | 596 } |
OLD | NEW |