Bug 576065 - Fix Android IME implementation, r=mwu a=blocking-fennec
authorJim Chen <jchen@mozilla.com>
Wed, 04 Aug 2010 12:47:26 -0700
changeset 48868 6cc5db7e2fad25e597e40bb77ab8a2a5300248fe
parent 48867 bb1553eca910b39cb6ebf96a957b538ce8f0c79d
child 48870 5be15126f539fa02489e9bbb423183642f5a902d
push idunknown
push userunknown
push dateunknown
reviewersmwu, blocking-fennec
bugs576065
milestone2.0b4pre
Bug 576065 - Fix Android IME implementation, r=mwu a=blocking-fennec
embedding/android/GeckoApp.java
embedding/android/GeckoAppShell.java
embedding/android/GeckoEvent.java
embedding/android/GeckoInputConnection.java
embedding/android/GeckoSurfaceView.java
widget/src/android/AndroidBridge.cpp
widget/src/android/AndroidBridge.h
widget/src/android/AndroidJavaWrappers.cpp
widget/src/android/AndroidJavaWrappers.h
widget/src/android/nsWindow.cpp
widget/src/android/nsWindow.h
--- a/embedding/android/GeckoApp.java
+++ b/embedding/android/GeckoApp.java
@@ -234,16 +234,24 @@ abstract public class GeckoApp
                     return true;
                 } else {
                     return false;
                 }
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_SEARCH:
                 return false;
+            case KeyEvent.KEYCODE_DEL:
+                // See comments in GeckoInputConnection.onKeyDel
+                if (surfaceView != null &&
+                    surfaceView.inputConnection != null &&
+                    surfaceView.inputConnection.onKeyDel()) {
+                    return true;
+                }
+                break;
             default:
                 break;
         }
         GeckoAppShell.sendEventToGecko(new GeckoEvent(event));
         return true;
     }
 
     public boolean onKeyUp(int keyCode, KeyEvent event) {
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -67,17 +67,23 @@ class GeckoAppShell
 
     // static members only
     private GeckoAppShell() { }
 
     static boolean sGeckoRunning;
 
     static private boolean gRestartScheduled = false;
 
-    static protected Timer mSoftKBTimer;
+    static private final Timer mIMETimer = new Timer();
+
+    static private final int NOTIFY_IME_RESETINPUTSTATE = 0;
+    static private final int NOTIFY_IME_SETOPENSTATE = 1;
+    static private final int NOTIFY_IME_SETENABLED = 2;
+    static private final int NOTIFY_IME_CANCELCOMPOSITION = 3;
+    static private final int NOTIFY_IME_FOCUSCHANGE = 4;
 
     /* The Android-side API: API methods that Android calls */
 
     // Initialization methods
     public static native void nativeInit();
     public static native void nativeRun(String args);
 
     // helper methods
@@ -191,37 +197,111 @@ class GeckoAppShell
             e = new GeckoEvent(GeckoEvent.DRAW, new Rect(x, y, w, h));
         }
 
         e.mNativeWindow = nativeWindow;
 
         sendEventToGecko(e);
     }
 
-    public static void showIME(int state) {
-        GeckoApp.surfaceView.mIMEState = state;
+    /* Delay updating IME states (see bug 573800) */
+    private static final class IMEStateUpdater extends TimerTask
+    {
+        static private IMEStateUpdater instance;
+        private boolean mEnable, mReset;
+
+        static private IMEStateUpdater getInstance() {
+            if (instance == null) {
+                instance = new IMEStateUpdater();
+                mIMETimer.schedule(instance, 200);
+            }
+            return instance;
+        }
+
+        static public synchronized void enableIME() {
+            getInstance().mEnable = true;
+        }
+
+        static public synchronized void resetIME() {
+            getInstance().mReset = true;
+        }
 
-        if (mSoftKBTimer == null) {
-            mSoftKBTimer = new Timer();
-            mSoftKBTimer.schedule(new TimerTask() {
-                public void run() {
-                    InputMethodManager imm = (InputMethodManager) 
-                        GeckoApp.surfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        public void run() {
+            synchronized(IMEStateUpdater.class) {
+                instance = null;
+            }
+
+            InputMethodManager imm = (InputMethodManager) 
+                GeckoApp.surfaceView.getContext().getSystemService(
+                    Context.INPUT_METHOD_SERVICE);
+            if (imm == null)
+                return;
+
+            if (mReset)
+                imm.restartInput(GeckoApp.surfaceView);
+
+            if (!mEnable)
+                return;
+
+            if (GeckoApp.surfaceView.mIMEState != 0)
+                imm.showSoftInput(GeckoApp.surfaceView, 0);
+            else
+                imm.hideSoftInputFromWindow(
+                    GeckoApp.surfaceView.getWindowToken(), 0);
+        }
+    }
 
-                    if (GeckoApp.surfaceView.mIMEState != 0)
-                        imm.showSoftInput(GeckoApp.surfaceView, 0);
-                    else
-                        imm.hideSoftInputFromWindow(GeckoApp.surfaceView.getWindowToken(), 0);
-                    mSoftKBTimer = null;
-                    
-                }
-            }, 200);
+    public static void notifyIME(int type, int state) {
+        if (GeckoApp.surfaceView == null)
+            return;
+
+        switch (type) {
+        case NOTIFY_IME_RESETINPUTSTATE:
+            IMEStateUpdater.resetIME();
+            // keep current enabled state
+            IMEStateUpdater.enableIME();
+            break;
+
+        case NOTIFY_IME_SETENABLED:
+            /* When IME is 'disabled', IME processing is disabled.
+                In addition, the IME UI is hidden */
+            GeckoApp.surfaceView.mIMEState = state;
+            IMEStateUpdater.enableIME();
+            break;
+
+        case NOTIFY_IME_CANCELCOMPOSITION:
+            IMEStateUpdater.resetIME();
+            break;
+
+        case NOTIFY_IME_FOCUSCHANGE:
+            GeckoApp.surfaceView.mIMEFocus = state != 0;
+            break;
+
         }
     }
 
+    public static void notifyIMEChange(String text, int start, int end, int newEnd) {
+        if (GeckoApp.surfaceView == null ||
+            GeckoApp.surfaceView.inputConnection == null)
+            return;
+
+        InputMethodManager imm = (InputMethodManager) 
+            GeckoApp.surfaceView.getContext().getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        if (imm == null)
+            return;
+
+        if (newEnd < 0)
+            GeckoApp.surfaceView.inputConnection.notifySelectionChange(
+                imm, start, end);
+        else
+            GeckoApp.surfaceView.inputConnection.notifyTextChange(
+                imm, text, start, end, newEnd);
+    }
+
     public static void enableAccelerometer(boolean enable) {
         SensorManager sm = (SensorManager) 
             GeckoApp.surfaceView.getContext().getSystemService(Context.SENSOR_SERVICE);
 
         if (enable) {
             Sensor accelSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
             if (accelSensor == null)
                 return;
@@ -249,19 +329,19 @@ class GeckoAppShell
             lm.removeUpdates(GeckoApp.surfaceView);
         }
     }
 
     public static void moveTaskToBack() {
         GeckoApp.mAppContext.moveTaskToBack(true);
     }
 
-    public static void returnIMEQueryResult(String result, int selectionStart, int selectionEnd) {
+    public static void returnIMEQueryResult(String result, int selectionStart, int selectionLength) {
         GeckoApp.surfaceView.inputConnection.mSelectionStart = selectionStart;
-        GeckoApp.surfaceView.inputConnection.mSelectionEnd = selectionEnd;
+        GeckoApp.surfaceView.inputConnection.mSelectionLength = selectionLength;
         try {
             GeckoApp.surfaceView.inputConnection.mQueryResult.put(result);
         } catch (InterruptedException e) {
         }
     }
 
     static void onXreExit() {
         sGeckoRunning = false;
--- a/embedding/android/GeckoEvent.java
+++ b/embedding/android/GeckoEvent.java
@@ -62,33 +62,48 @@ public class GeckoEvent {
     public static final int LOCATION_EVENT = 4;
     public static final int IME_EVENT = 5;
     public static final int DRAW = 6;
     public static final int SIZE_CHANGED = 7;
     public static final int ACTIVITY_STOPPING = 8;
     public static final int ACTIVITY_PAUSING = 9;
     public static final int LOAD_URI = 10;
 
-    public static final int IME_BATCH_END = 0;
-    public static final int IME_BATCH_BEGIN = 1;
+    public static final int IME_COMPOSITION_END = 0;
+    public static final int IME_COMPOSITION_BEGIN = 1;
     public static final int IME_SET_TEXT = 2;
     public static final int IME_GET_TEXT = 3;
     public static final int IME_DELETE_TEXT = 4;
+    public static final int IME_SET_SELECTION = 5;
+    public static final int IME_GET_SELECTION = 6;
+    public static final int IME_ADD_RANGE = 7;
+
+    public static final int IME_RANGE_CARETPOSITION = 1;
+    public static final int IME_RANGE_RAWINPUT = 2;
+    public static final int IME_RANGE_SELECTEDRAWTEXT = 3;
+    public static final int IME_RANGE_CONVERTEDTEXT = 4;
+    public static final int IME_RANGE_SELECTEDCONVERTEDTEXT = 5;
+
+    public static final int IME_RANGE_UNDERLINE = 1;
+    public static final int IME_RANGE_FORECOLOR = 2;
+    public static final int IME_RANGE_BACKCOLOR = 4;
 
     public int mType;
     public int mAction;
     public long mTime;
     public Point mP0, mP1;
     public Rect mRect;
     public float mX, mY, mZ;
 
     public int mMetaState, mFlags;
     public int mKeyCode, mUnicodeChar;
-    public int mCount, mCount2;
+    public int mOffset, mCount;
     public String mCharacters;
+    public int mRangeType, mRangeStyles;
+    public int mRangeForeColor, mRangeBackColor;
     public Location mLocation;
 
     public int mNativeWindow;
 
     public GeckoEvent() {
         mType = NATIVE_POKE;
     }
 
@@ -124,39 +139,50 @@ public class GeckoEvent {
         mZ = s.values[2] / SensorManager.GRAVITY_EARTH;
     }
 
     public GeckoEvent(Location l) {
         mType = LOCATION_EVENT;
         mLocation = l;
     }
 
-    public GeckoEvent(boolean batchEdit, String text) {
+    public GeckoEvent(int imeAction, int offset, int count) {
+        mType = IME_EVENT;
+        mAction = imeAction;
+        mOffset = offset;
+        mCount = count;
+    }
+
+    private void InitIMERange(int action, int offset, int count,
+                              int rangeType, int rangeStyles,
+                              int rangeForeColor, int rangeBackColor) {
         mType = IME_EVENT;
-        if (text != null)
-            mAction = IME_SET_TEXT;
-        else
-            mAction = batchEdit ? IME_BATCH_BEGIN : IME_BATCH_END;
+        mAction = action;
+        mOffset = offset;
+        mCount = count;
+        mRangeType = rangeType;
+        mRangeStyles = rangeStyles;
+        mRangeForeColor = rangeForeColor;
+        mRangeBackColor = rangeBackColor;
+        return;
+    }
+    
+    public GeckoEvent(int offset, int count,
+                      int rangeType, int rangeStyles,
+                      int rangeForeColor, int rangeBackColor, String text) {
+        InitIMERange(IME_SET_TEXT, offset, count, rangeType, rangeStyles,
+                     rangeForeColor, rangeBackColor);
         mCharacters = text;
     }
 
-    public GeckoEvent(boolean forward, int count) {
-        mType = IME_EVENT;
-        mAction = IME_GET_TEXT;
-        if (forward)
-            mCount = count;
-        else
-            mCount2 = count;
-    }
-
-    public GeckoEvent(int leftLen, int rightLen) {
-        mType = IME_EVENT;
-        mAction = IME_DELETE_TEXT;
-        mCount = leftLen;
-        mCount2 = rightLen;
+    public GeckoEvent(int offset, int count,
+                      int rangeType, int rangeStyles,
+                      int rangeForeColor, int rangeBackColor) {
+        InitIMERange(IME_ADD_RANGE, offset, count, rangeType, rangeStyles,
+                     rangeForeColor, rangeBackColor);
     }
 
     public GeckoEvent(int etype, Rect dirty) {
         if (etype != DRAW) {
             mType = INVALID;
             return;
         }
 
--- a/embedding/android/GeckoInputConnection.java
+++ b/embedding/android/GeckoInputConnection.java
@@ -40,135 +40,441 @@ package org.mozilla.gecko;
 import java.io.*;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
 
 import android.os.*;
 import android.app.*;
 import android.text.*;
+import android.text.style.*;
 import android.view.*;
 import android.view.inputmethod.*;
 import android.content.*;
 
 import android.util.*;
 
 public class GeckoInputConnection
     extends BaseInputConnection
 {
     public GeckoInputConnection (View targetView) {
         super(targetView, true);
         mQueryResult = new SynchronousQueue<String>();
-        mExtractedText.partialStartOffset = -1;
-        mExtractedText.partialEndOffset = -1;
-    }
-
-    @Override
-    public Editable getEditable() {
-        Log.i("GeckoAppJava", "getEditable");
-        return null;
     }
 
     @Override
     public boolean beginBatchEdit() {
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(true, null));
-        return true;
-    }
-    @Override
-    public boolean commitCompletion(CompletionInfo text) {
-        Log.i("GeckoAppJava", "Stub: commitCompletion");
-        return true;
-    }
-    @Override
-    public boolean commitText(CharSequence text, int newCursorPosition) {
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(true, text.toString()));
-        endBatchEdit();
+        Log.d("GeckoAppJava", "IME: beginBatchEdit");
+
         return true;
     }
-    @Override
-    public boolean deleteSurroundingText(int leftLength, int rightLength) {
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(leftLength, rightLength));
-        updateExtractedText();
-        return true;
-    }
+
     @Override
-    public boolean endBatchEdit() {
-        updateExtractedText();
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(false, null));
-        return true;
+    public boolean commitCompletion(CompletionInfo text) {
+        Log.d("GeckoAppJava", "IME: commitCompletion");
+
+        return commitText(text.getText(), 1);
     }
+
     @Override
-    public boolean finishComposingText() {
-        endBatchEdit();
+    public boolean commitText(CharSequence text, int newCursorPosition) {
+        //Log.d("GeckoAppJava", "IME: commitText");
+
+        setComposingText(text, newCursorPosition);
+        finishComposingText();
+
         return true;
     }
-    @Override
-    public int getCursorCapsMode(int reqModes) {
-        return 0;
-    }
+
     @Override
-    public ExtractedText getExtractedText(ExtractedTextRequest req, int flags) {
-        mExtractToken = req.token;
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(false, 0));
-        try {
-            mExtractedText.text = mQueryResult.take();
-            mExtractedText.selectionStart = mSelectionStart;
-            mExtractedText.selectionEnd = mSelectionEnd;
-        } catch (InterruptedException e) {
-            Log.i("GeckoAppJava", "getExtractedText: Interrupted!");
+    public boolean deleteSurroundingText(int leftLength, int rightLength) {
+        Log.d("GeckoAppJava", "IME: deleteSurroundingText");
+
+        /* deleteSurroundingText is supposed to ignore the composing text,
+            so we cancel any pending composition, delete the text, and then
+            restart the composition */
+
+        if (mComposing) {
+            // Cancel current composition
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(0, 0, 0, 0, 0, 0, null));
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0));
         }
-        return mExtractedText;
-    }
-    @Override
-    public CharSequence getTextAfterCursor(int length, int flags) {
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(true, length));
+
+        // Select text to be deleted
+        int delStart, delLen;
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
         try {
-            String result = mQueryResult.take();
-            return result;
+            mQueryResult.take();
         } catch (InterruptedException e) {
-            Log.i("GeckoAppJava", "getTextAfterCursor: Interrupted!");
+            Log.e("GeckoAppJava", "IME: deleteSurroundingText interrupted");
+            return false;
         }
-        return null;
-    }
-    @Override
-    public CharSequence getTextBeforeCursor(int length, int flags) {
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(false, length));
-        try {
-            String result = mQueryResult.take();
-            return result;
-        } catch (InterruptedException e) {
-            Log.i("GeckoAppJava", "getTextBeforeCursor: Interrupted!");
+        delStart = mSelectionStart > leftLength ?
+                    mSelectionStart - leftLength : 0;
+        delLen = mSelectionStart + rightLength - delStart;
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_SET_SELECTION, delStart, delLen));
+
+        // Restore composition / delete text
+        if (mComposing) {
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_COMPOSITION_BEGIN, 0, 0));
+            if (mComposingText.length() > 0) {
+                /* IME_SET_TEXT doesn't work well with empty strings */
+                GeckoAppShell.sendEventToGecko(
+                    new GeckoEvent(0, mComposingText.length(),
+                                   GeckoEvent.IME_RANGE_RAWINPUT,
+                                   GeckoEvent.IME_RANGE_UNDERLINE, 0, 0,
+                                   mComposingText.toString()));
+            }
+        } else {
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_DELETE_TEXT, 0, 0));
         }
-        return null;
-    }
-    @Override
-    public boolean setComposingText(CharSequence text, int newCursorPosition) {
-        beginBatchEdit();
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(true, text.toString()));
         return true;
     }
+
     @Override
-    public boolean setSelection(int start, int end) {
-        Log.i("GeckoAppJava", "Stub: setSelection " + start + " " + end);
+    public boolean endBatchEdit() {
+        Log.d("GeckoAppJava", "IME: endBatchEdit");
+
+        return true;
+    }
+
+    @Override
+    public boolean finishComposingText() {
+        //Log.d("GeckoAppJava", "IME: finishComposingText");
+
+        if (mComposing) {
+            // Set style to none
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(0, mComposingText.length(),
+                               GeckoEvent.IME_RANGE_RAWINPUT, 0, 0, 0,
+                               mComposingText));
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_COMPOSITION_END, 0, 0));
+            mComposing = false;
+            mComposingText = null;
+
+            // Make sure caret stays at the same position
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_SET_SELECTION,
+                               mCompositionStart + mCompositionSelStart, 0));
+        }
         return true;
     }
 
-    private void updateExtractedText() {
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(false, 0));
+    @Override
+    public int getCursorCapsMode(int reqModes) {
+        //Log.d("GeckoAppJava", "IME: getCursorCapsMode");
+
+        return 0;
+    }
+
+    @Override
+    public Editable getEditable() {
+        Log.w("GeckoAppJava", "IME: getEditable called from " +
+            Thread.currentThread().getStackTrace()[0].toString());
+
+        return null;
+    }
+
+    @Override
+    public ExtractedText getExtractedText(ExtractedTextRequest req, int flags) {
+        if (req == null)
+            return null;
+
+        //Log.d("GeckoAppJava", "IME: getExtractedText");
+
+        ExtractedText extract = new ExtractedText();
+        extract.flags = 0;
+        extract.partialStartOffset = -1;
+        extract.partialEndOffset = -1;
+
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
+        try {
+            mQueryResult.take();
+        } catch (InterruptedException e) {
+            Log.e("GeckoAppJava", "IME: getExtractedText interrupted");
+            return null;
+        }
+        extract.selectionStart = mSelectionStart;
+        extract.selectionEnd = mSelectionStart + mSelectionLength;
+
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_GET_TEXT, 0, Integer.MAX_VALUE));
         try {
-            mExtractedText.text = mQueryResult.take();
-            mExtractedText.selectionStart = mSelectionStart;
-            mExtractedText.selectionEnd = mSelectionEnd;
+            extract.startOffset = 0;
+            extract.text = mQueryResult.take();
+
+            if ((flags & GET_EXTRACTED_TEXT_MONITOR) != 0)
+                mUpdateRequest = req;
+            return extract;
+
+        } catch (InterruptedException e) {
+            Log.e("GeckoAppJava", "IME: getExtractedText interrupted");
+            return null;
+        }
+    }
+
+    @Override
+    public CharSequence getTextAfterCursor(int length, int flags) {
+        //Log.d("GeckoAppJava", "IME: getTextAfterCursor");
+
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
+        try {
+            mQueryResult.take();
         } catch (InterruptedException e) {
-            Log.i("GeckoAppJava", "getExtractedText: Interrupted!");
+            Log.e("GeckoAppJava", "IME: getTextBefore/AfterCursor interrupted");
+            return null;
+        }
+
+        /* Compatible with both positive and negative length
+            (no need for separate code for getTextBeforeCursor) */
+        int textStart = length > 0 ? mSelectionStart :
+            mSelectionStart + length > 0 ? mSelectionStart + length : 0;
+        int textLength = length > 0 ? length:
+            textStart == 0 ? mSelectionStart : length;
+
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(GeckoEvent.IME_GET_TEXT, textStart, textLength));
+        try {
+            return mQueryResult.take();
+        } catch (InterruptedException e) {
+            Log.e("GeckoAppJava", "IME: getTextBefore/AfterCursor: Interrupted!");
+            return null;
+        }
+    }
+
+    @Override
+    public CharSequence getTextBeforeCursor(int length, int flags) {
+        //Log.d("GeckoAppJava", "IME: getTextBeforeCursor");
+
+        return getTextAfterCursor(-length, flags);
+    }
+
+    @Override
+    public boolean setComposingText(CharSequence text, int newCursorPosition) {
+        //Log.d("GeckoAppJava", "IME: setComposingText");
+
+        // Set new composing text
+        mComposingText = text != null ? text.toString() : "";
+
+        if (!mComposing) {
+            // Get current selection
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_GET_SELECTION, 0, 0));
+            try {
+                mQueryResult.take();
+            } catch (InterruptedException e) {
+                Log.e("GeckoAppJava", "IME: setComposingText interrupted");
+                return false;
+            }
+            // Make sure we are in a composition
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_COMPOSITION_BEGIN, 0, 0));
+            mComposing = true;
+            mCompositionStart = mSelectionLength >= 0 ?
+                mSelectionStart : mSelectionStart + mSelectionLength;
         }
 
-        InputMethodManager imm = (InputMethodManager)
-            GeckoApp.surfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-        imm.updateExtractedText(GeckoApp.surfaceView, mExtractToken, mExtractedText);
+        // Set new selection
+        // New selection should be within the composition
+        mCompositionSelStart = newCursorPosition > 0 ? mComposingText.length() : 0;
+        mCompositionSelLen = 0;
+
+        // Handle composition text styles
+        if (text != null && text instanceof Spanned) {
+            Spanned span = (Spanned) text;
+            int spanStart = 0, spanEnd = 0;
+            boolean pastSelStart = false, pastSelEnd = false;
+
+            do {
+                int rangeType = GeckoEvent.IME_RANGE_CONVERTEDTEXT;
+                int rangeStyles = 0, rangeForeColor = 0, rangeBackColor = 0;
+
+                // Find next offset where there is a style transition
+                spanEnd = span.nextSpanTransition(spanStart + 1, text.length(),
+                    CharacterStyle.class);
+
+                // We need to count the selection as a transition
+                if (mCompositionSelLen >= 0) {
+                    if (!pastSelStart && spanEnd >= mCompositionSelStart) {
+                        spanEnd = mCompositionSelStart;
+                        pastSelStart = true;
+                    } else if (!pastSelEnd && spanEnd >=
+                            mCompositionSelStart + mCompositionSelLen) {
+                        spanEnd = mCompositionSelStart + mCompositionSelLen;
+                        pastSelEnd = true;
+                        rangeType = GeckoEvent.IME_RANGE_SELECTEDRAWTEXT;
+                    }
+                } else {
+                    if (!pastSelEnd && spanEnd >=
+                            mCompositionSelStart + mCompositionSelLen) {
+                        spanEnd = mCompositionSelStart + mCompositionSelLen;
+                        pastSelEnd = true;
+                    } else if (!pastSelStart &&
+                            spanEnd >= mCompositionSelStart) {
+                        spanEnd = mCompositionSelStart;
+                        pastSelStart = true;
+                        rangeType = GeckoEvent.IME_RANGE_SELECTEDRAWTEXT;
+                    }
+                }
+                // Empty range, continue
+                if (spanEnd <= spanStart)
+                    continue;
+
+                // Get and iterate through list of span objects within range
+                CharacterStyle styles[] = span.getSpans(
+                    spanStart, spanEnd, CharacterStyle.class);
+
+                for (CharacterStyle style : styles) {
+                    if (style instanceof UnderlineSpan) {
+                        // Text should be underlined
+                        rangeStyles |= GeckoEvent.IME_RANGE_UNDERLINE;
+
+                    } else if (style instanceof ForegroundColorSpan) {
+                        // Text should be of a different foreground color
+                        rangeStyles |= GeckoEvent.IME_RANGE_FORECOLOR;
+                        rangeForeColor =
+                            ((ForegroundColorSpan)style).getForegroundColor();
+
+                    } else if (style instanceof BackgroundColorSpan) {
+                        // Text should be of a different background color
+                        rangeStyles |= GeckoEvent.IME_RANGE_BACKCOLOR;
+                        rangeBackColor =
+                            ((BackgroundColorSpan)style).getBackgroundColor();
+                    }
+                }
+
+                // Add range to array, the actual styles are
+                //  applied when IME_SET_TEXT is sent
+                GeckoAppShell.sendEventToGecko(
+                    new GeckoEvent(spanStart, spanEnd - spanStart,
+                                   rangeType, rangeStyles,
+                                   rangeForeColor, rangeBackColor));
+
+                spanStart = spanEnd;
+            } while (spanStart < text.length());
+        } else {
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(0, text == null ? 0 : text.length(),
+                               GeckoEvent.IME_RANGE_RAWINPUT,
+                               GeckoEvent.IME_RANGE_UNDERLINE, 0, 0));
+        }
+
+        // Change composition (treating selection end as where the caret is)
+        GeckoAppShell.sendEventToGecko(
+            new GeckoEvent(mCompositionSelStart + mCompositionSelLen, 0,
+                           GeckoEvent.IME_RANGE_CARETPOSITION, 0, 0, 0,
+                           mComposingText));
+        return true;
     }
 
-    int mExtractToken;
-    final ExtractedText mExtractedText = new ExtractedText();
+    @Override
+    public boolean setSelection(int start, int end) {
+        //Log.d("GeckoAppJava", "IME: setSelection");
+
+        if (mComposing) {
+            /* Translate to fake selection positions */
+            start -= mCompositionStart;
+            end -= mCompositionStart;
+
+            if (start < 0)
+                start = 0;
+            else if (start > mComposingText.length())
+                start = mComposingText.length();
+
+            if (end < 0)
+                end = 0;
+            else if (end > mComposingText.length())
+                end = mComposingText.length();
+
+            mCompositionSelStart = start;
+            mCompositionSelLen = end - start;
+        } else {
+            GeckoAppShell.sendEventToGecko(
+                new GeckoEvent(GeckoEvent.IME_SET_SELECTION,
+                               start, end - start));
+        }
+        return true;
+    }
+
+    public boolean onKeyDel() {
+        // Some IMEs don't update us on deletions
+        // In that case we are not updated when a composition
+        // is destroyed, and Bad Things happen
+
+        if (!mComposing)
+            return false;
+
+        if (mComposingText.length() > 0) {
+            mComposingText = mComposingText.substring(0,
+                mComposingText.length() - 1);
+            if (mComposingText.length() > 0)
+                return false;
+        }
+
+        commitText(null, 1);
+        return true;
+    }
 
-    int mSelectionStart, mSelectionEnd;
+    public void notifyTextChange(InputMethodManager imm, String text,
+                                 int start, int oldEnd, int newEnd) {
+        //Log.d("GeckoAppJava", "IME: notifyTextChange");
+
+        if (mUpdateRequest == null)
+            return;
+
+        mUpdateExtract.flags = 0;
+        mUpdateExtract.partialStartOffset = 0;
+        mUpdateExtract.partialEndOffset = oldEnd;
+
+        // Faster to not query for selection
+        mUpdateExtract.selectionStart = newEnd;
+        mUpdateExtract.selectionEnd = newEnd;
+
+        mUpdateExtract.text = text;
+        mUpdateExtract.startOffset = 0;
+
+        imm.updateExtractedText(GeckoApp.surfaceView,
+            mUpdateRequest.token, mUpdateExtract);
+    }
+
+    public void notifySelectionChange(InputMethodManager imm,
+                                      int start, int end) {
+        //Log.d("GeckoAppJava", "IME: notifySelectionChange");
+
+        if (mComposing)
+            imm.updateSelection(GeckoApp.surfaceView,
+                mCompositionStart + mCompositionSelStart,
+                mCompositionStart + mCompositionSelStart + mCompositionSelLen,
+                mCompositionStart,
+                mCompositionStart + mComposingText.length());
+        else
+            imm.updateSelection(GeckoApp.surfaceView, start, end, -1, -1);
+    }
+
+    // Is a composition active?
+    boolean mComposing;
+    // Composition text when a composition is active
+    String mComposingText;
+    // Start index of the composition within the text body
+    int mCompositionStart;
+    /* During a composition, we should not alter the real selection,
+        therefore we keep our own offsets to emulate selection */
+    // Start of fake selection, relative to start of composition
+    int mCompositionSelStart;
+    // Length of fake selection
+    int mCompositionSelLen;
+
+    ExtractedTextRequest mUpdateRequest;
+    final ExtractedText mUpdateExtract = new ExtractedText();
+
+    int mSelectionStart, mSelectionLength;
     SynchronousQueue<String> mQueryResult;
 }
+
--- a/embedding/android/GeckoSurfaceView.java
+++ b/embedding/android/GeckoSurfaceView.java
@@ -228,16 +228,19 @@ class GeckoSurfaceView
 
     @Override
     public boolean onCheckIsTextEditor () {
         return false;
     }
 
     @Override
     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        if (!mIMEFocus)
+            return null;
+
         outAttrs.inputType = InputType.TYPE_CLASS_TEXT |
                              InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
         return inputConnection;
     }
 
     // accelerometer
     public void onAccuracyChanged(Sensor sensor, int accuracy)
     {
@@ -300,14 +303,15 @@ class GeckoSurfaceView
 
     // the dimensions of the buffer we're using for drawing,
     // that is the software buffer or the EGLSurface
     int mBufferWidth;
     int mBufferHeight;
 
     // IME stuff
     GeckoInputConnection inputConnection;
+    boolean mIMEFocus;
     int mIMEState;
 
     // Software rendering
     ByteBuffer mSoftwareBuffer;
     Bitmap mSoftwareBitmap;
 }
--- a/widget/src/android/AndroidBridge.cpp
+++ b/widget/src/android/AndroidBridge.cpp
@@ -86,17 +86,18 @@ AndroidBridge::Init(JNIEnv *jEnv,
 {
     jEnv->GetJavaVM(&mJavaVM);
 
     mJNIEnv = nsnull;
     mThread = nsnull;
 
     mGeckoAppShellClass = (jclass) jEnv->NewGlobalRef(jGeckoAppShellClass);
 
-    jShowIME = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showIME", "(I)V");
+    jNotifyIME = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIME", "(II)V");
+    jNotifyIMEChange = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "notifyIMEChange", "(Ljava/lang/String;III)V");
     jEnableAccelerometer = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableAccelerometer", "(Z)V");
     jEnableLocation = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "enableLocation", "(Z)V");
     jReturnIMEQueryResult = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "returnIMEQueryResult", "(Ljava/lang/String;II)V");
     jScheduleRestart = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "scheduleRestart", "()V");
     jNotifyXreExit = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "onXreExit", "()V");
     jGetHandlersForMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getHandlersForMimeType", "(Ljava/lang/String;)[Ljava/lang/String;");
     jOpenUriExternal = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "openUriExternal", "(Ljava/lang/String;Ljava/lang/String;)Z");
     jGetMimeTypeFromExtension = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getMimeTypeFromExtension", "(Ljava/lang/String;)Ljava/lang/String;");
@@ -178,41 +179,59 @@ AndroidBridge::EnsureJNIThread()
 
     if ((void*)pthread_self() != mThread) {
         ALOG("###!!!!!!! Something's grabbing the JNIEnv from the wrong thread! (thr %p should be %p)",
              (void*)pthread_self(), (void*)mThread);
     }
 }
 
 void
-AndroidBridge::ShowIME(int aState)
+AndroidBridge::NotifyIME(int aType, int aState)
 {
-    mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jShowIME, aState);
+    mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass,
+                                  jNotifyIME, aType, aState);
+}
+
+void
+AndroidBridge::NotifyIMEChange(const PRUnichar *aText, PRUint32 aTextLen,
+                               int aStart, int aEnd, int aNewEnd)
+{
+    jvalue args[4];
+    AutoLocalJNIFrame jniFrame(1);
+    args[0].l = mJNIEnv->NewString(aText, aTextLen);
+    args[1].i = aStart;
+    args[2].i = aEnd;
+    args[3].i = aNewEnd;
+    mJNIEnv->CallStaticVoidMethodA(mGeckoAppShellClass,
+                                   jNotifyIMEChange, args);
 }
 
 void
 AndroidBridge::EnableAccelerometer(bool aEnable)
 {
     mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jEnableAccelerometer, aEnable);
 }
 
 void
 AndroidBridge::EnableLocation(bool aEnable)
 {
     mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jEnableLocation, aEnable);
 }
 
 void
-AndroidBridge::ReturnIMEQueryResult(const PRUnichar *result, PRUint32 len, int selectionStart, int selectionEnd)
+AndroidBridge::ReturnIMEQueryResult(const PRUnichar *aResult, PRUint32 aLen,
+                                    int aSelStart, int aSelLen)
 {
     jvalue args[3];
-    args[0].l = mJNIEnv->NewString(result, len);
-    args[1].i = selectionStart;
-    args[2].i = selectionEnd;
-    mJNIEnv->CallStaticVoidMethodA(mGeckoAppShellClass, jReturnIMEQueryResult, args);
+    AutoLocalJNIFrame jniFrame(1);
+    args[0].l = mJNIEnv->NewString(aResult, aLen);
+    args[1].i = aSelStart;
+    args[2].i = aSelLen;
+    mJNIEnv->CallStaticVoidMethodA(mGeckoAppShellClass,
+                                   jReturnIMEQueryResult, args);
 }
 
 void
 AndroidBridge::ScheduleRestart()
 {
     ALOG("scheduling reboot");
     mJNIEnv->CallStaticVoidMethod(mGeckoAppShellClass, jScheduleRestart);
 }
--- a/widget/src/android/AndroidBridge.h
+++ b/widget/src/android/AndroidBridge.h
@@ -54,16 +54,24 @@
 
 class nsWindow;
 
 namespace mozilla {
 
 class AndroidBridge
 {
 public:
+    enum {
+        NOTIFY_IME_RESETINPUTSTATE = 0,
+        NOTIFY_IME_SETOPENSTATE = 1,
+        NOTIFY_IME_SETENABLED = 2,
+        NOTIFY_IME_CANCELCOMPOSITION = 3,
+        NOTIFY_IME_FOCUSCHANGE = 4
+    };
+
     static AndroidBridge *ConstructBridge(JNIEnv *jEnv,
                                           jclass jGeckoAppShellClass);
 
     static AndroidBridge *Bridge() {
         return sBridge;
     }
 
     static JavaVM *VM() {
@@ -84,23 +92,25 @@ public:
     // SetMainThread should be called which will create the JNIEnv for
     // us to use.  toolkit/xre/nsAndroidStartup.cpp calls
     // SetMainThread.
     PRBool SetMainThread(void *thr);
 
     JNIEnv* AttachThread(PRBool asDaemon = PR_TRUE);
 
     /* These are all implemented in Java */
-    void ShowIME(int aState);
+    void NotifyIME(int aType, int aState);
+
+    void NotifyIMEChange(const PRUnichar *aText, PRUint32 aTextLen, int aStart, int aEnd, int aNewEnd);
 
     void EnableAccelerometer(bool aEnable);
 
     void EnableLocation(bool aEnable);
 
-    void ReturnIMEQueryResult(const PRUnichar *result, PRUint32 len, int selectionStart, int selectionEnd);
+    void ReturnIMEQueryResult(const PRUnichar *aResult, PRUint32 aLen, int aSelStart, int aSelLen);
 
     void NotifyXreExit();
 
     void ScheduleRestart();
 
     void SetSurfaceView(jobject jobj);
     AndroidGeckoSurfaceView& SurfaceView() { return mSurfaceView; }
 
@@ -149,17 +159,18 @@ protected:
     jclass mGeckoAppShellClass;
 
     AndroidBridge() { }
     PRBool Init(JNIEnv *jEnv, jclass jGeckoApp);
 
     void EnsureJNIThread();
 
     // other things
-    jmethodID jShowIME;
+    jmethodID jNotifyIME;
+    jmethodID jNotifyIMEChange;
     jmethodID jEnableAccelerometer;
     jmethodID jEnableLocation;
     jmethodID jReturnIMEQueryResult;
     jmethodID jNotifyXreExit;
     jmethodID jScheduleRestart;
     jmethodID jGetOutstandingDrawEvents;
     jmethodID jGetHandlersForMimeType;
     jmethodID jOpenUriExternal;
--- a/widget/src/android/AndroidJavaWrappers.cpp
+++ b/widget/src/android/AndroidJavaWrappers.cpp
@@ -52,18 +52,22 @@ jfieldID AndroidGeckoEvent::jZField = 0;
 jfieldID AndroidGeckoEvent::jRectField = 0;
 jfieldID AndroidGeckoEvent::jNativeWindowField = 0;
 
 jfieldID AndroidGeckoEvent::jCharactersField = 0;
 jfieldID AndroidGeckoEvent::jKeyCodeField = 0;
 jfieldID AndroidGeckoEvent::jMetaStateField = 0;
 jfieldID AndroidGeckoEvent::jFlagsField = 0;
 jfieldID AndroidGeckoEvent::jUnicodeCharField = 0;
+jfieldID AndroidGeckoEvent::jOffsetField = 0;
 jfieldID AndroidGeckoEvent::jCountField = 0;
-jfieldID AndroidGeckoEvent::jCount2Field = 0;
+jfieldID AndroidGeckoEvent::jRangeTypeField = 0;
+jfieldID AndroidGeckoEvent::jRangeStylesField = 0;
+jfieldID AndroidGeckoEvent::jRangeForeColorField = 0;
+jfieldID AndroidGeckoEvent::jRangeBackColorField = 0;
 jfieldID AndroidGeckoEvent::jLocationField = 0;
 
 jclass AndroidPoint::jPointClass = 0;
 jfieldID AndroidPoint::jXField = 0;
 jfieldID AndroidPoint::jYField = 0;
 
 jclass AndroidRect::jRectClass = 0;
 jfieldID AndroidRect::jBottomField = 0;
@@ -128,18 +132,22 @@ AndroidGeckoEvent::InitGeckoEventClass(J
     jRectField = getField("mRect", "Landroid/graphics/Rect;");
     jNativeWindowField = getField("mNativeWindow", "I");
 
     jCharactersField = getField("mCharacters", "Ljava/lang/String;");
     jKeyCodeField = getField("mKeyCode", "I");
     jMetaStateField = getField("mMetaState", "I");
     jFlagsField = getField("mFlags", "I");
     jUnicodeCharField = getField("mUnicodeChar", "I");
+    jOffsetField = getField("mOffset", "I");
     jCountField = getField("mCount", "I");
-    jCount2Field = getField("mCount2", "I");
+    jRangeTypeField = getField("mRangeType", "I");
+    jRangeStylesField = getField("mRangeStyles", "I");
+    jRangeForeColorField = getField("mRangeForeColor", "I");
+    jRangeBackColorField = getField("mRangeBackColor", "I");
     jLocationField = getField("mLocation", "Landroid/location/Location;");
 }
 
 void
 AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(JNIEnv *jEnv)
 {
     initInit();
 
@@ -290,19 +298,31 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
             mTime = jenv->GetLongField(jobj, jTimeField);
             mCount = jenv->GetIntField(jobj, jCountField);
             ReadP0Field(jenv);
             if (mCount > 1)
                 ReadP1Field(jenv);
             break;
 
         case IME_EVENT:
-            mCount = jenv->GetIntField(jobj, jCountField);
-            mCount2 = jenv->GetIntField(jobj, jCount2Field);
-            ReadCharactersField(jenv);
+            if (mAction == IME_GET_TEXT || mAction == IME_SET_SELECTION) {
+                mOffset = jenv->GetIntField(jobj, jOffsetField);
+                mCount = jenv->GetIntField(jobj, jCountField);
+            } else if (mAction == IME_SET_TEXT || mAction == IME_ADD_RANGE) {
+                if (mAction == IME_SET_TEXT)
+                    ReadCharactersField(jenv);
+                mOffset = jenv->GetIntField(jobj, jOffsetField);
+                mCount = jenv->GetIntField(jobj, jCountField);
+                mRangeType = jenv->GetIntField(jobj, jRangeTypeField);
+                mRangeStyles = jenv->GetIntField(jobj, jRangeStylesField);
+                mRangeForeColor =
+                    jenv->GetIntField(jobj, jRangeForeColorField);
+                mRangeBackColor =
+                    jenv->GetIntField(jobj, jRangeBackColorField);
+            }
             break;
 
         case DRAW:
             ReadRectField(jenv);
             break;
 
         case SENSOR_EVENT:
             mX = jenv->GetFloatField(jobj, jXField);
@@ -329,16 +349,17 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jo
     ALOG("AndroidGeckoEvent: %p : %d %p", (void*)jobj, mType, (void*)mNativeWindow);
 #endif
 }
 
 void
 AndroidGeckoEvent::Init(int aType)
 {
     mType = aType;
+    mNativeWindow = nsnull;
 }
 
 void
 AndroidGeckoEvent::Init(void *window, int x1, int y1, int x2, int y2)
 {
     mType = DRAW;
     mNativeWindow = window;
     mRect.Empty();
--- a/widget/src/android/AndroidJavaWrappers.h
+++ b/widget/src/android/AndroidJavaWrappers.h
@@ -377,31 +377,37 @@ public:
     float Y() { return mY; }
     float Z() { return mZ; }
     const nsIntRect& Rect() { return mRect; }
     nsAString& Characters() { return mCharacters; }
     int KeyCode() { return mKeyCode; }
     int MetaState() { return mMetaState; }
     int Flags() { return mFlags; }
     int UnicodeChar() { return mUnicodeChar; }
+    int Offset() { return mOffset; }
     int Count() { return mCount; }
-    int Count2() { return mCount2; }
+    int RangeType() { return mRangeType; }
+    int RangeStyles() { return mRangeStyles; }
+    int RangeForeColor() { return mRangeForeColor; }
+    int RangeBackColor() { return mRangeBackColor; }
     nsGeoPosition* GeoPosition() { return mGeoPosition; }
 
 protected:
     int mAction;
     int mType;
     int64_t mTime;
     void *mNativeWindow;
     nsIntPoint mP0;
     nsIntPoint mP1;
     nsIntRect mRect;
     int mFlags, mMetaState;
     int mKeyCode, mUnicodeChar;
-    int mCount, mCount2;
+    int mOffset, mCount;
+    int mRangeType, mRangeStyles;
+    int mRangeForeColor, mRangeBackColor;
     float mX, mY, mZ;
     nsString mCharacters;
     nsRefPtr<nsGeoPosition> mGeoPosition;
 
     void ReadP0Field(JNIEnv *jenv);
     void ReadP1Field(JNIEnv *jenv);
     void ReadRectField(JNIEnv *jenv);
     void ReadCharactersField(JNIEnv *jenv);
@@ -417,19 +423,23 @@ protected:
     static jfieldID jZField;
     static jfieldID jRectField;
     static jfieldID jNativeWindowField;
 
     static jfieldID jCharactersField;
     static jfieldID jKeyCodeField;
     static jfieldID jMetaStateField;
     static jfieldID jFlagsField;
+    static jfieldID jOffsetField;
     static jfieldID jCountField;
-    static jfieldID jCount2Field;
     static jfieldID jUnicodeCharField;
+    static jfieldID jRangeTypeField;
+    static jfieldID jRangeStylesField;
+    static jfieldID jRangeForeColorField;
+    static jfieldID jRangeBackColorField;
     static jfieldID jLocationField;
 
 public:
     enum {
         NATIVE_POKE = 0,
         KEY_EVENT = 1,
         MOTION_EVENT = 2,
         SENSOR_EVENT = 3,
@@ -439,21 +449,24 @@ public:
         SIZE_CHANGED = 7,
         ACTIVITY_STOPPING = 8,
         ACTIVITY_PAUSING = 9,
         LOAD_URI = 10,
         dummy_java_enum_list_end
     };
 
     enum {
-        IME_BATCH_END = 0,
-        IME_BATCH_BEGIN = 1,
+        IME_COMPOSITION_END = 0,
+        IME_COMPOSITION_BEGIN = 1,
         IME_SET_TEXT = 2,
         IME_GET_TEXT = 3,
-        IME_DELETE_TEXT = 4
+        IME_DELETE_TEXT = 4,
+        IME_SET_SELECTION = 5,
+        IME_GET_SELECTION = 6,
+        IME_ADD_RANGE = 7
     };
 };
 
 class nsJNIString : public nsString
 {
 public:
     nsJNIString(jstring jstr);
 };
--- a/widget/src/android/nsWindow.cpp
+++ b/widget/src/android/nsWindow.cpp
@@ -74,17 +74,16 @@ static PRBool gLeftAlt;
 static PRBool gRightAlt;
 static PRBool gSym;
 
 // All the toplevel windows that have been created; these are in
 // stacking order, so the window at gAndroidBounds[0] is the topmost
 // one.
 static nsTArray<nsWindow*> gTopLevelWindows;
 static nsWindow* gFocusedWindow = nsnull;
-static PRUint32 gIMEState;
 
 static nsRefPtr<gl::GLContext> sGLContext;
 static PRBool sFailedToCreateGLContext = PR_FALSE;
 
 static nsWindow*
 TopWindow()
 {
     if (!gTopLevelWindows.IsEmpty())
@@ -1278,122 +1277,169 @@ nsWindow::OnKeyEvent(AndroidGeckoEvent *
         InitKeyEvent(pressEvent, *ae);
 #ifdef ANDROID_DEBUG_WIDGET
         ALOG("Dispatching key event with keyCode %d charCode %d shift %d alt %d sym/ctrl %d metamask %d", event.keyCode, event.charCode, event.isShift, event.isAlt, event.isControl, ae->MetaState());
 #endif
         DispatchEvent(&pressEvent);
     }
 }
 
-nsresult
-nsWindow::GetCurrentOffset(PRUint32 &aOffset, PRUint32 &aLength)
-{
-    nsQueryContentEvent event(PR_TRUE, NS_QUERY_SELECTED_TEXT, this);
-    DispatchEvent(&event);
-
-    if (!event.mSucceeded)
-        return NS_ERROR_FAILURE;
-
-    aOffset = event.mReply.mOffset;
-    aLength = event.mReply.mString.Length();
-    return NS_OK;
-}
+#ifdef ANDROID_DEBUG_IME
+#define ALOGIME(args...) ALOG(args)
+#else
+#define ALOGIME(args...)
+#endif
 
-nsresult
-nsWindow::DeleteRange(int aOffset, int aLen)
+void
+nsWindow::OnIMEAddRange(AndroidGeckoEvent *ae)
 {
-    nsSelectionEvent selectEvent(PR_TRUE, NS_SELECTION_SET, this);
-    selectEvent.mOffset = aOffset;
-    selectEvent.mLength = aLen;
-    DispatchEvent(&selectEvent);
-    NS_ENSURE_TRUE(selectEvent.mSucceeded, NS_ERROR_FAILURE);
-
-    nsContentCommandEvent event(PR_TRUE, NS_CONTENT_COMMAND_DELETE, this);
-    DispatchEvent(&event);
-    NS_ENSURE_TRUE(event.mSucceeded, NS_ERROR_FAILURE);
-
-    return NS_OK;
+    //ALOGIME("IME: IME_ADD_RANGE");
+    nsTextRange range;
+    range.mStartOffset = ae->Offset();
+    range.mEndOffset = range.mStartOffset + ae->Count();
+    range.mRangeType = ae->RangeType();
+    range.mRangeStyle.mDefinedStyles = ae->RangeStyles();
+    range.mRangeStyle.mLineStyle = nsTextRangeStyle::LINESTYLE_SOLID;
+    range.mRangeStyle.mForegroundColor = NS_RGBA(
+        ((ae->RangeForeColor() >> 16) & 0xff),
+        ((ae->RangeForeColor() >> 8) & 0xff),
+        (ae->RangeForeColor() & 0xff),
+        ((ae->RangeForeColor() >> 24) & 0xff));
+    range.mRangeStyle.mBackgroundColor = NS_RGBA(
+        ((ae->RangeBackColor() >> 16) & 0xff),
+        ((ae->RangeBackColor() >> 8) & 0xff),
+        (ae->RangeBackColor() & 0xff),
+        ((ae->RangeBackColor() >> 24) & 0xff));
+    mIMERanges.AppendElement(range);
+    return;
 }
 
 void
 nsWindow::OnIMEEvent(AndroidGeckoEvent *ae)
 {
     switch (ae->Action()) {
-    case AndroidGeckoEvent::IME_BATCH_END:
+    case AndroidGeckoEvent::IME_COMPOSITION_END:
         {
+            ALOGIME("IME: IME_COMPOSITION_END");
             nsCompositionEvent event(PR_TRUE, NS_COMPOSITION_END, this);
-            event.time = PR_Now() / 1000;
+            InitEvent(event, nsnull);
             DispatchEvent(&event);
+            mIMEComposing = PR_FALSE;
         }
         return;
-    case AndroidGeckoEvent::IME_BATCH_BEGIN:
+    case AndroidGeckoEvent::IME_COMPOSITION_BEGIN:
         {
+            ALOGIME("IME: IME_COMPOSITION_BEGIN");
             nsCompositionEvent event(PR_TRUE, NS_COMPOSITION_START, this);
-            event.time = PR_Now() / 1000;
+            InitEvent(event, nsnull);
             DispatchEvent(&event);
+            mIMEComposing = PR_TRUE;
+        }
+        return;
+    case AndroidGeckoEvent::IME_ADD_RANGE:
+        {
+            OnIMEAddRange(ae);
         }
         return;
     case AndroidGeckoEvent::IME_SET_TEXT:
         {
+            OnIMEAddRange(ae);
+
             nsTextEvent event(PR_TRUE, NS_TEXT_TEXT, this);
+            InitEvent(event, nsnull);
+
             event.theText.Assign(ae->Characters());
-            event.time = PR_Now() / 1000;
+            event.rangeArray = mIMERanges.Elements();
+            event.rangeCount = mIMERanges.Length();
+
+            ALOGIME("IME: IME_SET_TEXT: l=%u, r=%u",
+                event.theText.Length(), mIMERanges.Length());
+
             DispatchEvent(&event);
+            mIMERanges.Clear();
         }
         return;
     case AndroidGeckoEvent::IME_GET_TEXT:
         {
-            PRUint32 offset, len;
-            if (NS_FAILED(GetCurrentOffset(offset, len))) {
-                AndroidBridge::Bridge()->ReturnIMEQueryResult(nsnull, 0, 0, 0);
+            ALOGIME("IME: IME_GET_TEXT: o=%u, l=%u", ae->Offset(), ae->Count());
+
+            nsQueryContentEvent event(PR_TRUE, NS_QUERY_TEXT_CONTENT, this);
+            InitEvent(event, nsnull);
+
+            event.InitForQueryTextContent(ae->Offset(), ae->Count());
+            
+            DispatchEvent(&event);
+
+            if (!event.mSucceeded) {
+                ALOGIME("IME:     -> failed");
+                AndroidBridge::Bridge()->ReturnIMEQueryResult(
+                    nsnull, 0, 0, 0);
                 return;
             }
 
-            PRUint32 readLen = ae->Count();
-            PRUint32 readOffset = offset;
-            if (!ae->Count() && !ae->Count2()) {
-                readOffset = 0;
-                readLen = PR_UINT32_MAX;
-            } else if (!readLen) { // backwards
-                readLen = ae->Count2();
-                if (readLen > offset) {
-                    readLen = offset;
-                    readOffset = 0;
-                } else
-                    readOffset -= readLen;
-            } else
-                readOffset += len;
+            AndroidBridge::Bridge()->ReturnIMEQueryResult(
+                event.mReply.mString.get(), 
+                event.mReply.mString.Length(), 0, 0);
+            //ALOGIME("IME:     -> l=%u", event.mReply.mString.Length());
+        }
+        return;
+    case AndroidGeckoEvent::IME_DELETE_TEXT:
+        {   
+            ALOGIME("IME: IME_DELETE_TEXT");
+            nsContentCommandEvent event(PR_TRUE,
+                                        NS_CONTENT_COMMAND_DELETE, this);
+            InitEvent(event, nsnull);
+            DispatchEvent(&event);
+        }
+        return;
+    case AndroidGeckoEvent::IME_SET_SELECTION:
+        {
+            ALOGIME("IME: IME_SET_SELECTION: o=%u, l=%d", ae->Offset(), ae->Count());
 
-            nsQueryContentEvent event(PR_TRUE, NS_QUERY_TEXT_CONTENT, this);
-            event.InitForQueryTextContent(0, PR_UINT32_MAX);
+            nsSelectionEvent selEvent(PR_TRUE, NS_SELECTION_SET, this);
+            InitEvent(selEvent, nsnull);
+
+            selEvent.mOffset = PRUint32(ae->Count() >= 0 ?
+                                        ae->Offset() :
+                                        ae->Offset() + ae->Count());
+            selEvent.mLength = PRUint32(PR_ABS(ae->Count()));
+            selEvent.mReversed = ae->Count() >= 0 ? PR_FALSE : PR_TRUE;
+
+            DispatchEvent(&selEvent);
+        }
+        return;
+    case AndroidGeckoEvent::IME_GET_SELECTION:
+        {
+            ALOGIME("IME: IME_GET_SELECTION");
+
+            nsQueryContentEvent event(PR_TRUE, NS_QUERY_SELECTED_TEXT, this);
+            InitEvent(event, nsnull);
             DispatchEvent(&event);
 
             if (!event.mSucceeded) {
-                AndroidBridge::Bridge()->ReturnIMEQueryResult(nsnull, 0, 0, 0);
+                ALOGIME("IME:     -> failed");
+                AndroidBridge::Bridge()->ReturnIMEQueryResult(
+                    nsnull, 0, 0, 0);
                 return;
             }
 
-            nsAutoString textContent(Substring(event.mReply.mString, readOffset, readLen));
-            AndroidBridge::Bridge()->ReturnIMEQueryResult(textContent.get(), textContent.Length(), offset, offset + len);
-        }
-        return;
-    case AndroidGeckoEvent::IME_DELETE_TEXT:
-        {
-            PRUint32 offset, len;
-            PRUint32 count = ae->Count();
-            if (NS_FAILED(GetCurrentOffset(offset, len)))
-                return;
+            int selStart = int(event.mReply.mOffset + 
+                            (event.mReply.mReversed ? 
+                                event.mReply.mString.Length() : 0));
 
-            DeleteRange(offset + len, ae->Count2());
-            offset -= ae->Count();
-            if (offset < 0) {
-                count += offset;
-                offset = 0;
-            }
-            DeleteRange(offset, count);
+            int selLength = event.mReply.mReversed ?
+                                int(event.mReply.mString.Length()) : 
+                                -int(event.mReply.mString.Length());
+
+            AndroidBridge::Bridge()->ReturnIMEQueryResult(
+                event.mReply.mString.get(),
+                event.mReply.mString.Length(), 
+                selStart, selLength);
+
+            //ALOGIME("IME:     -> o=%u, l=%u", event.mReply.mOffset, event.mReply.mString.Length());
         }
         return;
     }
 }
 
 nsWindow *
 nsWindow::FindWindowForPoint(const nsIntPoint& pt)
 {
@@ -1419,22 +1465,132 @@ nsWindow::UserActivity()
   }
 
   if (mIdleService) {
     mIdleService->ResetIdleTimeOut();
   }
 }
 
 NS_IMETHODIMP
+nsWindow::ResetInputState()
+{
+    //ALOGIME("IME: ResetInputState: s=%d", aState);
+
+    // Cancel composition on Gecko side
+    if (mIMEComposing) {
+        nsCompositionEvent event(PR_TRUE, NS_COMPOSITION_END, this);
+        InitEvent(event, nsnull);
+        DispatchEvent(&event);
+        mIMEComposing = PR_FALSE;
+    }
+
+    if (AndroidBridge::Bridge())
+        AndroidBridge::Bridge()->NotifyIME(
+            AndroidBridge::NOTIFY_IME_RESETINPUTSTATE, 0);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsWindow::SetIMEEnabled(PRUint32 aState)
 {
-    gIMEState = aState;
+    ALOGIME("IME: SetIMEEnabled: s=%d", aState);
+
+    mIMEEnabled = aState;
     if (AndroidBridge::Bridge())
-        AndroidBridge::Bridge()->ShowIME(aState);
+        AndroidBridge::Bridge()->NotifyIME(
+            AndroidBridge::NOTIFY_IME_SETENABLED, int(aState));
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindow::GetIMEEnabled(PRUint32* aState)
 {
-    *aState = gIMEState;
+    *aState = mIMEEnabled;
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::CancelIMEComposition()
+{
+    ALOGIME("IME: CancelIMEComposition");
+
+    // Cancel composition on Gecko side
+    if (mIMEComposing) {
+        nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, this);
+        InitEvent(textEvent, nsnull);
+        DispatchEvent(&textEvent);
+
+        nsCompositionEvent compEvent(PR_TRUE, NS_COMPOSITION_END, this);
+        InitEvent(compEvent, nsnull);
+        DispatchEvent(&compEvent);
+        mIMEComposing = PR_FALSE;
+    }
+
+    if (AndroidBridge::Bridge())
+        AndroidBridge::Bridge()->NotifyIME(
+            AndroidBridge::NOTIFY_IME_CANCELCOMPOSITION, 0);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::OnIMEFocusChange(PRBool aFocus)
+{
+    ALOGIME("IME: OnIMEFocusChange: f=%d", aFocus);
+    
+    if (AndroidBridge::Bridge())
+        AndroidBridge::Bridge()->NotifyIME(
+            AndroidBridge::NOTIFY_IME_FOCUSCHANGE, int(aFocus));
     return NS_OK;
 }
+
+NS_IMETHODIMP
+nsWindow::OnIMETextChange(PRUint32 aStart, PRUint32 aOldEnd, PRUint32 aNewEnd)
+{
+    ALOGIME("IME: OnIMETextChange: s=%d, oe=%d, ne=%d",
+            aStart, aOldEnd, aNewEnd);
+
+    // A quirk in Android makes it necessary to pass the whole text
+    // from index 0 to index aNewEnd. The more efficient way would
+    // have been passing the substring from index aStart to index aNewEnd
+
+    if (aNewEnd > 0) {
+        nsQueryContentEvent event(PR_TRUE, NS_QUERY_TEXT_CONTENT, this);
+        InitEvent(event, nsnull);
+        event.InitForQueryTextContent(0, aNewEnd);
+
+        DispatchEvent(&event);
+        if (!event.mSucceeded)
+            return NS_OK;
+
+        if (AndroidBridge::Bridge())
+            AndroidBridge::Bridge()->NotifyIMEChange(
+                event.mReply.mString.get(),
+                event.mReply.mString.Length(),
+                aStart, aOldEnd, aNewEnd);
+    } else {
+        if (AndroidBridge::Bridge())
+            AndroidBridge::Bridge()->NotifyIMEChange(
+                nsnull, 0,
+                aStart, aOldEnd, aNewEnd);
+    }
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::OnIMESelectionChange(void)
+{
+    ALOGIME("IME: OnIMESelectionChange");
+
+    nsQueryContentEvent event(PR_TRUE, NS_QUERY_SELECTED_TEXT, this);
+    InitEvent(event, nsnull);
+
+    DispatchEvent(&event);
+    if (!event.mSucceeded)
+        return NS_OK;
+
+    if (AndroidBridge::Bridge())
+        AndroidBridge::Bridge()->NotifyIMEChange(
+            nsnull, 0,
+            int(event.mReply.mOffset),
+            int(event.mReply.mOffset + event.mReply.mString.Length()), -1);
+    return NS_OK;
+}
+
--- a/widget/src/android/nsWindow.h
+++ b/widget/src/android/nsWindow.h
@@ -149,38 +149,47 @@ public:
     NS_IMETHOD CaptureRollupEvents(nsIRollupListener *aListener,
                                    nsIMenuRollup *aMenuRollup,
                                    PRBool aDoCapture,
                                    PRBool aConsumeRollupEvent) { return NS_ERROR_NOT_IMPLEMENTED; }
 
     NS_IMETHOD GetAttention(PRInt32 aCycleCount) { return NS_ERROR_NOT_IMPLEMENTED; }
     NS_IMETHOD BeginResizeDrag(nsGUIEvent* aEvent, PRInt32 aHorizontal, PRInt32 aVertical) { return NS_ERROR_NOT_IMPLEMENTED; }
 
+    NS_IMETHOD ResetInputState();
     NS_IMETHOD SetIMEEnabled(PRUint32 aState);
     NS_IMETHOD GetIMEEnabled(PRUint32* aState);
+    NS_IMETHOD CancelIMEComposition();
+
+    NS_IMETHOD OnIMEFocusChange(PRBool aFocus);
+    NS_IMETHOD OnIMETextChange(PRUint32 aStart, PRUint32 aOldEnd, PRUint32 aNewEnd);
+    NS_IMETHOD OnIMESelectionChange(void);
 
     gfxASurface* GetThebesSurface();
 
 protected:
     void BringToFront();
     nsWindow *FindTopLevel();
     PRBool DrawTo(gfxASurface *targetSurface);
     PRBool IsTopLevel();
-    nsresult GetCurrentOffset(PRUint32 &aOffset, PRUint32 &aLength);
-    nsresult DeleteRange(int aOffset, int aLen);
+    void OnIMEAddRange(mozilla::AndroidGeckoEvent *ae);
 
     // Call this function when the users activity is the direct cause of an
     // event (like a keypress or mouse click).
     void UserActivity();
 
     PRPackedBool mIsVisible;
     nsTArray<nsWindow*> mChildren;
     nsWindow* mParent;
     double mStartDist;
     nsCOMPtr<nsIdleService> mIdleService;
+    
+    PRUint32 mIMEEnabled;
+    PRBool mIMEComposing;
+    nsAutoTArray<nsTextRange, 4> mIMERanges;
 
     static void DumpWindows();
     static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
     static void LogWindow(nsWindow *win, int index, int indent);
 
 private:
     void InitKeyEvent(nsKeyEvent& event, mozilla::AndroidGeckoEvent& key);
     void HandleSpecialKey(mozilla::AndroidGeckoEvent *ae);