Bug 835906 - Let GeckoInputConnection switch to background thread; r=cpeterson
authorJim Chen <nchen@mozilla.com>
Fri, 08 Feb 2013 16:07:12 -0500
changeset 131256 1579121d48d2429f1d555d171ade3baf105d354b
parent 131255 364d4dd9ffbdd104aa668f891ee143d18204e6ad
child 131257 97f6e73073dd79cee68a5dcb91e8288a8d8cf8f3
push id2323
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 19:47:02 +0000
treeherdermozilla-beta@7712be144d91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpeterson
bugs835906
milestone21.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 835906 - Let GeckoInputConnection switch to background thread; r=cpeterson
mobile/android/base/GeckoInputConnection.java
mobile/android/base/gfx/InputConnectionHandler.java
mobile/android/base/gfx/LayerView.java
--- a/mobile/android/base/GeckoInputConnection.java
+++ b/mobile/android/base/GeckoInputConnection.java
@@ -5,16 +5,18 @@
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.gfx.InputConnectionHandler;
 
 import android.R;
 import android.content.Context;
 import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
 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;
@@ -35,16 +37,18 @@ class GeckoInputConnection
     extends BaseInputConnection
     implements InputConnectionHandler, GeckoEditableListener {
 
     private static final boolean DEBUG = false;
     protected static final String LOGTAG = "GeckoInputConnection";
 
     private static final int INLINE_IME_MIN_DISPLAY_SIZE = 480;
 
+    private static Handler sBackgroundHandler;
+
     // Managed only by notifyIMEEnabled; see comments in notifyIMEEnabled
     private int mIMEState;
     private String mIMETypeHint = "";
     private String mIMEModeHint = "";
     private String mIMEActionHint = "";
 
     private String mCurrentInputMethod = "";
 
@@ -305,17 +309,90 @@ class GeckoInputConnection
             return;
         }
         final View v = getView();
         final Editable editable = getEditable();
         imm.updateSelection(v, start, end, getComposingSpanStart(editable),
                             getComposingSpanEnd(editable));
     }
 
+    private static synchronized Handler getBackgroundHandler() {
+        if (sBackgroundHandler != null) {
+            return sBackgroundHandler;
+        }
+        // Don't use GeckoBackgroundThread because Gecko thread may block waiting on
+        // GeckoBackgroundThread. If we were to use GeckoBackgroundThread, due to IME,
+        // GeckoBackgroundThread may end up also block waiting on Gecko thread and a
+        // deadlock occurs
+        Thread backgroundThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                Looper.prepare();
+                synchronized (GeckoInputConnection.class) {
+                    sBackgroundHandler = new Handler();
+                    GeckoInputConnection.class.notify();
+                }
+                Looper.loop();
+                sBackgroundHandler = null;
+            }
+        });
+        backgroundThread.setDaemon(true);
+        backgroundThread.start();
+        while (sBackgroundHandler == null) {
+            try {
+                // wait for new thread to set sBackgroundHandler
+                GeckoInputConnection.class.wait();
+            } catch (InterruptedException e) {
+            }
+        }
+        return sBackgroundHandler;
+    }
+
+    private boolean canReturnCustomHandler() {
+        if (mIMEState == IME_STATE_DISABLED) {
+            return false;
+        }
+        for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
+            // We only return our custom Handler to InputMethodManager's InputConnection
+            // proxy. For all other purposes, we return the regular Handler.
+            // InputMethodManager retrieves the Handler for its InputConnection proxy
+            // inside its method startInputInner(), so we check for that here. This is
+            // valid from Android 2.2 to at least Android 4.2. If this situation ever
+            // changes, we gracefully fall back to using the regular Handler.
+            if ("startInputInner".equals(frame.getMethodName()) &&
+                "android.view.inputmethod.InputMethodManager".equals(frame.getClassName())) {
+                // only return our own Handler to InputMethodManager
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Handler getHandler(Handler defHandler) {
+        if (!canReturnCustomHandler()) {
+            return defHandler;
+        }
+        // getBackgroundHandler() is synchronized and requires locking,
+        // but if we already have our handler, we don't have to lock
+        final Handler newHandler = sBackgroundHandler != null
+                                 ? sBackgroundHandler
+                                 : getBackgroundHandler();
+        if (mEditableClient.setInputConnectionHandler(newHandler)) {
+            return newHandler;
+        }
+        // Setting new IC handler failed; return old IC handler
+        return mEditableClient.getInputConnectionHandler();
+    }
+
     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        if (mIMEState == IME_STATE_DISABLED) {
+            return null;
+        }
+
         outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
         outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE;
         outAttrs.actionLabel = null;
 
         if (mIMEState == IME_STATE_PASSWORD)
             outAttrs.inputType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
         else if (mIMEState == IME_STATE_PLUGIN)
             outAttrs.inputType = InputType.TYPE_NULL; // "send key events" mode
--- a/mobile/android/base/gfx/InputConnectionHandler.java
+++ b/mobile/android/base/gfx/InputConnectionHandler.java
@@ -1,20 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
+import android.os.Handler;
 import android.view.KeyEvent;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 
 public interface InputConnectionHandler
 {
+    Handler getHandler(Handler defHandler);
     InputConnection onCreateInputConnection(EditorInfo outAttrs);
     boolean onKeyPreIme(int keyCode, KeyEvent event);
     boolean onKeyDown(int keyCode, KeyEvent event);
     boolean onKeyLongPress(int keyCode, KeyEvent event);
     boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event);
     boolean onKeyUp(int keyCode, KeyEvent event);
     boolean isIMEEnabled();
 }
--- a/mobile/android/base/gfx/LayerView.java
+++ b/mobile/android/base/gfx/LayerView.java
@@ -15,16 +15,17 @@ import org.mozilla.gecko.GeckoAccessibil
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.os.Handler;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.TextureView;
 import android.view.View;
@@ -230,16 +231,23 @@ public class LayerView extends FrameLayo
     }
 
     public void setInputConnectionHandler(InputConnectionHandler inputConnectionHandler) {
         mInputConnectionHandler = inputConnectionHandler;
         mLayerClient.forceRedraw();
     }
 
     @Override
+    public Handler getHandler() {
+        if (mInputConnectionHandler != null)
+            return mInputConnectionHandler.getHandler(super.getHandler());
+        return super.getHandler();
+    }
+
+    @Override
     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
         if (mInputConnectionHandler != null)
             return mInputConnectionHandler.onCreateInputConnection(outAttrs);
         return null;
     }
 
     @Override
     public boolean onKeyPreIme(int keyCode, KeyEvent event) {