OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 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; | 5 package org.chromium.content.browser; |
6 | 6 |
7 import android.content.Context; | 7 import android.content.Context; |
8 import android.os.Handler; | 8 import android.os.Handler; |
9 import android.os.ResultReceiver; | 9 import android.os.ResultReceiver; |
10 import android.text.Editable; | 10 import android.text.Editable; |
11 import android.text.InputType; | 11 import android.text.InputType; |
12 import android.text.Selection; | 12 import android.text.Selection; |
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 import android.view.inputmethod.InputMethodManager; | 20 import android.view.inputmethod.InputMethodManager; |
20 | 21 |
21 import org.chromium.base.CalledByNative; | 22 import org.chromium.base.CalledByNative; |
22 import org.chromium.base.JNINamespace; | 23 import org.chromium.base.JNINamespace; |
23 | 24 |
24 // We have to adapt and plumb android IME service and chrome text input API. | 25 // We have to adapt and plumb android IME service and chrome text input API. |
25 // ImeAdapter provides an interface in both ways native <-> java: | 26 // ImeAdapter provides an interface in both ways native <-> java: |
26 // 1. InputConnectionAdapter notifies native code of text composition state and | 27 // 1. InputConnectionAdapter notifies native code of text composition state and |
27 // dispatch key events from java -> WebKit. | 28 // dispatch key events from java -> WebKit. |
28 // 2. Native ImeAdapter notifies java side to clear composition text. | 29 // 2. Native ImeAdapter notifies java side to clear composition text. |
29 // | 30 // |
30 // The basic flow is: | 31 // The basic flow is: |
31 // 1. Intercept dispatchKeyEventPreIme() to record the current key event, but do | 32 // 1. When InputConnectionAdapter gets called with composition or result text: |
32 // nothing else. | |
33 // 2. When InputConnectionAdapter gets called with composition or result text: | |
34 // a) If a key event has been recorded in dispatchKeyEventPreIme() and we | 33 // a) If a key event has been recorded in dispatchKeyEventPreIme() and we |
aurimas (slooooooooow)
2013/01/16 23:54:46
Should this dispatchKeyEventPreIme() comment chang
Yusuf
2013/01/17 00:44:19
Done.
| |
35 // receive a result text with single character, then we probably need to | 34 // receive a result text with single character, then we probably need to |
36 // send the result text as a Char event rather than a ConfirmComposition | 35 // send the result text as a Char event rather than a ConfirmComposition |
37 // event. So we need to dispatch the recorded key event followed by a | 36 // event. So we need to dispatch the recorded key event followed by a |
38 // synthetic Char event. | 37 // synthetic Char event. |
39 // b) If we receive a composition text or a result text with more than one | 38 // b) If we receive a composition text or a result text with more than one |
40 // characters, then no matter if we recorded a key event or not in | 39 // characters, then no matter if we recorded a key event or not in |
41 // dispatchKeyEventPreIme(), we just need to dispatch a synthetic key | 40 // dispatchKeyEventPreIme(), we just need to dispatch a synthetic key |
42 // event with special keycode 229, and then dispatch the composition or | 41 // event with special keycode 229, and then dispatch the composition or |
43 // result text. | 42 // result text. |
44 // 3. Intercept dispatchKeyEvent() method for key events not handled by IME, we | 43 // 2. Intercept dispatchKeyEvent() method for key events not handled by IME, we |
45 // need to dispatch them to webkit and check webkit's reply. Then inject a | 44 // need to dispatch them to webkit and check webkit's reply. Then inject a |
46 // new key event for further processing if webkit didn't handle it. | 45 // new key event for further processing if webkit didn't handle it. |
47 @JNINamespace("content") | 46 @JNINamespace("content") |
48 class ImeAdapter { | 47 class ImeAdapter { |
49 interface ViewEmbedder { | 48 interface ViewEmbedder { |
50 /** | 49 /** |
51 * @param isFinish whether the event is occuring because input is finish ed. | 50 * @param isFinish whether the event is occuring because input is finish ed. |
52 */ | 51 */ |
53 public void onImeEvent(boolean isFinish); | 52 public void onImeEvent(boolean isFinish); |
54 public void onSetFieldValue(); | 53 public void onSetFieldValue(); |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
110 sTextInputTypeTel = textInputTypeTel; | 109 sTextInputTypeTel = textInputTypeTel; |
111 sTextInputTypeNumber = textInputTypeNumber; | 110 sTextInputTypeNumber = textInputTypeNumber; |
112 sTextInputTypeWeek = textInputTypeWeek; | 111 sTextInputTypeWeek = textInputTypeWeek; |
113 sTextInputTypeContentEditable = textInputTypeContentEditable; | 112 sTextInputTypeContentEditable = textInputTypeContentEditable; |
114 InputDialogContainer.initializeInputTypes(textInputTypeDate, textInputTy peDateTime, | 113 InputDialogContainer.initializeInputTypes(textInputTypeDate, textInputTy peDateTime, |
115 textInputTypeDateTimeLocal, textInputTypeMonth, textInputTypeTim e); | 114 textInputTypeDateTimeLocal, textInputTypeMonth, textInputTypeTim e); |
116 } | 115 } |
117 | 116 |
118 private int mNativeImeAdapterAndroid; | 117 private int mNativeImeAdapterAndroid; |
119 private int mTextInputType; | 118 private int mTextInputType; |
120 private int mPreImeEventCount; | |
121 | 119 |
122 private Context mContext; | 120 private Context mContext; |
123 private SelectionHandleController mSelectionHandleController; | 121 private SelectionHandleController mSelectionHandleController; |
124 private InsertionHandleController mInsertionHandleController; | 122 private InsertionHandleController mInsertionHandleController; |
125 private AdapterInputConnection mInputConnection; | 123 private AdapterInputConnection mInputConnection; |
126 private ViewEmbedder mViewEmbedder; | 124 private ViewEmbedder mViewEmbedder; |
127 private Handler mHandler; | 125 private Handler mHandler; |
128 private InputDialogContainer mInputDialogContainer; | 126 private InputDialogContainer mInputDialogContainer; |
129 | 127 |
130 private class DelayedDismissInput implements Runnable { | 128 private class DelayedDismissInput implements Runnable { |
(...skipping 14 matching lines...) Expand all Loading... | |
145 | 143 |
146 // Delay introduced to avoid hiding the keyboard if new show requests are re ceived. | 144 // Delay introduced to avoid hiding the keyboard if new show requests are re ceived. |
147 // The time required by the unfocus-focus events triggered by tab has been m easured in soju: | 145 // The time required by the unfocus-focus events triggered by tab has been m easured in soju: |
148 // Mean: 18.633 ms, Standard deviation: 7.9837 ms. | 146 // Mean: 18.633 ms, Standard deviation: 7.9837 ms. |
149 // The value here should be higher enough to cover these cases, but not too high to avoid | 147 // The value here should be higher enough to cover these cases, but not too high to avoid |
150 // letting the user perceiving important delays. | 148 // letting the user perceiving important delays. |
151 private static final int INPUT_DISMISS_DELAY = 150; | 149 private static final int INPUT_DISMISS_DELAY = 150; |
152 | 150 |
153 ImeAdapter(Context context, SelectionHandleController selectionHandleControl ler, | 151 ImeAdapter(Context context, SelectionHandleController selectionHandleControl ler, |
154 InsertionHandleController insertionHandleController, ViewEmbedder em bedder) { | 152 InsertionHandleController insertionHandleController, ViewEmbedder em bedder) { |
155 mPreImeEventCount = 0; | |
156 mContext = context; | 153 mContext = context; |
157 mSelectionHandleController = selectionHandleController; | 154 mSelectionHandleController = selectionHandleController; |
158 mInsertionHandleController = insertionHandleController; | 155 mInsertionHandleController = insertionHandleController; |
159 mViewEmbedder = embedder; | 156 mViewEmbedder = embedder; |
160 mHandler = new Handler(); | 157 mHandler = new Handler(); |
161 mInputDialogContainer = new InputDialogContainer(context, | 158 mInputDialogContainer = new InputDialogContainer(context, |
162 new InputDialogContainer.InputActionDelegate() { | 159 new InputDialogContainer.InputActionDelegate() { |
163 | 160 |
164 @Override | 161 @Override |
165 public void replaceDateTime(String text) { | 162 public void replaceDateTime(String text) { |
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
295 } | 292 } |
296 | 293 |
297 boolean hasTextInputType() { | 294 boolean hasTextInputType() { |
298 return isTextInputType(mTextInputType); | 295 return isTextInputType(mTextInputType); |
299 } | 296 } |
300 | 297 |
301 boolean hasDialogInputType() { | 298 boolean hasDialogInputType() { |
302 return InputDialogContainer.isDialogInputType(mTextInputType); | 299 return InputDialogContainer.isDialogInputType(mTextInputType); |
303 } | 300 } |
304 | 301 |
305 void dispatchKeyEventPreIme(KeyEvent event) { | |
306 // We only register that a key was pressed, but we don't actually interc ept | |
307 // it. | |
308 ++mPreImeEventCount; | |
309 } | |
310 | |
311 boolean dispatchKeyEvent(KeyEvent event) { | 302 boolean dispatchKeyEvent(KeyEvent event) { |
312 mPreImeEventCount = 0; | |
313 return translateAndSendNativeEvents(event); | 303 return translateAndSendNativeEvents(event); |
314 } | 304 } |
315 | 305 |
316 void commitText() { | 306 void commitText() { |
317 cancelComposition(); | 307 cancelComposition(); |
318 if (mNativeImeAdapterAndroid != 0) { | 308 if (mNativeImeAdapterAndroid != 0) { |
319 nativeCommitText(mNativeImeAdapterAndroid, ""); | 309 nativeCommitText(mNativeImeAdapterAndroid, ""); |
320 } | 310 } |
321 } | 311 } |
322 | 312 |
(...skipping 11 matching lines...) Expand all Loading... | |
334 return false; | 324 return false; |
335 } | 325 } |
336 | 326 |
337 // Committing an empty string finishes the current composition. | 327 // Committing an empty string finishes the current composition. |
338 boolean isFinish = text.isEmpty(); | 328 boolean isFinish = text.isEmpty(); |
339 if (!isFinish) { | 329 if (!isFinish) { |
340 mSelectionHandleController.hideAndDisallowAutomaticShowing(); | 330 mSelectionHandleController.hideAndDisallowAutomaticShowing(); |
341 mInsertionHandleController.hideAndDisallowAutomaticShowing(); | 331 mInsertionHandleController.hideAndDisallowAutomaticShowing(); |
342 } | 332 } |
343 mViewEmbedder.onImeEvent(isFinish); | 333 mViewEmbedder.onImeEvent(isFinish); |
344 boolean hasSingleChar = mPreImeEventCount == 1 && text.length() == 1; | 334 int keyCode = shouldSendKeyEventWithKeyCode(text); |
345 int keyCode = hasSingleChar ? text.codePointAt(0) : COMPOSITION_KEY_CODE ; | |
346 int keyChar = hasSingleChar ? text.codePointAt(0) : 0; | |
347 long timeStampMs = System.currentTimeMillis(); | 335 long timeStampMs = System.currentTimeMillis(); |
348 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawKeyDo wn, | 336 |
349 timeStampMs, keyCode, keyChar); | 337 if (keyCode != COMPOSITION_KEY_CODE) { |
350 if (hasSingleChar) { | 338 sendKeyEventWithKeyCode(keyCode, |
351 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeChar , | 339 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE) ; |
352 timeStampMs, text.codePointAt(0), text.codePointAt(0)); | |
353 } else { | 340 } else { |
341 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawK eyDown, | |
342 timeStampMs, keyCode, 0); | |
354 if (isCommit) { | 343 if (isCommit) { |
355 nativeCommitText(mNativeImeAdapterAndroid, text); | 344 nativeCommitText(mNativeImeAdapterAndroid, text); |
356 } else { | 345 } else { |
357 nativeSetComposingText(mNativeImeAdapterAndroid, text, newCursor Position); | 346 nativeSetComposingText(mNativeImeAdapterAndroid, text, newCursor Position); |
358 } | 347 } |
348 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyU p, | |
349 timeStampMs, keyCode, 0); | |
359 } | 350 } |
360 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyUp, | 351 |
361 timeStampMs, keyCode, keyChar); | |
362 mPreImeEventCount = 0; | |
363 return true; | 352 return true; |
364 } | 353 } |
365 | 354 |
355 private int shouldSendKeyEventWithKeyCode(String text) { | |
356 if (text.length() != 1) return COMPOSITION_KEY_CODE; | |
357 | |
358 if (text.equals("\n")) return KeyEvent.KEYCODE_ENTER; | |
359 else if (text.equals("\t")) return KeyEvent.KEYCODE_TAB; | |
360 else return COMPOSITION_KEY_CODE; | |
361 } | |
362 | |
363 private void sendKeyEventWithKeyCode(int KeyCode, int flags) { | |
Ted C
2013/01/16 23:48:57
s/KeyCode/keyCode
Yusuf
2013/01/17 00:44:19
Done.
Yusuf
2013/01/17 00:44:19
Done.
| |
364 long eventTime = System.currentTimeMillis(); | |
365 translateAndSendNativeEvents(new KeyEvent(eventTime, eventTime, | |
366 KeyEvent.ACTION_DOWN, KeyCode, 0, 0, | |
367 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, | |
368 flags)); | |
369 translateAndSendNativeEvents(new KeyEvent(System.currentTimeMillis(), ev entTime, | |
370 KeyEvent.ACTION_UP, KeyCode, 0, 0, | |
371 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, | |
372 flags)); | |
373 } | |
374 | |
366 private boolean translateAndSendNativeEvents(KeyEvent event) { | 375 private boolean translateAndSendNativeEvents(KeyEvent event) { |
367 if (mNativeImeAdapterAndroid == 0) { | 376 if (mNativeImeAdapterAndroid == 0) { |
368 return false; | 377 return false; |
369 } | 378 } |
370 int action = event.getAction(); | 379 int action = event.getAction(); |
371 if (action != KeyEvent.ACTION_DOWN && | 380 if (action != KeyEvent.ACTION_DOWN && |
372 action != KeyEvent.ACTION_UP) { | 381 action != KeyEvent.ACTION_UP) { |
373 // action == KeyEvent.ACTION_MULTIPLE | 382 // action == KeyEvent.ACTION_MULTIPLE |
374 // TODO(bulach): confirm the actual behavior. Apparently: | 383 // TODO(bulach): confirm the actual behavior. Apparently: |
375 // If event.getKeyCode() == KEYCODE_UNKNOWN, we can send a | 384 // If event.getKeyCode() == KEYCODE_UNKNOWN, we can send a |
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
590 // Send TAB key event | 599 // Send TAB key event |
591 long timeStampMs = System.currentTimeMillis(); | 600 long timeStampMs = System.currentTimeMillis(); |
592 mImeAdapter.sendSyntheticKeyEvent( | 601 mImeAdapter.sendSyntheticKeyEvent( |
593 sEventTypeRawKeyDown, timeStampMs, KeyEvent.KEYCODE_ TAB, 0); | 602 sEventTypeRawKeyDown, timeStampMs, KeyEvent.KEYCODE_ TAB, 0); |
594 return true; | 603 return true; |
595 case EditorInfo.IME_ACTION_GO: | 604 case EditorInfo.IME_ACTION_GO: |
596 case EditorInfo.IME_ACTION_SEARCH: | 605 case EditorInfo.IME_ACTION_SEARCH: |
597 mImeAdapter.dismissInput(true); | 606 mImeAdapter.dismissInput(true); |
598 break; | 607 break; |
599 } | 608 } |
600 | 609 mImeAdapter.sendKeyEventWithKeyCode(KeyEvent.KEYCODE_ENTER, |
Ted C
2013/01/16 23:48:57
Hmm...it seems odd that we are treating a search a
aurimas (slooooooooow)
2013/01/16 23:54:46
There is KeyEvent.KEYCODE_SEARCH. Should we be usi
Yusuf
2013/01/17 00:44:19
I dont think KEY_CODE_SEARCH will have any corresp
| |
601 return super.performEditorAction(actionCode); | 610 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE |
611 | KeyEvent.FLAG_EDITOR_ACTION); | |
612 return true; | |
602 } | 613 } |
603 | 614 |
604 @Override | 615 @Override |
605 public boolean performContextMenuAction(int id) { | 616 public boolean performContextMenuAction(int id) { |
606 switch (id) { | 617 switch (id) { |
607 case android.R.id.selectAll: | 618 case android.R.id.selectAll: |
608 return mImeAdapter.selectAll(); | 619 return mImeAdapter.selectAll(); |
609 case android.R.id.cut: | 620 case android.R.id.cut: |
610 return mImeAdapter.cut(); | 621 return mImeAdapter.cut(); |
611 case android.R.id.copy: | 622 case android.R.id.copy: |
(...skipping 177 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
789 | 800 |
790 private native void nativeDeleteSurroundingText(int nativeImeAdapterAndroid, | 801 private native void nativeDeleteSurroundingText(int nativeImeAdapterAndroid, |
791 int before, int after); | 802 int before, int after); |
792 | 803 |
793 private native void nativeUnselect(int nativeImeAdapterAndroid); | 804 private native void nativeUnselect(int nativeImeAdapterAndroid); |
794 private native void nativeSelectAll(int nativeImeAdapterAndroid); | 805 private native void nativeSelectAll(int nativeImeAdapterAndroid); |
795 private native void nativeCut(int nativeImeAdapterAndroid); | 806 private native void nativeCut(int nativeImeAdapterAndroid); |
796 private native void nativeCopy(int nativeImeAdapterAndroid); | 807 private native void nativeCopy(int nativeImeAdapterAndroid); |
797 private native void nativePaste(int nativeImeAdapterAndroid); | 808 private native void nativePaste(int nativeImeAdapterAndroid); |
798 } | 809 } |
OLD | NEW |