Chromium Code Reviews| 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 |