Bug 1112212 - Send key events in onKeyMultiple; r=cpeterson
authorJim Chen <nchen@mozilla.com>
Sat, 10 Jan 2015 12:41:58 -0500
changeset 223189 3cfe8379a8a55ed1eb5586aca7a1f1650dcfce59
parent 223188 779b73087f78ad76aba77dd3d01a5f0a20f2c261
child 223190 ee29d84c968a826c02463a76c5a8361ff001ae1f
push id10769
push usercbook@mozilla.com
push dateMon, 12 Jan 2015 14:15:52 +0000
treeherderfx-team@0e9765732906 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpeterson
bugs1112212
milestone37.0a1
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
@@ -258,19 +258,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;
@@ -279,18 +279,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();
@@ -615,17 +618,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
@@ -810,23 +810,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.
@@ -834,17 +835,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) {
@@ -858,37 +859,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) {