Merge "Fix auto-complete for content-editable fields."
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 3697635..b255c57 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -60,6 +60,10 @@
import android.os.SystemClock;
import android.provider.Settings;
import android.speech.tts.TextToSpeech;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.Selection;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
@@ -363,38 +367,125 @@
}
/**
- * InputConnection used for ContentEditable. This captures the 'delete'
- * commands and sends delete key presses.
+ * InputConnection used for ContentEditable. This captures changes
+ * to the text and sends them either as key strokes or text changes.
*/
private class WebViewInputConnection extends BaseInputConnection {
- public WebViewInputConnection() {
- super(WebView.this, false);
- }
+ // Used for mapping characters to keys typed.
+ private KeyCharacterMap mKeyCharacterMap;
- private void sendKeyPress(int keyCode) {
- long eventTime = SystemClock.uptimeMillis();
- sendKeyEvent(new KeyEvent(eventTime, eventTime,
- KeyEvent.ACTION_DOWN, keyCode, 0, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD));
- sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
- KeyEvent.ACTION_UP, keyCode, 0, 0,
- KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- KeyEvent.FLAG_SOFT_KEYBOARD));
+ public WebViewInputConnection() {
+ super(WebView.this, true);
}
@Override
- public boolean deleteSurroundingText(int beforeLength, int afterLength) {
- // Look for one-character delete and send it as a key press.
- if (beforeLength == 1 && afterLength == 0) {
- sendKeyPress(KeyEvent.KEYCODE_DEL);
- } else if (beforeLength == 0 && afterLength == 1){
- sendKeyPress(KeyEvent.KEYCODE_FORWARD_DEL);
- } else if (mWebViewCore != null) {
- mWebViewCore.sendMessage(EventHub.DELETE_SURROUNDING_TEXT,
- beforeLength, afterLength);
+ public boolean setComposingText(CharSequence text, int newCursorPosition) {
+ Editable editable = getEditable();
+ int start = getComposingSpanStart(editable);
+ int end = getComposingSpanEnd(editable);
+ if (start < 0 || end < 0) {
+ start = Selection.getSelectionStart(editable);
+ end = Selection.getSelectionEnd(editable);
}
- return super.deleteSurroundingText(beforeLength, afterLength);
+ if (end < start) {
+ int temp = end;
+ end = start;
+ start = temp;
+ }
+ setNewText(start, end, text);
+ return super.setComposingText(text, newCursorPosition);
+ }
+
+ @Override
+ public boolean commitText(CharSequence text, int newCursorPosition) {
+ setComposingText(text, newCursorPosition);
+ finishComposingText();
+ return true;
+ }
+
+ @Override
+ public boolean deleteSurroundingText(int leftLength, int rightLength) {
+ Editable editable = getEditable();
+ int cursorPosition = Selection.getSelectionEnd(editable);
+ int startDelete = Math.max(0, cursorPosition - leftLength);
+ int endDelete = Math.min(editable.length(),
+ cursorPosition + rightLength);
+ setNewText(startDelete, endDelete, "");
+ return super.deleteSurroundingText(leftLength, rightLength);
+ }
+
+ /**
+ * Sends a text change to webkit indirectly. If it is a single-
+ * character add or delete, it sends it as a key stroke. If it cannot
+ * be represented as a key stroke, it sends it as a field change.
+ * @param start The start offset (inclusive) of the text being changed.
+ * @param end The end offset (exclusive) of the text being changed.
+ * @param text The new text to replace the changed text.
+ */
+ private void setNewText(int start, int end, CharSequence text) {
+ Editable editable = getEditable();
+ CharSequence original = editable.subSequence(start, end);
+ boolean isCharacterAdd = false;
+ boolean isCharacterDelete = false;
+ int textLength = text.length();
+ int originalLength = original.length();
+ if (textLength > originalLength) {
+ isCharacterAdd = (textLength == originalLength + 1)
+ && TextUtils.regionMatches(text, 0, original, 0,
+ originalLength);
+ } else if (originalLength > textLength) {
+ isCharacterDelete = (textLength == originalLength - 1)
+ && TextUtils.regionMatches(text, 0, original, 0,
+ textLength);
+ }
+ if (isCharacterAdd) {
+ sendCharacter(text.charAt(textLength - 1));
+ mTextGeneration++;
+ } else if (isCharacterDelete) {
+ sendDeleteKey();
+ mTextGeneration++;
+ } else if (textLength != originalLength ||
+ !TextUtils.regionMatches(text, 0, original, 0,
+ textLength)) {
+ // Send a message so that key strokes and text replacement
+ // do not come out of order.
+ Message replaceMessage = mPrivateHandler.obtainMessage(
+ REPLACE_TEXT, start, end, text.toString());
+ mPrivateHandler.sendMessage(replaceMessage);
+ }
+ }
+
+ /**
+ * Send a single character to the WebView as a key down and up event.
+ * @param c The character to be sent.
+ */
+ private void sendCharacter(char c) {
+ if (mKeyCharacterMap == null) {
+ mKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+ }
+ char[] chars = new char[1];
+ chars[0] = c;
+ KeyEvent[] events = mKeyCharacterMap.getEvents(chars);
+ if (events != null) {
+ for (KeyEvent event : events) {
+ sendKeyEvent(event);
+ }
+ }
+ }
+
+ /**
+ * Send the delete character as a key down and up event.
+ */
+ private void sendDeleteKey() {
+ long eventTime = SystemClock.uptimeMillis();
+ sendKeyEvent(new KeyEvent(eventTime, eventTime,
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, 0, 0,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD));
+ sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
+ KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL, 0, 0,
+ KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
+ KeyEvent.FLAG_SOFT_KEYBOARD));
}
}
@@ -799,6 +890,8 @@
static final int EXIT_FULLSCREEN_VIDEO = 140;
static final int COPY_TO_CLIPBOARD = 141;
+ static final int INIT_EDIT_FIELD = 142;
+ static final int REPLACE_TEXT = 143;
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -4951,12 +5044,18 @@
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN
+ outAttrs.inputType = EditorInfo.IME_FLAG_NO_FULLSCREEN
| EditorInfo.TYPE_CLASS_TEXT
- | EditorInfo.TYPE_TEXT_VARIATION_NORMAL;
+ | EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
+ | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
+ | EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT
+ | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
+ outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
+
if (mInputConnection == null) {
mInputConnection = new WebViewInputConnection();
}
+ outAttrs.initialCapsMode = mInputConnection.getCursorCapsMode(InputType.TYPE_CLASS_TEXT);
return mInputConnection;
}
@@ -8963,6 +9062,31 @@
copyToClipboard((String) msg.obj);
break;
+ case INIT_EDIT_FIELD:
+ if (mInputConnection != null) {
+ mTextGeneration = 0;
+ String text = (String)msg.obj;
+ mInputConnection.beginBatchEdit();
+ Editable editable = mInputConnection.getEditable();
+ editable.replace(0, editable.length(), text);
+ int start = msg.arg1;
+ int end = msg.arg2;
+ mInputConnection.setComposingRegion(end, end);
+ mInputConnection.setSelection(start, end);
+ mInputConnection.endBatchEdit();
+ }
+ break;
+
+ case REPLACE_TEXT:{
+ String text = (String)msg.obj;
+ int start = msg.arg1;
+ int end = msg.arg2;
+ int cursorPosition = start + text.length();
+ replaceTextfieldText(start, end, text,
+ cursorPosition, cursorPosition);
+ break;
+ }
+
default:
super.handleMessage(msg);
break;
@@ -9088,10 +9212,13 @@
*/
private void updateTextSelectionFromMessage(int nodePointer,
int textGeneration, WebViewCore.TextSelectionData data) {
- if (inEditingMode()
- && mWebTextView.isSameTextField(nodePointer)
- && textGeneration == mTextGeneration) {
- mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd);
+ if (textGeneration == mTextGeneration) {
+ if (inEditingMode()
+ && mWebTextView.isSameTextField(nodePointer)) {
+ mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd);
+ } else if (mInputConnection != null){
+ mInputConnection.setSelection(data.mStart, data.mEnd);
+ }
}
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index fe5c04c..fe51581 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -647,18 +647,6 @@
int end, int textGeneration);
/**
- * Delete text near the cursor.
- * @param nativeClass The pointer to the native class (mNativeClass)
- * @param leftLength The number of characters to the left of the cursor to
- * delete
- * @param rightLength The number of characters to the right of the cursor
- * to delete.
- */
- private native void nativeDeleteSurroundingText(int nativeClass,
- int leftLength,
- int rightLength);
-
- /**
* Set the selection to (start, end) in the focused textfield. If start and
* end are out of order, swap them.
* @param nativeClass Pointer to the C++ WebViewCore object mNativeClass
@@ -1576,11 +1564,6 @@
deleteSelectionData.mStart, deleteSelectionData.mEnd, msg.arg1);
break;
- case DELETE_SURROUNDING_TEXT:
- nativeDeleteSurroundingText(mNativeClass,
- msg.arg1, msg.arg2);
- break;
-
case SET_SELECTION:
nativeSetSelection(mNativeClass, msg.arg1, msg.arg2);
break;
@@ -2739,6 +2722,15 @@
WebView.FIND_AGAIN).sendToTarget();
}
+ // called by JNI
+ private void initEditField(String text, int start, int end) {
+ if (mWebView == null) {
+ return;
+ }
+ Message.obtain(mWebView.mPrivateHandler,
+ WebView.INIT_EDIT_FIELD, start, end, text).sendToTarget();
+ }
+
private native void nativeUpdateFrameCacheIfLoading(int nativeClass);
private native void nativeRevealSelection(int nativeClass);
private native String nativeRequestLabel(int nativeClass, int framePtr,