Bug 1112212 - Send key events in onKeyMultiple; r=cpeterson
authorJim Chen <nchen@mozilla.com>
Tue, 16 Jun 2015 19:02:39 -0400
changeset 249202 860d548d92d0202485d68869f3ce3614becc1334
parent 249201 9a77983bf7b07e9ceb3cea80bea09faa5165ca2b
child 249203 0a03c1ef13d098287902a197db167b23577dcb14
push id61171
push usernchen@mozilla.com
push dateTue, 16 Jun 2015 23:02:58 +0000
treeherdermozilla-inbound@0a03c1ef13d0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpeterson
bugs1112212
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1112212 - Send key events in onKeyMultiple; r=cpeterson
mobile/android/base/GeckoEvent.java
mobile/android/base/GeckoInputConnection.java
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -239,19 +239,19 @@ public class GeckoEvent {
     public static GeckoEvent createAppForegroundingEvent() {
         return GeckoEvent.get(NativeGeckoEvent.APP_FOREGROUNDING);
     }
 
     public static GeckoEvent createNoOpEvent() {
         return GeckoEvent.get(NativeGeckoEvent.NOOP);
     }
 
-    public static GeckoEvent createKeyEvent(KeyEvent k, int metaState) {
+    public static GeckoEvent createKeyEvent(KeyEvent k, int action, int metaState) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.KEY_EVENT);
-        event.initKeyEvent(k, metaState);
+        event.initKeyEvent(k, action, metaState);
         return event;
     }
 
     public static GeckoEvent createCompositorCreateEvent(int width, int height) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.COMPOSITOR_CREATE);
         event.mWidth = width;
         event.mHeight = height;
         return event;
@@ -260,18 +260,21 @@ public class GeckoEvent {
     public static GeckoEvent createCompositorPauseEvent() {
         return GeckoEvent.get(NativeGeckoEvent.COMPOSITOR_PAUSE);
     }
 
     public static GeckoEvent createCompositorResumeEvent() {
         return GeckoEvent.get(NativeGeckoEvent.COMPOSITOR_RESUME);
     }
 
-    private void initKeyEvent(KeyEvent k, int metaState) {
-        mAction = k.getAction();
+    private void initKeyEvent(KeyEvent k, int action, int metaState) {
+        // Use a separate action argument so we can override the key's original action,
+        // e.g. change ACTION_MULTIPLE to ACTION_DOWN. That way we don't have to allocate
+        // a new key event just to change its action field.
+        mAction = action;
         mTime = k.getEventTime();
         // Normally we expect k.getMetaState() to reflect the current meta-state; however,
         // some software-generated key events may not have k.getMetaState() set, e.g. key
         // events from Swype. Therefore, it's necessary to combine the key's meta-states
         // with the meta-states that we keep separately in KeyListener
         mMetaState = k.getMetaState() | metaState;
         mFlags = k.getFlags();
         mKeyCode = k.getKeyCode();
@@ -601,17 +604,17 @@ public class GeckoEvent {
     public static GeckoEvent createIMEEvent(ImeAction action) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_EVENT);
         event.mAction = action.value;
         return event;
     }
 
     public static GeckoEvent createIMEKeyEvent(KeyEvent k) {
         GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.IME_KEY_EVENT);
-        event.initKeyEvent(k, 0);
+        event.initKeyEvent(k, k.getAction(), 0);
         return event;
     }
 
     public static GeckoEvent createIMEReplaceEvent(int start, int end, String text) {
         return createIMETextEvent(false, start, end, text);
     }
 
     public static GeckoEvent createIMEComposeEvent(int start, int end, String text) {
--- a/mobile/android/base/GeckoInputConnection.java
+++ b/mobile/android/base/GeckoInputConnection.java
@@ -572,18 +572,16 @@ class GeckoInputConnection
             "password".equalsIgnoreCase(mIMETypeHint))
             outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
         else if (mIMEState == IME_STATE_PLUGIN)
             outAttrs.inputType = InputType.TYPE_NULL; // "send key events" mode
         else if (mIMETypeHint.equalsIgnoreCase("url"))
             outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_URI;
         else if (mIMETypeHint.equalsIgnoreCase("email"))
             outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
-        else if (mIMETypeHint.equalsIgnoreCase("search"))
-            outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
         else if (mIMETypeHint.equalsIgnoreCase("tel"))
             outAttrs.inputType = InputType.TYPE_CLASS_PHONE;
         else if (mIMETypeHint.equalsIgnoreCase("number") ||
                  mIMETypeHint.equalsIgnoreCase("range"))
             outAttrs.inputType = InputType.TYPE_CLASS_NUMBER
                                  | InputType.TYPE_NUMBER_FLAG_SIGNED
                                  | InputType.TYPE_NUMBER_FLAG_DECIMAL;
         else if (mIMETypeHint.equalsIgnoreCase("week") ||
@@ -618,17 +616,18 @@ class GeckoInputConnection
         }
 
         if (mIMEActionHint.equalsIgnoreCase("go"))
             outAttrs.imeOptions = EditorInfo.IME_ACTION_GO;
         else if (mIMEActionHint.equalsIgnoreCase("done"))
             outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE;
         else if (mIMEActionHint.equalsIgnoreCase("next"))
             outAttrs.imeOptions = EditorInfo.IME_ACTION_NEXT;
-        else if (mIMEActionHint.equalsIgnoreCase("search"))
+        else if (mIMEActionHint.equalsIgnoreCase("search") ||
+                 mIMETypeHint.equalsIgnoreCase("search"))
             outAttrs.imeOptions = EditorInfo.IME_ACTION_SEARCH;
         else if (mIMEActionHint.equalsIgnoreCase("send"))
             outAttrs.imeOptions = EditorInfo.IME_ACTION_SEND;
         else if (mIMEActionHint.length() > 0) {
             if (DEBUG)
                 Log.w(LOGTAG, "Unexpected mIMEActionHint=\"" + mIMEActionHint + "\"");
             outAttrs.actionLabel = mIMEActionHint;
         }
@@ -798,23 +797,24 @@ class GeckoInputConnection
             event = GamepadUtils.translateSonyXperiaGamepadKeys(keyCode, event);
             keyCode = event.getKeyCode();
         }
 
         if (keyCode > KeyEvent.getMaxKeyCode() ||
             !shouldProcessKey(keyCode, event)) {
             return false;
         }
+        final int action = down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP;
         event = translateKey(keyCode, event);
         keyCode = event.getKeyCode();
 
         View view = getView();
         if (view == null) {
             InputThreadUtils.sInstance.sendEventFromUiThread(ThreadUtils.getUiHandler(),
-                mEditableClient, GeckoEvent.createKeyEvent(event, 0));
+                mEditableClient, GeckoEvent.createKeyEvent(event, action, 0));
             return true;
         }
 
         // KeyListener returns true if it handled the event for us. KeyListener is only
         // safe to use on the UI thread; therefore we need to pass a proxy Editable to it
         KeyListener keyListener = TextKeyListener.getInstance();
         Handler uiHandler = view.getRootView().getHandler();
         Editable uiEditable = InputThreadUtils.sInstance.
@@ -822,17 +822,17 @@ class GeckoInputConnection
         boolean skip = shouldSkipKeyListener(keyCode, event);
         if (down) {
             mEditableClient.setSuppressKeyUp(true);
         }
         if (skip ||
             (down && !keyListener.onKeyDown(view, uiEditable, keyCode, event)) ||
             (!down && !keyListener.onKeyUp(view, uiEditable, keyCode, event))) {
             InputThreadUtils.sInstance.sendEventFromUiThread(uiHandler, mEditableClient,
-                GeckoEvent.createKeyEvent(event, TextKeyListener.getMetaState(uiEditable)));
+                GeckoEvent.createKeyEvent(event, action, TextKeyListener.getMetaState(uiEditable)));
             if (skip && down) {
                 // Usually, the down key listener call above adjusts meta states for us.
                 // However, if we skip that call above, we have to manually adjust meta
                 // states so the meta states remain consistent
                 TextKeyListener.adjustMetaAfterKeypress(uiEditable);
             }
         }
         if (down) {
@@ -846,37 +846,53 @@ class GeckoInputConnection
         return processKey(keyCode, event, true);
     }
 
     @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         return processKey(keyCode, event, false);
     }
 
+    /**
+     * Get a key that represents a given character.
+     */
+    private KeyEvent getCharKeyEvent(final char c) {
+        final long time = SystemClock.uptimeMillis();
+        return new KeyEvent(time, time, KeyEvent.ACTION_MULTIPLE,
+                            KeyEvent.KEYCODE_UNKNOWN, /* repeat */ 0) {
+            @Override
+            public int getUnicodeChar() {
+                return c;
+            }
+
+            @Override
+            public int getUnicodeChar(int metaState) {
+                return c;
+            }
+        };
+    }
+
     @Override
     public boolean onKeyMultiple(int keyCode, int repeatCount, final KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
             // KEYCODE_UNKNOWN means the characters are in KeyEvent.getCharacters()
-            View view = getView();
-            if (view != null) {
-                InputThreadUtils.sInstance.runOnIcThread(
-                    view.getRootView().getHandler(), mEditableClient,
-                    new Runnable() {
-                        @Override public void run() {
-                            // Don't call GeckoInputConnection.commitText because it can
-                            // post a key event back to onKeyMultiple, causing a loop
-                            GeckoInputConnection.super.commitText(event.getCharacters(), 1);
-                        }
-                    });
+            final String str = event.getCharacters();
+            for (int i = 0; i < str.length(); i++) {
+                final KeyEvent charEvent = getCharKeyEvent(str.charAt(i));
+                if (!processKey(KeyEvent.KEYCODE_UNKNOWN, charEvent, /* down */ true) ||
+                    !processKey(KeyEvent.KEYCODE_UNKNOWN, charEvent, /* down */ false)) {
+                    return false;
+                }
             }
             return true;
         }
+
         while ((repeatCount--) != 0) {
-            if (!processKey(keyCode, event, true) ||
-                !processKey(keyCode, event, false)) {
+            if (!processKey(keyCode, event, /* down */ true) ||
+                !processKey(keyCode, event, /* down */ false)) {
                 return false;
             }
         }
         return true;
     }
 
     @Override
     public boolean onKeyLongPress(int keyCode, KeyEvent event) {