Bug 825120 - Fake selection update to force IME hard reset; r=cpeterson
authorJim Chen <nchen@mozilla.com>
Fri, 11 Jan 2013 15:34:58 -0500
changeset 118636 4e3e172789bac8cbda2552fef0df289f9a73ebb6
parent 118635 c12c5e31d72fefca30d83ed5bd14d577fefa1b67
child 118637 118918b2774e56e4a0ef828a538a582e4e5abc08
push idunknown
push userunknown
push dateunknown
reviewerscpeterson
bugs825120
milestone21.0a1
Bug 825120 - Fake selection update to force IME hard reset; r=cpeterson
mobile/android/base/GeckoInputConnection.java
mobile/android/base/InputMethods.java
--- a/mobile/android/base/GeckoInputConnection.java
+++ b/mobile/android/base/GeckoInputConnection.java
@@ -5,16 +5,17 @@
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.InputConnectionHandler;
 
 import android.R;
 import android.content.Context;
 import android.os.Build;
+import android.os.SystemClock;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.Selection;
 import android.text.method.KeyListener;
 import android.text.method.TextKeyListener;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -47,17 +48,17 @@ class GeckoInputConnection
     private String mCurrentInputMethod;
 
     private final GeckoEditableClient mEditableClient;
     protected int mBatchEditCount;
     private ExtractedTextRequest mUpdateRequest;
     private final ExtractedText mUpdateExtract = new ExtractedText();
     private boolean mBatchSelectionChanged;
     private boolean mBatchTextChanged;
-    private Runnable mRestartInputRunnable;
+    private long mLastRestartInputTime;
     private final InputConnection mPluginInputConnection;
 
     public static GeckoEditableListener create(View targetView,
                                                GeckoEditableClient editable) {
         if (DEBUG)
             return DebugGeckoInputConnection.create(targetView, editable);
         else
             return new GeckoInputConnection(targetView, editable);
@@ -195,37 +196,44 @@ class GeckoInputConnection
         final InputMethodManager imm = getInputMethodManager();
         if (imm != null) {
             final View v = getView();
             imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
         }
     }
 
     private void restartInput() {
-        if (mRestartInputRunnable != null) {
+        // Coalesce restartInput calls because InputMethodManager.restartInput()
+        // is expensive and successive calls to it can lock up the keyboard
+        long time = SystemClock.uptimeMillis();
+        if (time < mLastRestartInputTime + 200) {
             return;
         }
+        mLastRestartInputTime = time;
+
         final InputMethodManager imm = getInputMethodManager();
         if (imm == null) {
             return;
         }
-        mRestartInputRunnable = new Runnable() {
-            @Override
-            public void run() {
-                final View v = getView();
-                final Editable editable = getEditable();
-                // Fake a selection change, so that when we restart the input,
-                // the IME will make sure that any old composition string is cleared
-                notifySelectionChange(Selection.getSelectionStart(editable),
-                                      Selection.getSelectionEnd(editable));
-                imm.restartInput(v);
-                mRestartInputRunnable = null;
-            }
-        };
-        GeckoApp.mAppContext.mMainHandler.postDelayed(mRestartInputRunnable, 200);
+        final View v = getView();
+        // InputMethodManager has internal logic to detect if we are restarting input
+        // in an already focused View, which is the case here because all content text
+        // fields are inside one LayerView. When this happens, InputMethodManager will
+        // tell the input method to soft reset instead of hard reset. Stock latin IME
+        // on Android 4.2+ has a quirk that when it soft resets, it does not clear the
+        // composition. The following workaround tricks the IME into clearing the
+        // composition when soft resetting.
+        if (InputMethods.needsSoftResetWorkaround(mCurrentInputMethod)) {
+            // Fake a selection change, because the IME clears the composition when
+            // the selection changes, even if soft-resetting. Offsets here must be
+            // different from the previous selection offsets, and -1 seems to be a
+            // reasonable, deterministic value
+            notifySelectionChange(-1, -1);
+        }
+        imm.restartInput(v);
     }
 
     public void onTextChange(String text, int start, int oldEnd, int newEnd) {
 
         if (mUpdateRequest == null) {
             return;
         }
 
--- a/mobile/android/base/InputMethods.java
+++ b/mobile/android/base/InputMethods.java
@@ -17,16 +17,17 @@ import java.util.Locale;
 
 final class InputMethods {
 
     public static final String METHOD_ATOK = "com.justsystems.atokmobile.service/.AtokInputMethodService";
     public static final String METHOD_GOOGLE_JAPANESE_INPUT = "com.google.android.inputmethod.japanese/.MozcService";
     public static final String METHOD_IWNN = "jp.co.omronsoft.iwnnime.ml/.standardcommon.IWnnLanguageSwitcher";
     public static final String METHOD_OPENWNN_PLUS = "com.owplus.ime.openwnnplus/.OpenWnnJAJP";
     public static final String METHOD_SIMEJI = "com.adamrocker.android.input.simeji/.OpenWnnSimeji";
+    public static final String METHOD_STOCK_LATINIME = "com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME";
     public static final String METHOD_SWYPE = "com.swype.android.inputmethod/.SwypeInputMethod";
     public static final String METHOD_SWYPE_BETA = "com.nuance.swype.input/.IME";
 
     /* These input method names are currently unused, but kept here for future reference:
     public static final String METHOD_EYES_FREE_KEYBOARD = "com.googlecode.eyesfree.inputmethod.latin/.LatinIME";
     public static final String METHOD_GO_KEYBOARD = "com.jb.gokeyboard/.GoKeyboard";
     public static final String METHOD_GOOGLE_PINYIN = "com.google.android.inputmethod.pinyin/.PinyinIME";
     public static final String METHOD_GOOGLE_TALKBACK = "com.google.android.marvin.talkback/com.googlecode.eyesfree.inputmethod.latin.LatinIME";
@@ -78,9 +79,15 @@ final class InputMethods {
         if (sIsPreJellyBeanAsusTransformer == null) {
             sIsPreJellyBeanAsusTransformer = Build.VERSION.SDK_INT < 16 &&
                                              "asus".equals(Build.BRAND) &&
                                              "EeePad".equals(Build.BOARD);
         }
         // The locale may change while Firefox is running, but the device and OS should not. :)
         return sIsPreJellyBeanAsusTransformer && !Locale.getDefault().equals(Locale.US);
     }
+
+    public static boolean needsSoftResetWorkaround(String inputMethod) {
+        // Stock latin IME on Android 4.2 and above
+        return Build.VERSION.SDK_INT >= 17 &&
+               inputMethod.equals(METHOD_STOCK_LATINIME);
+    }
 }