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

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: Added call to performEditorAction 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 // 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
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
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
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
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
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
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 }
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