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

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

Issue 11914003: Start sending synthetic keyevents for enter and tab in Android IME (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebased Created 7 years, 11 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 | Annotate | Revision Log
« no previous file with comments | « content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 /** 25 /**
25 We have to adapt and plumb android IME service and chrome text input API. 26 We have to adapt and plumb android IME service and chrome text input API.
26 ImeAdapter provides an interface in both ways native <-> java: 27 ImeAdapter provides an interface in both ways native <-> java:
27 1. InputConnectionAdapter notifies native code of text composition state and 28 1. InputConnectionAdapter notifies native code of text composition state and
28 dispatch key events from java -> WebKit. 29 dispatch key events from java -> WebKit.
29 2. Native ImeAdapter notifies java side to clear composition text. 30 2. Native ImeAdapter notifies java side to clear composition text.
30 31
31 The basic flow is: 32 The basic flow is:
32 1. Intercept dispatchKeyEventPreIme() to record the current key event, but do 33 1. When InputConnectionAdapter gets called with composition or result text:
33 nothing else. 34 If we receive a composition text or a result text, then we just need to
34 2. When InputConnectionAdapter gets called with composition or result text: 35 dispatch a synthetic key event with special keycode 229, and then dispatch
35 a) If a key event has been recorded in dispatchKeyEventPreIme() and we 36 the composition or result text.
36 receive a result text with single character, then we probably need to 37 2. Intercept dispatchKeyEvent() method for key events not handled by IME, we
37 send the result text as a Char event rather than a ConfirmComposition 38 need to dispatch them to webkit and check webkit's reply. Then inject a
38 event. So we need to dispatch the recorded key event followed by a 39 new key event for further processing if webkit didn't handle it.
39 synthetic Char event.
40 b) If we receive a composition text or a result text with more than one
41 characters, then no matter if we recorded a key event or not in
42 dispatchKeyEventPreIme(), we just need to dispatch a synthetic key
43 event with special keycode 229, and then dispatch the composition or
44 result text.
45 3. Intercept dispatchKeyEvent() method for key events not handled by IME, we
46 need to dispatch them to WebKit and check webkit's reply. Then inject a
47 new key event for further processing if WebKit didn't handle it.
48 */ 40 */
49 @JNINamespace("content") 41 @JNINamespace("content")
50 class ImeAdapter { 42 class ImeAdapter {
51 interface ViewEmbedder { 43 interface ViewEmbedder {
52 /** 44 /**
53 * @param isFinish whether the event is occurring because input is finis hed. 45 * @param isFinish whether the event is occurring because input is finis hed.
54 */ 46 */
55 public void onImeEvent(boolean isFinish); 47 public void onImeEvent(boolean isFinish);
56 public void onSetFieldValue(); 48 public void onSetFieldValue();
57 public void onDismissInput(); 49 public void onDismissInput();
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
110 sTextInputTypeUrl = textInputTypeUrl; 102 sTextInputTypeUrl = textInputTypeUrl;
111 sTextInputTypeEmail = textInputTypeEmail; 103 sTextInputTypeEmail = textInputTypeEmail;
112 sTextInputTypeTel = textInputTypeTel; 104 sTextInputTypeTel = textInputTypeTel;
113 sTextInputTypeNumber = textInputTypeNumber; 105 sTextInputTypeNumber = textInputTypeNumber;
114 sTextInputTypeWeek = textInputTypeWeek; 106 sTextInputTypeWeek = textInputTypeWeek;
115 sTextInputTypeContentEditable = textInputTypeContentEditable; 107 sTextInputTypeContentEditable = textInputTypeContentEditable;
116 } 108 }
117 109
118 private int mNativeImeAdapterAndroid; 110 private int mNativeImeAdapterAndroid;
119 private int mTextInputType; 111 private int mTextInputType;
120 private int mPreImeEventCount;
121 112
122 private Context mContext; 113 private Context mContext;
123 private SelectionHandleController mSelectionHandleController; 114 private SelectionHandleController mSelectionHandleController;
124 private InsertionHandleController mInsertionHandleController; 115 private InsertionHandleController mInsertionHandleController;
125 private AdapterInputConnection mInputConnection; 116 private AdapterInputConnection mInputConnection;
126 private ViewEmbedder mViewEmbedder; 117 private ViewEmbedder mViewEmbedder;
127 private Handler mHandler; 118 private Handler mHandler;
128 119
129 private class DelayedDismissInput implements Runnable { 120 private class DelayedDismissInput implements Runnable {
130 private int mNativeImeAdapter; 121 private int mNativeImeAdapter;
(...skipping 13 matching lines...) Expand all
144 135
145 // Delay introduced to avoid hiding the keyboard if new show requests are re ceived. 136 // Delay introduced to avoid hiding the keyboard if new show requests are re ceived.
146 // The time required by the unfocus-focus events triggered by tab has been m easured in soju: 137 // The time required by the unfocus-focus events triggered by tab has been m easured in soju:
147 // Mean: 18.633 ms, Standard deviation: 7.9837 ms. 138 // Mean: 18.633 ms, Standard deviation: 7.9837 ms.
148 // The value here should be higher enough to cover these cases, but not too high to avoid 139 // The value here should be higher enough to cover these cases, but not too high to avoid
149 // letting the user perceiving important delays. 140 // letting the user perceiving important delays.
150 private static final int INPUT_DISMISS_DELAY = 150; 141 private static final int INPUT_DISMISS_DELAY = 150;
151 142
152 ImeAdapter(Context context, SelectionHandleController selectionHandleControl ler, 143 ImeAdapter(Context context, SelectionHandleController selectionHandleControl ler,
153 InsertionHandleController insertionHandleController, ViewEmbedder em bedder) { 144 InsertionHandleController insertionHandleController, ViewEmbedder em bedder) {
154 mPreImeEventCount = 0;
155 mContext = context; 145 mContext = context;
156 mSelectionHandleController = selectionHandleController; 146 mSelectionHandleController = selectionHandleController;
157 mInsertionHandleController = insertionHandleController; 147 mInsertionHandleController = insertionHandleController;
158 mViewEmbedder = embedder; 148 mViewEmbedder = embedder;
159 mHandler = new Handler(); 149 mHandler = new Handler();
160 } 150 }
161 151
162 boolean isFor(int nativeImeAdapter, int textInputType) { 152 boolean isFor(int nativeImeAdapter, int textInputType) {
163 return mNativeImeAdapterAndroid == nativeImeAdapter && 153 return mNativeImeAdapterAndroid == nativeImeAdapter &&
164 mTextInputType == textInputType; 154 mTextInputType == textInputType;
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after
257 } 247 }
258 248
259 static boolean isTextInputType(int type) { 249 static boolean isTextInputType(int type) {
260 return type != sTextInputTypeNone && !InputDialogContainer.isDialogInput Type(type); 250 return type != sTextInputTypeNone && !InputDialogContainer.isDialogInput Type(type);
261 } 251 }
262 252
263 boolean hasTextInputType() { 253 boolean hasTextInputType() {
264 return isTextInputType(mTextInputType); 254 return isTextInputType(mTextInputType);
265 } 255 }
266 256
267 void dispatchKeyEventPreIme(KeyEvent event) {
268 // We only register that a key was pressed, but we don't actually interc ept
269 // it.
270 ++mPreImeEventCount;
271 }
272
273 boolean dispatchKeyEvent(KeyEvent event) { 257 boolean dispatchKeyEvent(KeyEvent event) {
274 mPreImeEventCount = 0;
275 return translateAndSendNativeEvents(event); 258 return translateAndSendNativeEvents(event);
276 } 259 }
277 260
278 void commitText() { 261 void commitText() {
279 cancelComposition(); 262 cancelComposition();
280 if (mNativeImeAdapterAndroid != 0) { 263 if (mNativeImeAdapterAndroid != 0) {
281 nativeCommitText(mNativeImeAdapterAndroid, ""); 264 nativeCommitText(mNativeImeAdapterAndroid, "");
282 } 265 }
283 } 266 }
284 267
(...skipping 11 matching lines...) Expand all
296 return false; 279 return false;
297 } 280 }
298 281
299 // Committing an empty string finishes the current composition. 282 // Committing an empty string finishes the current composition.
300 boolean isFinish = text.isEmpty(); 283 boolean isFinish = text.isEmpty();
301 if (!isFinish) { 284 if (!isFinish) {
302 mSelectionHandleController.hideAndDisallowAutomaticShowing(); 285 mSelectionHandleController.hideAndDisallowAutomaticShowing();
303 mInsertionHandleController.hideAndDisallowAutomaticShowing(); 286 mInsertionHandleController.hideAndDisallowAutomaticShowing();
304 } 287 }
305 mViewEmbedder.onImeEvent(isFinish); 288 mViewEmbedder.onImeEvent(isFinish);
306 boolean hasSingleChar = mPreImeEventCount == 1 && text.length() == 1; 289 int keyCode = shouldSendKeyEventWithKeyCode(text);
307 int keyCode = hasSingleChar ? text.codePointAt(0) : COMPOSITION_KEY_CODE ;
308 int keyChar = hasSingleChar ? text.codePointAt(0) : 0;
309 long timeStampMs = System.currentTimeMillis(); 290 long timeStampMs = System.currentTimeMillis();
310 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawKeyDo wn, 291
311 timeStampMs, keyCode, keyChar); 292 if (keyCode != COMPOSITION_KEY_CODE) {
312 if (hasSingleChar) { 293 sendKeyEventWithKeyCode(keyCode,
313 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeChar , 294 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE) ;
314 timeStampMs, text.codePointAt(0), text.codePointAt(0));
315 } else { 295 } else {
296 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawK eyDown,
297 timeStampMs, keyCode, 0);
316 if (isCommit) { 298 if (isCommit) {
317 nativeCommitText(mNativeImeAdapterAndroid, text); 299 nativeCommitText(mNativeImeAdapterAndroid, text);
318 } else { 300 } else {
319 nativeSetComposingText(mNativeImeAdapterAndroid, text, newCursor Position); 301 nativeSetComposingText(mNativeImeAdapterAndroid, text, newCursor Position);
320 } 302 }
303 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyU p,
304 timeStampMs, keyCode, 0);
321 } 305 }
322 nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyUp, 306
323 timeStampMs, keyCode, keyChar);
324 mPreImeEventCount = 0;
325 return true; 307 return true;
326 } 308 }
327 309
310 private int shouldSendKeyEventWithKeyCode(String text) {
311 if (text.length() != 1) return COMPOSITION_KEY_CODE;
312
313 if (text.equals("\n")) return KeyEvent.KEYCODE_ENTER;
314 else if (text.equals("\t")) return KeyEvent.KEYCODE_TAB;
315 else return COMPOSITION_KEY_CODE;
316 }
317
318 private void sendKeyEventWithKeyCode(int keyCode, int flags) {
319 long eventTime = System.currentTimeMillis();
320 translateAndSendNativeEvents(new KeyEvent(eventTime, eventTime,
321 KeyEvent.ACTION_DOWN, keyCode, 0, 0,
322 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
323 flags));
324 translateAndSendNativeEvents(new KeyEvent(System.currentTimeMillis(), ev entTime,
325 KeyEvent.ACTION_UP, keyCode, 0, 0,
326 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
327 flags));
328 }
329
328 private boolean translateAndSendNativeEvents(KeyEvent event) { 330 private boolean translateAndSendNativeEvents(KeyEvent event) {
329 if (mNativeImeAdapterAndroid == 0) { 331 if (mNativeImeAdapterAndroid == 0) {
330 return false; 332 return false;
331 } 333 }
332 int action = event.getAction(); 334 int action = event.getAction();
333 if (action != KeyEvent.ACTION_DOWN && 335 if (action != KeyEvent.ACTION_DOWN &&
334 action != KeyEvent.ACTION_UP) { 336 action != KeyEvent.ACTION_UP) {
335 // action == KeyEvent.ACTION_MULTIPLE 337 // action == KeyEvent.ACTION_MULTIPLE
336 // TODO(bulach): confirm the actual behavior. Apparently: 338 // TODO(bulach): confirm the actual behavior. Apparently:
337 // If event.getKeyCode() == KEYCODE_UNKNOWN, we can send a 339 // If event.getKeyCode() == KEYCODE_UNKNOWN, we can send a
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after
552 // Send TAB key event 554 // Send TAB key event
553 long timeStampMs = System.currentTimeMillis(); 555 long timeStampMs = System.currentTimeMillis();
554 mImeAdapter.sendSyntheticKeyEvent( 556 mImeAdapter.sendSyntheticKeyEvent(
555 sEventTypeRawKeyDown, timeStampMs, KeyEvent.KEYCODE_ TAB, 0); 557 sEventTypeRawKeyDown, timeStampMs, KeyEvent.KEYCODE_ TAB, 0);
556 return true; 558 return true;
557 case EditorInfo.IME_ACTION_GO: 559 case EditorInfo.IME_ACTION_GO:
558 case EditorInfo.IME_ACTION_SEARCH: 560 case EditorInfo.IME_ACTION_SEARCH:
559 mImeAdapter.dismissInput(true); 561 mImeAdapter.dismissInput(true);
560 break; 562 break;
561 } 563 }
562 564 mImeAdapter.sendKeyEventWithKeyCode(KeyEvent.KEYCODE_ENTER,
563 return super.performEditorAction(actionCode); 565 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
566 | KeyEvent.FLAG_EDITOR_ACTION);
567 return true;
564 } 568 }
565 569
566 @Override 570 @Override
567 public boolean performContextMenuAction(int id) { 571 public boolean performContextMenuAction(int id) {
568 switch (id) { 572 switch (id) {
569 case android.R.id.selectAll: 573 case android.R.id.selectAll:
570 return mImeAdapter.selectAll(); 574 return mImeAdapter.selectAll();
571 case android.R.id.cut: 575 case android.R.id.cut:
572 return mImeAdapter.cut(); 576 return mImeAdapter.cut();
573 case android.R.id.copy: 577 case android.R.id.copy:
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
621 if (selectionStart > selectionEnd) { 625 if (selectionStart > selectionEnd) {
622 int temp = selectionStart; 626 int temp = selectionStart;
623 selectionStart = selectionEnd; 627 selectionStart = selectionEnd;
624 selectionEnd = temp; 628 selectionEnd = temp;
625 } 629 }
626 editable.replace(selectionStart, selectionEnd, 630 editable.replace(selectionStart, selectionEnd,
627 Character.toString((char)unicodeChar)); 631 Character.toString((char)unicodeChar));
628 } 632 }
629 } 633 }
630 } 634 }
631 return super.sendKeyEvent(event); 635 mImeAdapter.translateAndSendNativeEvents(event);
636 return true;
632 } 637 }
633 638
634 @Override 639 @Override
635 public boolean finishComposingText() { 640 public boolean finishComposingText() {
636 Editable editable = getEditable(); 641 Editable editable = getEditable();
637 if (getComposingSpanStart(editable) == getComposingSpanEnd(editable) ) { 642 if (getComposingSpanStart(editable) == getComposingSpanEnd(editable) ) {
638 return true; 643 return true;
639 } 644 }
640 super.finishComposingText(); 645 super.finishComposingText();
641 return mImeAdapter.checkCompositionQueueAndCallNative("", 0, true); 646 return mImeAdapter.checkCompositionQueueAndCallNative("", 0, true);
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after
747 752
748 private native void nativeDeleteSurroundingText(int nativeImeAdapterAndroid, 753 private native void nativeDeleteSurroundingText(int nativeImeAdapterAndroid,
749 int before, int after); 754 int before, int after);
750 755
751 private native void nativeUnselect(int nativeImeAdapterAndroid); 756 private native void nativeUnselect(int nativeImeAdapterAndroid);
752 private native void nativeSelectAll(int nativeImeAdapterAndroid); 757 private native void nativeSelectAll(int nativeImeAdapterAndroid);
753 private native void nativeCut(int nativeImeAdapterAndroid); 758 private native void nativeCut(int nativeImeAdapterAndroid);
754 private native void nativeCopy(int nativeImeAdapterAndroid); 759 private native void nativeCopy(int nativeImeAdapterAndroid);
755 private native void nativePaste(int nativeImeAdapterAndroid); 760 private native void nativePaste(int nativeImeAdapterAndroid);
756 } 761 }
OLDNEW
« no previous file with comments | « content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698