Commit 36ef2cb9 authored by bcwhite@chromium.org's avatar bcwhite@chromium.org

Fix keycode problems for modified keys.

There is a problem with keycode generation for non-trivial characters
(i.e. those that have modifiers) because the event array generated is
not simply a KeyDown/KeyUp combination; each modifier also gets a pair
of events.

Instead, scan through the events and send the first KeyDown that isn't
a modifier key.  Also, don't send key events if nothing is changing
(as happens with the final "commit").

SendSythenticKeyEvent needed to be extended to accept modifier flags.

BUG=118639

Review URL: https://codereview.chromium.org/426063002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@288179 0039d316-1c4b-4281-b951-d872f2087c98
parent 74893ffe
......@@ -148,9 +148,10 @@ bool ImeAdapterAndroid::SendSyntheticKeyEvent(JNIEnv*,
int type,
long time_ms,
int key_code,
int modifiers,
int text) {
NativeWebKeyboardEvent event(static_cast<blink::WebInputEvent::Type>(type),
0 /* modifiers */, time_ms / 1000.0, key_code,
modifiers, time_ms / 1000.0, key_code,
text, false /* is_system_key */);
rwhva_->SendKeyEvent(event);
return true;
......
......@@ -41,6 +41,7 @@ class ImeAdapterAndroid {
int event_type,
long timestamp_ms,
int native_key_code,
int modifiers,
int unicode_char);
void SetComposingText(JNIEnv*,
jobject obj,
......
......@@ -243,7 +243,7 @@ public class AdapterInputConnection extends BaseInputConnection {
// Send TAB key event
long timeStampMs = SystemClock.uptimeMillis();
mImeAdapter.sendSyntheticKeyEvent(
ImeAdapter.sEventTypeRawKeyDown, timeStampMs, KeyEvent.KEYCODE_TAB, 0);
ImeAdapter.sEventTypeRawKeyDown, timeStampMs, KeyEvent.KEYCODE_TAB, 0, 0);
} else {
mImeAdapter.sendKeyEventWithKeyCode(KeyEvent.KEYCODE_ENTER,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
......@@ -339,10 +339,10 @@ public class AdapterInputConnection extends BaseInputConnection {
boolean result = true;
if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
result = mImeAdapter.sendSyntheticKeyEvent(
ImeAdapter.sEventTypeRawKeyDown, SystemClock.uptimeMillis(), keyCode, 0);
ImeAdapter.sEventTypeRawKeyDown, SystemClock.uptimeMillis(), keyCode, 0, 0);
result &= mImeAdapter.deleteSurroundingText(beforeLength, afterLength);
result &= mImeAdapter.sendSyntheticKeyEvent(
ImeAdapter.sEventTypeKeyUp, SystemClock.uptimeMillis(), keyCode, 0);
ImeAdapter.sEventTypeKeyUp, SystemClock.uptimeMillis(), keyCode, 0, 0);
} else {
mImeAdapter.sendKeyEventWithKeyCode(
keyCode, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
......
......@@ -326,44 +326,55 @@ public class ImeAdapter {
}
/**
* @return Android keycode for a single unicode character.
* @return Android KeyEvent for a single unicode character. Only one KeyEvent is returned
* even if the system determined that various modifier keys (like Shift) would also have
* been pressed.
*/
private static int androidKeyCodeForCharacter(char chr) {
private static KeyEvent androidKeyEventForCharacter(char chr) {
if (sKeyCharacterMap == null) {
sKeyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
}
sSingleCharArray[0] = chr;
// TODO: Evaluate cost of this system call.
KeyEvent[] events = sKeyCharacterMap.getEvents(sSingleCharArray);
if (events == null || events.length != 2) // One key-down event and one key-up event.
return KeyEvent.KEYCODE_UNKNOWN;
return events[0].getKeyCode();
if (events == null) { // No known key sequence will create that character.
return null;
}
for (int i = 0; i < events.length; ++i) {
if (events[i].getAction() == KeyEvent.ACTION_DOWN &&
!KeyEvent.isModifierKey(events[i].getKeyCode())) {
return events[i];
}
}
return null; // No printing characters were found.
}
@VisibleForTesting
public static int getTypedKeycodeGuess(String oldtext, String newtext) {
public static KeyEvent getTypedKeyEventGuess(String oldtext, String newtext) {
// Starting typing a new composition should add only a single character. Any composition
// beginning with text longer than that must come from something other than typing so
// return 0.
if (oldtext == null) {
if (newtext.length() == 1) {
return androidKeyCodeForCharacter(newtext.charAt(0));
return androidKeyEventForCharacter(newtext.charAt(0));
} else {
return 0;
return null;
}
}
// The content has grown in length: assume the last character is the key that caused it.
if (newtext.length() > oldtext.length() && newtext.startsWith(oldtext))
return androidKeyCodeForCharacter(newtext.charAt(newtext.length() - 1));
return androidKeyEventForCharacter(newtext.charAt(newtext.length() - 1));
// The content has shrunk in length: assume that backspace was pressed.
if (oldtext.length() > newtext.length() && oldtext.startsWith(newtext))
return KeyEvent.KEYCODE_DEL;
return new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL);
// The content is unchanged or has undergone a complex change (i.e. not a simple tail
// modification) so return an unknown key-code.
return 0;
return null;
}
void sendKeyEventWithKeyCode(int keyCode, int flags) {
......@@ -395,9 +406,26 @@ public class ImeAdapter {
sendKeyEventWithKeyCode(keyCode,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
} else {
keyCode = getTypedKeycodeGuess(mLastComposeText, textStr);
mLastComposeText = textStr;
KeyEvent keyEvent = getTypedKeyEventGuess(mLastComposeText, textStr);
int modifiers = 0;
if (keyEvent != null) {
keyCode = keyEvent.getKeyCode();
modifiers = getModifiers(keyEvent.getMetaState());
} else if (!textStr.equals(mLastComposeText)) {
keyCode = KeyEvent.KEYCODE_UNKNOWN;
} else {
keyCode = -1;
}
// If this is a commit with no previous composition, then treat it as a native
// KeyDown/KeyUp pair with no composition rather than a synthetic pair with
// composition below.
if (keyCode > 0 && isCommit && mLastComposeText == null) {
mLastSyntheticKeyCode = keyCode;
return translateAndSendNativeEvents(keyEvent) &&
translateAndSendNativeEvents(KeyEvent.changeAction(
keyEvent, KeyEvent.ACTION_UP));
}
// When typing, there is no issue sending KeyDown and KeyUp events around the
// composition event because those key events do nothing (other than call JS
......@@ -417,20 +445,27 @@ public class ImeAdapter {
// For now, the solution is to endure the restarting of composition and only dive
// into the alternate solution should there be problems in the field. --bcwhite
if (keyCode >= 0) {
nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeRawKeyDown,
timeStampMs, keyCode, 0);
timeStampMs, keyCode, modifiers, 0);
}
if (isCommit) {
nativeCommitText(mNativeImeAdapterAndroid, textStr);
mLastComposeText = null;
textStr = null;
} else {
nativeSetComposingText(mNativeImeAdapterAndroid, text, textStr, newCursorPosition);
}
if (keyCode >= 0) {
nativeSendSyntheticKeyEvent(mNativeImeAdapterAndroid, sEventTypeKeyUp,
timeStampMs, keyCode, 0);
timeStampMs, keyCode, modifiers, 0);
}
mLastSyntheticKeyCode = keyCode;
}
mLastComposeText = textStr;
return true;
}
......@@ -464,11 +499,12 @@ public class ImeAdapter {
/*isSystemKey=*/false, event.getUnicodeChar());
}
boolean sendSyntheticKeyEvent(int eventType, long timestampMs, int keyCode, int unicodeChar) {
boolean sendSyntheticKeyEvent(int eventType, long timestampMs, int keyCode, int modifiers,
int unicodeChar) {
if (mNativeImeAdapterAndroid == 0) return false;
nativeSendSyntheticKeyEvent(
mNativeImeAdapterAndroid, eventType, timestampMs, keyCode, unicodeChar);
mNativeImeAdapterAndroid, eventType, timestampMs, keyCode, modifiers, unicodeChar);
return true;
}
......@@ -633,7 +669,7 @@ public class ImeAdapter {
}
private native boolean nativeSendSyntheticKeyEvent(long nativeImeAdapterAndroid,
int eventType, long timestampMs, int keyCode, int unicodeChar);
int eventType, long timestampMs, int keyCode, int modifiers, int unicodeChar);
private native boolean nativeSendKeyEvent(long nativeImeAdapterAndroid, KeyEvent event,
int action, int modifiers, long timestampMs, int keyCode, boolean isSystemKey,
......
......@@ -262,40 +262,51 @@ public class ImeTest extends ContentShellTestBase {
waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 6, "h\nllo ", 2, 2, -1, -1);
}
private int getTypedKeycodeGuess(String before, String after) {
KeyEvent ev = ImeAdapter.getTypedKeyEventGuess(before, after);
if (ev == null) return -1;
return ev.getKeyCode();
}
@SmallTest
@Feature({"TextInput", "Main"})
public void testGuessedKeycodeFromTyping() throws Throwable {
assertEquals(0, ImeAdapter.getTypedKeycodeGuess(null, ""));
assertEquals(KeyEvent.KEYCODE_X, ImeAdapter.getTypedKeycodeGuess(null, "x"));
assertEquals(0, ImeAdapter.getTypedKeycodeGuess(null, "xyz"));
assertEquals(0, ImeAdapter.getTypedKeycodeGuess("abc", "abc"));
assertEquals(KeyEvent.KEYCODE_DEL, ImeAdapter.getTypedKeycodeGuess("abc", ""));
assertEquals(KeyEvent.KEYCODE_H, ImeAdapter.getTypedKeycodeGuess("", "h"));
assertEquals(KeyEvent.KEYCODE_DEL, ImeAdapter.getTypedKeycodeGuess("h", ""));
assertEquals(KeyEvent.KEYCODE_E, ImeAdapter.getTypedKeycodeGuess("h", "he"));
assertEquals(KeyEvent.KEYCODE_L, ImeAdapter.getTypedKeycodeGuess("he", "hel"));
assertEquals(KeyEvent.KEYCODE_O, ImeAdapter.getTypedKeycodeGuess("hel", "helo"));
assertEquals(KeyEvent.KEYCODE_DEL, ImeAdapter.getTypedKeycodeGuess("helo", "hel"));
assertEquals(KeyEvent.KEYCODE_L, ImeAdapter.getTypedKeycodeGuess("hel", "hell"));
assertEquals(KeyEvent.KEYCODE_L, ImeAdapter.getTypedKeycodeGuess("hell", "helll"));
assertEquals(KeyEvent.KEYCODE_DEL, ImeAdapter.getTypedKeycodeGuess("helll", "hell"));
assertEquals(KeyEvent.KEYCODE_O, ImeAdapter.getTypedKeycodeGuess("hell", "hello"));
assertEquals(KeyEvent.KEYCODE_X, ImeAdapter.getTypedKeycodeGuess("xxx", "xxxx"));
assertEquals(KeyEvent.KEYCODE_X, ImeAdapter.getTypedKeycodeGuess("xxx", "xxxxx"));
assertEquals(KeyEvent.KEYCODE_DEL, ImeAdapter.getTypedKeycodeGuess("xxx", "xx"));
assertEquals(KeyEvent.KEYCODE_DEL, ImeAdapter.getTypedKeycodeGuess("xxx", "x"));
assertEquals(KeyEvent.KEYCODE_Y, ImeAdapter.getTypedKeycodeGuess("xxx", "xxxy"));
assertEquals(KeyEvent.KEYCODE_Y, ImeAdapter.getTypedKeycodeGuess("xxx", "xxxxy"));
assertEquals(0, ImeAdapter.getTypedKeycodeGuess("xxx", "xy"));
assertEquals(0, ImeAdapter.getTypedKeycodeGuess("xxx", "y"));
assertEquals(0, ImeAdapter.getTypedKeycodeGuess("foo", "bar"));
assertEquals(0, ImeAdapter.getTypedKeycodeGuess("foo", "bars"));
assertEquals(0, ImeAdapter.getTypedKeycodeGuess("foo", "ba"));
public void testGuessedKeyCodeFromTyping() throws Throwable {
assertEquals(-1, getTypedKeycodeGuess(null, ""));
assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess(null, "x"));
assertEquals(-1, getTypedKeycodeGuess(null, "xyz"));
assertEquals(-1, getTypedKeycodeGuess("abc", "abc"));
assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("abc", ""));
assertEquals(KeyEvent.KEYCODE_H, getTypedKeycodeGuess("", "h"));
assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("h", ""));
assertEquals(KeyEvent.KEYCODE_E, getTypedKeycodeGuess("h", "he"));
assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("he", "hel"));
assertEquals(KeyEvent.KEYCODE_O, getTypedKeycodeGuess("hel", "helo"));
assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("helo", "hel"));
assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("hel", "hell"));
assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("hell", "helll"));
assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("helll", "hell"));
assertEquals(KeyEvent.KEYCODE_O, getTypedKeycodeGuess("hell", "hello"));
assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess("xxx", "xxxx"));
assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess("xxx", "xxxxx"));
assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("xxx", "xx"));
assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("xxx", "x"));
assertEquals(KeyEvent.KEYCODE_Y, getTypedKeycodeGuess("xxx", "xxxy"));
assertEquals(KeyEvent.KEYCODE_Y, getTypedKeycodeGuess("xxx", "xxxxy"));
assertEquals(-1, getTypedKeycodeGuess("xxx", "xy"));
assertEquals(-1, getTypedKeycodeGuess("xxx", "y"));
assertEquals(-1, getTypedKeycodeGuess("foo", "bar"));
assertEquals(-1, getTypedKeycodeGuess("foo", "bars"));
assertEquals(-1, getTypedKeycodeGuess("foo", "ba"));
// Some characters also require modifiers so we have to check the full event.
KeyEvent ev = ImeAdapter.getTypedKeyEventGuess(null, "!");
assertEquals(KeyEvent.KEYCODE_1, ev.getKeyCode());
assertTrue(ev.isShiftPressed());
}
@SmallTest
......@@ -315,7 +326,6 @@ public class ImeTest extends ContentShellTestBase {
expectUpdateStateCall(mConnection);
setComposingText(mConnection, "h", 1);
assertEquals(KeyEvent.KEYCODE_H, mImeAdapter.mLastSyntheticKeyCode);
assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
assertUpdateStateCall(mConnection, 1000);
assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
......@@ -323,7 +333,6 @@ public class ImeTest extends ContentShellTestBase {
expectUpdateStateCall(mConnection);
setComposingText(mConnection, "ho", 1);
assertEquals(KeyEvent.KEYCODE_O, mImeAdapter.mLastSyntheticKeyCode);
assertEquals("ho", mConnection.getTextBeforeCursor(9, 0));
assertUpdateStateCall(mConnection, 1000);
assertEquals("ho", mConnection.getTextBeforeCursor(9, 0));
......@@ -331,10 +340,10 @@ public class ImeTest extends ContentShellTestBase {
expectUpdateStateCall(mConnection);
setComposingText(mConnection, "h", 1);
assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
assertUpdateStateCall(mConnection, 1000);
assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
setComposingRegion(mConnection, 0, 1); // DEL calls cancelComposition() then restarts
setComposingText(mConnection, "h", 1);
assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
// I
setComposingText(mConnection, "hi", 1);
......@@ -343,7 +352,7 @@ public class ImeTest extends ContentShellTestBase {
// SPACE
commitText(mConnection, "hi", 1);
assertEquals(0, mImeAdapter.mLastSyntheticKeyCode);
assertEquals(-1, mImeAdapter.mLastSyntheticKeyCode);
commitText(mConnection, " ", 1);
assertEquals(KeyEvent.KEYCODE_SPACE, mImeAdapter.mLastSyntheticKeyCode);
assertEquals("hi ", mConnection.getTextBeforeCursor(9, 0));
......@@ -365,7 +374,7 @@ public class ImeTest extends ContentShellTestBase {
assertEquals("", mConnection.getTextBeforeCursor(9, 0));
// DEL (on empty input)
deleteSurroundingText(mConnection, 1, 0); // BS on empty still sends 1,0
deleteSurroundingText(mConnection, 1, 0); // DEL on empty still sends 1,0
assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
assertEquals("", mConnection.getTextBeforeCursor(9, 0));
}
......@@ -447,7 +456,7 @@ public class ImeTest extends ContentShellTestBase {
assertEquals("", mConnection.getTextBeforeCursor(9, 0));
// DEL (on empty input)
deleteSurroundingText(mConnection, 1, 0); // BS on empty still sends 1,0
deleteSurroundingText(mConnection, 1, 0); // DEL on empty still sends 1,0
assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
assertEquals("", mConnection.getTextBeforeCursor(9, 0));
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment